378 lines
14 KiB
Bash
Executable File
378 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# /usr/local/bin/opsec-harden.sh — System hardening apply/revert
|
|
# Usage: sudo opsec-harden.sh apply|revert
|
|
#
|
|
# Reads settings from /etc/opsec/opsec.conf
|
|
# Handles: core dumps, swap, timezone, locale, screen lock, WebRTC,
|
|
# USB blocking, clipboard auto-clear, traffic jitter
|
|
|
|
## NOTE: Do NOT use "set -euo pipefail" — it causes silent crashes
|
|
## when commands like sysctl, dconf, tc, etc. return non-zero.
|
|
## Use explicit error handling instead (|| true).
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo "[-] Please run as root"
|
|
exit 1
|
|
fi
|
|
|
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
|
BACKUP_DIR="/etc/opsec/.harden-backup"
|
|
|
|
# Source shared library
|
|
if [ -f "$OPSEC_LIB" ]; then
|
|
. "$OPSEC_LIB"
|
|
else
|
|
# Minimal fallback
|
|
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
|
|
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
|
|
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
|
|
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
|
|
opsec_load_config() {
|
|
[ -f /etc/opsec/opsec.conf ] && . /etc/opsec/opsec.conf
|
|
}
|
|
fi
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
opsec_load_config 2>/dev/null || true
|
|
|
|
# ─── CORE DUMPS ────────────────────────────────────────────────────────────────
|
|
|
|
apply_core_dumps() {
|
|
if [ "${HARDEN_CORE_DUMPS:-1}" = "1" ]; then
|
|
opsec_info "Disabling core dumps..."
|
|
|
|
# sysctl
|
|
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1
|
|
sysctl -w fs.suid_dumpable=0 >/dev/null 2>&1
|
|
|
|
# limits.d
|
|
cat > /etc/security/limits.d/opsec-coredump.conf << 'EOF'
|
|
# OPSEC: Disable core dumps
|
|
* hard core 0
|
|
* soft core 0
|
|
EOF
|
|
# systemd coredump
|
|
mkdir -p /etc/systemd/coredump.conf.d
|
|
cat > /etc/systemd/coredump.conf.d/opsec.conf << 'EOF'
|
|
[Coredump]
|
|
Storage=none
|
|
ProcessSizeMax=0
|
|
EOF
|
|
opsec_green "Core dumps disabled"
|
|
fi
|
|
}
|
|
|
|
revert_core_dumps() {
|
|
sysctl -w kernel.core_pattern='core' >/dev/null 2>&1
|
|
sysctl -w fs.suid_dumpable=1 >/dev/null 2>&1
|
|
rm -f /etc/security/limits.d/opsec-coredump.conf
|
|
rm -f /etc/systemd/coredump.conf.d/opsec.conf
|
|
opsec_green "Core dumps restored"
|
|
}
|
|
|
|
# ─── SWAP ──────────────────────────────────────────────────────────────────────
|
|
|
|
apply_swap() {
|
|
if [ "${HARDEN_SWAP:-1}" = "1" ]; then
|
|
opsec_info "Disabling swap..."
|
|
swapoff -a 2>/dev/null || true
|
|
|
|
# Comment out swap entries in fstab (backup first)
|
|
if [ ! -f "$BACKUP_DIR/fstab.swap" ]; then
|
|
grep -E '^\s*[^#].*\sswap\s' /etc/fstab > "$BACKUP_DIR/fstab.swap" 2>/dev/null || true
|
|
fi
|
|
sed -i '/\sswap\s/s/^/#OPSEC# /' /etc/fstab 2>/dev/null || true
|
|
opsec_green "Swap disabled"
|
|
fi
|
|
}
|
|
|
|
revert_swap() {
|
|
sed -i 's/^#OPSEC# //' /etc/fstab 2>/dev/null || true
|
|
swapon -a 2>/dev/null || true
|
|
opsec_green "Swap restored"
|
|
}
|
|
|
|
# ─── IPv6 ──────────────────────────────────────────────────────────────────────
|
|
|
|
apply_ipv6() {
|
|
if [ "${HARDEN_IPV6:-1}" = "1" ]; then
|
|
opsec_info "Disabling IPv6..."
|
|
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null
|
|
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null
|
|
opsec_green "IPv6 disabled"
|
|
fi
|
|
}
|
|
|
|
revert_ipv6() {
|
|
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null
|
|
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null
|
|
opsec_green "IPv6 restored"
|
|
}
|
|
|
|
# ─── TIMEZONE SPOOF ────────────────────────────────────────────────────────────
|
|
|
|
apply_timezone() {
|
|
if [ "${HARDEN_TIMEZONE_SPOOF:-0}" = "1" ]; then
|
|
local tz="${HARDEN_TIMEZONE_VALUE:-UTC}"
|
|
opsec_info "Spoofing timezone to ${tz}..."
|
|
|
|
# Backup current timezone
|
|
timedatectl show -p Timezone --value > "$BACKUP_DIR/timezone.orig" 2>/dev/null || true
|
|
timedatectl set-timezone "$tz" 2>/dev/null || {
|
|
ln -sf "/usr/share/zoneinfo/${tz}" /etc/localtime
|
|
}
|
|
opsec_green "Timezone set to ${tz}"
|
|
fi
|
|
}
|
|
|
|
revert_timezone() {
|
|
if [ -f "$BACKUP_DIR/timezone.orig" ]; then
|
|
local orig_tz
|
|
orig_tz=$(cat "$BACKUP_DIR/timezone.orig")
|
|
timedatectl set-timezone "$orig_tz" 2>/dev/null || true
|
|
rm -f "$BACKUP_DIR/timezone.orig"
|
|
opsec_green "Timezone restored to ${orig_tz}"
|
|
fi
|
|
}
|
|
|
|
# ─── LOCALE SPOOF ─────────────────────────────────────────────────────────────
|
|
|
|
apply_locale() {
|
|
if [ "${HARDEN_LOCALE_SPOOF:-0}" = "1" ]; then
|
|
local loc="${HARDEN_LOCALE_VALUE:-en_US.UTF-8}"
|
|
opsec_info "Spoofing locale to ${loc}..."
|
|
|
|
# Backup
|
|
locale > "$BACKUP_DIR/locale.orig" 2>/dev/null || true
|
|
export LANG="$loc"
|
|
export LC_ALL="$loc"
|
|
echo "LANG=${loc}" > /etc/default/locale.opsec
|
|
opsec_green "Locale set to ${loc}"
|
|
fi
|
|
}
|
|
|
|
revert_locale() {
|
|
rm -f /etc/default/locale.opsec
|
|
if [ -f "$BACKUP_DIR/locale.orig" ]; then
|
|
rm -f "$BACKUP_DIR/locale.orig"
|
|
fi
|
|
opsec_green "Locale restored"
|
|
}
|
|
|
|
# ─── SCREEN LOCK ───────────────────────────────────────────────────────────────
|
|
|
|
apply_screen_lock() {
|
|
if [ "${HARDEN_SCREEN_LOCK:-1}" = "1" ]; then
|
|
local timeout="${HARDEN_SCREEN_LOCK_TIMEOUT:-300}"
|
|
opsec_info "Setting screen lock timeout to ${timeout}s..."
|
|
|
|
# Try GNOME dconf (runs as user via sudo)
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
if command -v dconf >/dev/null 2>&1; then
|
|
su - "$real_user" -c "
|
|
dconf write /org/gnome/desktop/session/idle-delay 'uint32 ${timeout}' 2>/dev/null
|
|
dconf write /org/gnome/desktop/screensaver/lock-enabled 'true' 2>/dev/null
|
|
dconf write /org/gnome/desktop/screensaver/lock-delay 'uint32 0' 2>/dev/null
|
|
" 2>/dev/null || true
|
|
fi
|
|
opsec_green "Screen lock set to ${timeout}s"
|
|
fi
|
|
}
|
|
|
|
revert_screen_lock() {
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
if command -v dconf >/dev/null 2>&1; then
|
|
su - "$real_user" -c "
|
|
dconf write /org/gnome/desktop/session/idle-delay 'uint32 900' 2>/dev/null
|
|
" 2>/dev/null || true
|
|
fi
|
|
opsec_green "Screen lock restored to default"
|
|
}
|
|
|
|
# ─── WEBRTC BLOCKING ──────────────────────────────────────────────────────────
|
|
|
|
apply_webrtc() {
|
|
if [ "${LEAK_WEBRTC_BLOCK:-1}" = "1" ]; then
|
|
opsec_info "Blocking WebRTC..."
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
local user_home
|
|
user_home=$(eval echo "~${real_user}")
|
|
|
|
# Firefox profiles
|
|
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
|
|
[ -d "$profile_dir" ] || continue
|
|
cat >> "${profile_dir}/user.js" << 'EOF'
|
|
// OPSEC: Disable WebRTC IP leak
|
|
user_pref("media.peerconnection.enabled", false);
|
|
user_pref("media.peerconnection.turn.disable", true);
|
|
user_pref("media.peerconnection.use_document_iceservers", false);
|
|
user_pref("media.peerconnection.video.enabled", false);
|
|
user_pref("media.peerconnection.identity.timeout", 1);
|
|
EOF
|
|
done
|
|
|
|
# Brave/Chromium policies
|
|
mkdir -p /etc/brave/policies/managed /etc/chromium/policies/managed
|
|
cat > /etc/brave/policies/managed/opsec-webrtc.json << 'EOF'
|
|
{ "WebRtcIPHandling": "disable_non_proxied_udp" }
|
|
EOF
|
|
cp /etc/brave/policies/managed/opsec-webrtc.json /etc/chromium/policies/managed/ 2>/dev/null || true
|
|
opsec_green "WebRTC blocked (Firefox + Brave/Chromium)"
|
|
fi
|
|
}
|
|
|
|
revert_webrtc() {
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
local user_home
|
|
user_home=$(eval echo "~${real_user}")
|
|
|
|
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
|
|
[ -d "$profile_dir" ] || continue
|
|
sed -i '/OPSEC: Disable WebRTC/,/peerconnection\.identity/d' "${profile_dir}/user.js" 2>/dev/null || true
|
|
done
|
|
rm -f /etc/brave/policies/managed/opsec-webrtc.json
|
|
rm -f /etc/chromium/policies/managed/opsec-webrtc.json
|
|
opsec_green "WebRTC restrictions removed"
|
|
}
|
|
|
|
# ─── USB BLOCKING ─────────────────────────────────────────────────────────────
|
|
|
|
apply_usb_block() {
|
|
if [ "${LEAK_USB_BLOCK:-0}" = "1" ]; then
|
|
opsec_info "Blocking new USB devices..."
|
|
echo 0 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
|
|
opsec_green "USB auto-probe disabled (new devices blocked)"
|
|
fi
|
|
}
|
|
|
|
revert_usb_block() {
|
|
echo 1 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
|
|
opsec_green "USB auto-probe restored"
|
|
}
|
|
|
|
# ─── CLIPBOARD AUTO-CLEAR ─────────────────────────────────────────────────────
|
|
|
|
apply_clipboard_clear() {
|
|
if [ "${HARDEN_CLIPBOARD_CLEAR:-0}" = "1" ]; then
|
|
opsec_info "Starting clipboard auto-clear..."
|
|
# Launch background clearer (clears clipboard every 30 seconds)
|
|
local pidfile="/var/run/opsec-clipboard.pid"
|
|
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
|
opsec_yellow "Clipboard cleaner already running"
|
|
return
|
|
fi
|
|
|
|
(
|
|
while true; do
|
|
sleep 30
|
|
# Clear X clipboard
|
|
if command -v xclip >/dev/null 2>&1; then
|
|
echo -n "" | xclip -selection clipboard 2>/dev/null || true
|
|
echo -n "" | xclip -selection primary 2>/dev/null || true
|
|
elif command -v xsel >/dev/null 2>&1; then
|
|
xsel --clipboard --clear 2>/dev/null || true
|
|
xsel --primary --clear 2>/dev/null || true
|
|
fi
|
|
done
|
|
) &
|
|
echo $! > "$pidfile"
|
|
opsec_green "Clipboard auto-clear active (every 30s)"
|
|
fi
|
|
}
|
|
|
|
revert_clipboard_clear() {
|
|
local pidfile="/var/run/opsec-clipboard.pid"
|
|
if [ -f "$pidfile" ]; then
|
|
kill "$(cat "$pidfile")" 2>/dev/null || true
|
|
rm -f "$pidfile"
|
|
fi
|
|
opsec_green "Clipboard auto-clear stopped"
|
|
}
|
|
|
|
# ─── TRAFFIC JITTER ───────────────────────────────────────────────────────────
|
|
|
|
apply_traffic_jitter() {
|
|
if [ "${TRAFFIC_JITTER_ENABLED:-0}" = "1" ]; then
|
|
local ms="${TRAFFIC_JITTER_MS:-50}"
|
|
opsec_info "Adding ${ms}ms traffic jitter..."
|
|
|
|
# Find primary outbound interface
|
|
local iface
|
|
iface=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
|
if [ -n "$iface" ]; then
|
|
# Remove existing qdisc first
|
|
tc qdisc del dev "$iface" root 2>/dev/null || true
|
|
tc qdisc add dev "$iface" root netem delay "${ms}ms" "${ms}ms" distribution normal 2>/dev/null || {
|
|
opsec_yellow "tc not available — jitter skipped"
|
|
return
|
|
}
|
|
echo "$iface" > "$BACKUP_DIR/jitter-iface"
|
|
opsec_green "Traffic jitter active on ${iface} (${ms}ms)"
|
|
else
|
|
opsec_yellow "No outbound interface found for jitter"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
revert_traffic_jitter() {
|
|
if [ -f "$BACKUP_DIR/jitter-iface" ]; then
|
|
local iface
|
|
iface=$(cat "$BACKUP_DIR/jitter-iface")
|
|
tc qdisc del dev "$iface" root 2>/dev/null || true
|
|
rm -f "$BACKUP_DIR/jitter-iface"
|
|
opsec_green "Traffic jitter removed from ${iface}"
|
|
fi
|
|
}
|
|
|
|
# ─── MAIN ──────────────────────────────────────────────────────────────────────
|
|
|
|
do_apply() {
|
|
echo ""
|
|
opsec_hdr "APPLYING SYSTEM HARDENING"
|
|
echo ""
|
|
apply_core_dumps
|
|
apply_swap
|
|
apply_ipv6
|
|
apply_timezone
|
|
apply_locale
|
|
apply_screen_lock
|
|
apply_webrtc
|
|
# Ghost-mode-only features — skip when called from mode_base (OPSEC_BASE_ONLY=1)
|
|
if [ "${OPSEC_BASE_ONLY:-0}" != "1" ]; then
|
|
apply_usb_block
|
|
apply_clipboard_clear
|
|
apply_traffic_jitter
|
|
else
|
|
opsec_info "Base-only mode — skipping USB block, clipboard clear, traffic jitter"
|
|
fi
|
|
echo ""
|
|
opsec_green "All hardening measures applied"
|
|
}
|
|
|
|
do_revert() {
|
|
echo ""
|
|
opsec_hdr "REVERTING SYSTEM HARDENING"
|
|
echo ""
|
|
revert_core_dumps
|
|
revert_swap
|
|
revert_ipv6
|
|
revert_timezone
|
|
revert_locale
|
|
revert_screen_lock
|
|
revert_webrtc
|
|
revert_usb_block
|
|
revert_clipboard_clear
|
|
revert_traffic_jitter
|
|
echo ""
|
|
opsec_green "All hardening measures reverted"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
apply) do_apply ;;
|
|
revert) do_revert ;;
|
|
*)
|
|
echo "Usage: $(basename "$0") apply|revert"
|
|
exit 1
|
|
;;
|
|
esac
|