#!/usr/bin/env bash ############################################################################### # NIST 800-53 Hardening Script v2 – Ubuntu 24.04 LTS (Proxmox VM Template) # # CHANGES FROM v1: # - iptables instead of UFW (Docker-compatible, CIS-clean) # - Fixes: ssh service name (not sshd), TMOUT readonly guard, # AIDE mkdir ordering, auditd.conf inline edits # - Expanded audit rules (chcon, setfacl, chacl, usermod, mount, DAC, etc.) # - CIS/Wazuh SCA alignment: kernel module blacklisting, cron/at allow # files, journald rotation, sudo logging, nullok removal, GRUB audit # flags, root umask, log permissions, opasswd, rsyslog file creation # mode, apport disabled, telnet/ftp/rsync removed, AppArmor in GRUB, # SSH AllowUsers, GSSAPIAuthentication, DisableForwarding, # HostbasedAuthentication, IgnoreRhosts, PermitUserEnvironment # # Maps to control families: AC, AU, CM, IA, SC, SI, MP # Run as root on a freshly installed Ubuntu 24.04 minimal server after # running the partition-layout.sh script, then convert to Proxmox template. # # Usage: chmod +x nist-800-53-harden-v2.sh && sudo ./nist-800-53-harden-v2.sh ############################################################################### set -euo pipefail export DEBIAN_FRONTEND=noninteractive LOG="/var/log/nist-hardening-$(date +%Y%m%d-%H%M%S).log" exec > >(tee -a "$LOG") 2>&1 banner() { printf '\n\e[1;36m>>> %s\e[0m\n' "$1"; } if [[ $EUID -ne 0 ]]; then echo "ERROR: Run this script as root." >&2 exit 1 fi ############################################################################### # CONFIGURATION — edit these before running ############################################################################### # The primary non-root admin user (used for SSH AllowUsers, cron.allow, etc.) ADMIN_USER="${ADMIN_USER:-chris}" ############################################################################### # 0. PRE-FLIGHT – Update & install required packages # CM-2 Baseline Configuration / SI-2 Flaw Remediation ############################################################################### banner "CM-2 / SI-2: System update and baseline packages" apt-get update -y apt-get upgrade -y apt-get dist-upgrade -y apt-get install -y \ auditd audispd-plugins \ aide aide-common \ libpam-pwquality \ iptables iptables-persistent netfilter-persistent \ chrony \ rsyslog \ acl \ apparmor apparmor-utils \ unattended-upgrades apt-listchanges \ cloud-init \ qemu-guest-agent \ needrestart # --------------------------------------------------------------------------- # CM-7 Remove unnecessary packages (CIS 2.4.x, 2.1.x) # --------------------------------------------------------------------------- banner "CM-7: Remove unnecessary packages" REMOVE_PKGS=( telnet inetutils-telnet ftp tnftp rsync ufw nftables apport snapd ) for pkg in "${REMOVE_PKGS[@]}"; do if dpkg -s "$pkg" &>/dev/null; then apt-get purge -y "$pkg" 2>/dev/null || true fi done apt-get autoremove -y # Disable apport if config file remains if [[ -f /etc/default/apport ]]; then sed -i 's/^enabled=.*/enabled=0/' /etc/default/apport fi # Disable motd-news network fetches systemctl disable --now motd-news.timer 2>/dev/null || true if [[ -f /etc/default/motd-news ]]; then sed -i 's/^ENABLED=.*/ENABLED=0/' /etc/default/motd-news fi # Disable unnecessary motd scripts for f in 10-help-text 50-motd-news 88-esm-announce 91-contract-ua-esm-status 91-release-upgrade 95-hwe-eol; do chmod -x /etc/update-motd.d/$f 2>/dev/null || true done ############################################################################### # 1. ACCESS CONTROL (AC) ############################################################################### # --------------------------------------------------------------------------- # AC-2 Account Management # --------------------------------------------------------------------------- banner "AC-2: Account management" passwd -l root for usr in games lp news uucp proxy gnats; do if id "$usr" &>/dev/null; then userdel "$usr" 2>/dev/null || true fi done # Default umask 027 sed -i 's/^UMASK.*/UMASK\t\t027/' /etc/login.defs # Root umask (CIS 5.5.6 / Wazuh 35703) for rcfile in /root/.bash_profile /root/.bashrc; do if [[ -f "$rcfile" ]]; then sed -i '/^umask/d' "$rcfile" else touch "$rcfile" fi echo "umask 0077" >> "$rcfile" done # --------------------------------------------------------------------------- # AC-7 Unsuccessful Logon Attempts – pam_faillock # --------------------------------------------------------------------------- banner "AC-7: Account lockout (pam_faillock)" cat > /etc/security/faillock.conf <<'EOF' # AC-7: Lock account after 5 failed attempts for 900 s deny = 5 fail_interval = 900 unlock_time = 900 even_deny_root EOF # Write pam_faillock into common-auth # Remove any existing faillock lines first to avoid duplicates sed -i '/pam_faillock/d' /etc/pam.d/common-auth sed -i '/pam_faillock/d' /etc/pam.d/common-account # Insert before pam_unix sed -i '/^auth.*pam_unix.so/i auth required pam_faillock.so preauth' \ /etc/pam.d/common-auth sed -i '/^auth.*pam_unix.so/a auth [default=die] pam_faillock.so authfail' \ /etc/pam.d/common-auth echo "account required pam_faillock.so" >> /etc/pam.d/common-account # --------------------------------------------------------------------------- # AC-8 System Use Notification – login banners # --------------------------------------------------------------------------- banner "AC-8: Login banners" BANNER_TEXT="======================================================================== WARNING: This system is for authorized use only. All activity is monitored and recorded. Unauthorized access is prohibited and subject to criminal and civil penalties. ========================================================================" echo "$BANNER_TEXT" > /etc/issue echo "$BANNER_TEXT" > /etc/issue.net cat > /etc/motd <<'EOF' **** NOTICE: Use of this system constitutes consent to monitoring. **** EOF # --------------------------------------------------------------------------- # AC-11 / AC-12 Session Lock / Termination # --------------------------------------------------------------------------- banner "AC-11 / AC-12: Session timeout" cat > /etc/profile.d/nist-timeout.sh <<'EOF' # AC-11/12: Idle session timeout – 15 minutes if [ -z "${TMOUT:-}" ]; then readonly TMOUT=900 export TMOUT fi EOF chmod 644 /etc/profile.d/nist-timeout.sh ############################################################################### # 2. AUDIT AND ACCOUNTABILITY (AU) ############################################################################### banner "AU-2 / AU-3 / AU-12: Audit configuration" # --------------------------------------------------------------------------- # AU-2/3/12 Audit Events – comprehensive rules for CIS/Wazuh # --------------------------------------------------------------------------- cat > /etc/audit/rules.d/nist-800-53.rules <<'AUDITRULES' ## ---- Remove any existing rules ---- -D ## ---- Buffer size ---- -b 8192 ## ---- Failure mode: 1 = printk, 2 = panic ---- -f 1 ## ---- Time changes (AU-8 / CIS 4.1.3.4 / Wazuh 35734) ---- -a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change -a always,exit -F arch=b32 -S adjtimex,settimeofday,clock_settime -k time-change -w /etc/localtime -p wa -k time-change ## ---- User/group changes (AC-2 / CIS 4.1.3.7 / Wazuh 35737) ---- -w /etc/passwd -p wa -k identity -w /etc/group -p wa -k identity -w /etc/shadow -p wa -k identity -w /etc/gshadow -p wa -k identity -w /etc/security/opasswd -p wa -k identity ## ---- Network configuration (CIS 4.1.3.5 / Wazuh 35735) ---- -a always,exit -F arch=b64 -S sethostname,setdomainname -k network-config -a always,exit -F arch=b32 -S sethostname,setdomainname -k network-config -w /etc/hosts -p wa -k network-config -w /etc/hostname -p wa -k network-config -w /etc/netplan/ -p wa -k network-config -w /etc/network/ -p wa -k network-config -w /etc/networks -p wa -k network-config ## ---- Login/logout events (CIS 4.1.3.10 / Wazuh 35741) ---- -w /var/log/lastlog -p wa -k logins -w /var/log/faillog -p wa -k logins -w /var/log/wtmp -p wa -k logins -w /var/log/btmp -p wa -k logins ## ---- Session initiation (CIS 4.1.3.11 / Wazuh 35740) ---- -w /var/run/utmp -p wa -k session ## ---- Sudo configuration changes (CIS 4.1.3.1 / Wazuh 35731) ---- -w /etc/sudoers -p wa -k scope -w /etc/sudoers.d -p wa -k scope ## ---- Sudo log file (CIS 4.1.3.3 / Wazuh 35733) ---- -w /var/log/sudo.log -p wa -k actions ## ---- Actions as another user (CIS 4.1.3.2 / Wazuh 35732) ---- -a always,exit -F arch=b64 -S execve -C euid!=uid -F auid!=unset -k user_emulation -a always,exit -F arch=b32 -S execve -C euid!=uid -F auid!=unset -k user_emulation ## ---- Privileged commands ---- -a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k privileged -a always,exit -F arch=b32 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k privileged ## ---- Unsuccessful file access (CIS 4.1.3.6 / Wazuh 35736) ---- -a always,exit -F arch=b64 -S open,openat,creat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access -a always,exit -F arch=b64 -S open,openat,creat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access -a always,exit -F arch=b32 -S open,openat,creat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access -a always,exit -F arch=b32 -S open,openat,creat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access ## ---- DAC permission changes (CIS 4.1.3.8 / Wazuh 35738) ---- -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b32 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=4294967295 -k perm_mod -a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod ## ---- Successful file system mounts (CIS 4.1.3.9 / Wazuh 35739) ---- -a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts -a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts ## ---- File deletion events (CIS 4.1.3.12 / Wazuh 35742) ---- -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -k delete -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -k delete ## ---- Mandatory access control changes (CIS 4.1.3.13 / Wazuh 35743) ---- -w /etc/apparmor/ -p wa -k MAC-policy -w /etc/apparmor.d/ -p wa -k MAC-policy ## ---- chcon command (CIS 4.1.3.14 / Wazuh 35744) ---- -a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=4294967295 -k perm_chng ## ---- setfacl command (CIS 4.1.3.15 / Wazuh 35745) ---- -a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=4294967295 -k perm_chng ## ---- chacl command (CIS 4.1.3.16 / Wazuh 35746) ---- -a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=4294967295 -k perm_chng ## ---- usermod command (CIS 4.1.3.17 / Wazuh 35747) ---- -a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=4294967295 -k usermod ## ---- Kernel module loading (CIS 4.1.3.18 / Wazuh 35748) ---- -a always,exit -F arch=b64 -S init_module,finit_module,delete_module,create_module,query_module -k kernel_modules -a always,exit -F arch=b32 -S init_module,finit_module,delete_module,create_module,query_module -k kernel_modules -w /sbin/insmod -p x -k kernel_modules -w /sbin/rmmod -p x -k kernel_modules -w /sbin/modprobe -p x -k kernel_modules -w /bin/kmod -p x -k kernel_modules ## ---- Make the audit configuration immutable (requires reboot to change) ---- -e 2 AUDITRULES # --------------------------------------------------------------------------- # AU-4 Audit Log Storage (CIS 4.1.2.x / Wazuh 35728-35730) # --------------------------------------------------------------------------- banner "AU-4: Audit log retention" # Edit auditd.conf inline AUDITD_CONF="/etc/audit/auditd.conf" cp "$AUDITD_CONF" "${AUDITD_CONF}.bak" sed -i 's/^max_log_file_action.*/max_log_file_action = keep_logs/' "$AUDITD_CONF" sed -i 's/^space_left_action.*/space_left_action = email/' "$AUDITD_CONF" sed -i 's/^admin_space_left_action.*/admin_space_left_action = halt/' "$AUDITD_CONF" # Add space thresholds if not present grep -q '^space_left ' "$AUDITD_CONF" || echo "space_left = 75" >> "$AUDITD_CONF" grep -q '^admin_space_left ' "$AUDITD_CONF" || echo "admin_space_left = 50" >> "$AUDITD_CONF" # Ensure max_log_file is set grep -q '^max_log_file ' "$AUDITD_CONF" || sed -i '1i max_log_file = 50' "$AUDITD_CONF" # Set permissions on audit config files (CIS 4.1.4.5 / Wazuh 35752) find /etc/audit/ -type f \( -name '*.conf' -o -name '*.rules' \) -exec chmod 640 {} \; chown -R root:root /etc/audit/ # Set permissions on audit tools (CIS 4.1.4.9 / Wazuh 35755) chmod 755 /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/augenrules 2>/dev/null || true systemctl enable auditd systemctl restart auditd || augenrules --load # --------------------------------------------------------------------------- # AU-8 Timestamps / NTP (chrony) # --------------------------------------------------------------------------- banner "AU-8: NTP time synchronization (chrony)" cat > /etc/chrony/chrony.conf <<'EOF' # NIST 800-53 AU-8: Authoritative time source pool ntp.ubuntu.com iburst maxsources 4 pool time.nist.gov iburst maxsources 2 keyfile /etc/chrony/chrony.keys driftfile /var/lib/chrony/chrony.drift logdir /var/log/chrony maxupdateskew 100.0 rtcsync makestep 1 3 EOF systemctl enable chrony systemctl restart chrony # Disable systemd-timesyncd (Wazuh 35589 wants one or the other) systemctl stop systemd-timesyncd 2>/dev/null || true systemctl disable systemd-timesyncd 2>/dev/null || true systemctl mask systemd-timesyncd 2>/dev/null || true ############################################################################### # 3. IDENTIFICATION AND AUTHENTICATION (IA) ############################################################################### # --------------------------------------------------------------------------- # IA-5 Password Quality (CIS 5.4.x / Wazuh 35681, 35683) # --------------------------------------------------------------------------- banner "IA-5: Password quality (pam_pwquality)" cat > /etc/security/pwquality.conf <<'EOF' # IA-5(1): Password complexity (CIS 5.4.1) minlen = 15 dcredit = -1 ucredit = -1 lcredit = -1 ocredit = -1 minclass = 4 difok = 8 maxrepeat = 3 maxsequence = 3 maxclassrepeat = 4 gecoscheck = 1 dictcheck = 1 enforcing = 1 EOF # Password aging (IA-5 / CIS 5.5.1 / Wazuh 35694, 35695) sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS\t60/' /etc/login.defs sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS\t1/' /etc/login.defs sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE\t14/' /etc/login.defs # Set INACTIVE period for new accounts (CIS 5.5.3 / Wazuh 35698) useradd -D -f 30 # Apply aging to existing admin user if id "$ADMIN_USER" &>/dev/null; then chage -M 60 -m 1 -W 14 -I 30 "$ADMIN_USER" fi # --------------------------------------------------------------------------- # IA-5 PAM configuration (CIS 5.3.x / Wazuh 35672, 35673, 35675, 35687-35690) # --------------------------------------------------------------------------- banner "IA-5: PAM hardening" # Remove nullok from all PAM files (Wazuh 35690) sed -i 's/ nullok//g' /etc/pam.d/common-auth sed -i 's/ nullok//g' /etc/pam.d/common-password # pam_pwhistory: remember last 24 passwords (Wazuh 35675, 35687, 35688, 35689) sed -i '/pam_pwhistory/d' /etc/pam.d/common-password sed -i '/^password.*pam_unix.so/i password required pam_pwhistory.so remember=24 use_authtok enforce_for_root retry=3' \ /etc/pam.d/common-password # Ensure pam_unix is configured properly (Wazuh 35672) # pam_unix should be present in common-auth, common-account, common-password, common-session # Verify — these should already exist from the default Ubuntu PAM config # Create opasswd file with correct permissions (Wazuh 35770) touch /etc/security/opasswd /etc/security/opasswd.old chmod 600 /etc/security/opasswd /etc/security/opasswd.old chown root:root /etc/security/opasswd /etc/security/opasswd.old ############################################################################### # 4. SYSTEM AND COMMUNICATIONS PROTECTION (SC) ############################################################################### # --------------------------------------------------------------------------- # SC-5 / SC-7 Firewall – iptables with persistent rules # --------------------------------------------------------------------------- banner "SC-5 / SC-7: iptables firewall" # Flush existing rules iptables -F iptables -X iptables -Z ip6tables -F ip6tables -X ip6tables -Z # ---- DEFAULT POLICIES: deny all ---- iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT DROP ip6tables -P INPUT DROP ip6tables -P FORWARD DROP ip6tables -P OUTPUT DROP # ---- LOOPBACK (CIS 3.5.3.3.1 / Wazuh 35623) ---- iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT iptables -A INPUT ! -i lo -s 127.0.0.0/8 -j DROP ip6tables -A INPUT -i lo -j ACCEPT ip6tables -A OUTPUT -o lo -j ACCEPT ip6tables -A INPUT ! -i lo -s ::1 -j DROP # ---- CONNECTION TRACKING ---- iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A INPUT -m conntrack --ctstate INVALID -j DROP # ---- ICMP CONTROL ---- # Allow essential ICMP types, drop the rest iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT # Rate-limit inbound pings iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT iptables -A INPUT -p icmp -j DROP iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT iptables -A OUTPUT -p icmp --icmp-type destination-unreachable -j ACCEPT iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT ip6tables -A INPUT -p icmpv6 -j DROP ip6tables -A OUTPUT -p icmpv6 -j ACCEPT # ---- INBOUND SERVICES ---- # SSH – rate limited: max 4 new connections per 60 seconds per source IP iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \ -m recent --set --name SSH --rsource iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \ -m recent --update --seconds 60 --hitcount 4 --name SSH --rsource -j DROP iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \ -m recent --set --name SSH --rsource ip6tables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \ -m recent --update --seconds 60 --hitcount 4 --name SSH --rsource -j DROP ip6tables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT # ---- OUTBOUND SERVICES ---- # DNS iptables -A OUTPUT -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT iptables -A OUTPUT -p tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT # HTTP/HTTPS (apt updates, downloads) iptables -A OUTPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT iptables -A OUTPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT # NTP iptables -A OUTPUT -p udp --dport 123 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p udp --dport 123 -m conntrack --ctstate NEW -j ACCEPT # Wazuh agent (remove if not using Wazuh) iptables -A OUTPUT -p tcp --dport 1514 -m conntrack --ctstate NEW -j ACCEPT iptables -A OUTPUT -p tcp --dport 1515 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p tcp --dport 1514 -m conntrack --ctstate NEW -j ACCEPT ip6tables -A OUTPUT -p tcp --dport 1515 -m conntrack --ctstate NEW -j ACCEPT # ---- LOGGING dropped packets ---- iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "IPT_INPUT_DROP: " --log-level 4 iptables -A FORWARD -m limit --limit 5/min -j LOG --log-prefix "IPT_FORWARD_DROP: " --log-level 4 iptables -A OUTPUT -m limit --limit 5/min -j LOG --log-prefix "IPT_OUTPUT_DROP: " --log-level 4 ip6tables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "IP6T_INPUT_DROP: " --log-level 4 ip6tables -A FORWARD -m limit --limit 5/min -j LOG --log-prefix "IP6T_FORWARD_DROP: " --log-level 4 ip6tables -A OUTPUT -m limit --limit 5/min -j LOG --log-prefix "IP6T_OUTPUT_DROP: " --log-level 4 # ---- DOCKER-USER chain (pre-create for Docker compatibility) ---- iptables -N DOCKER-USER 2>/dev/null || true iptables -A DOCKER-USER -j RETURN 2>/dev/null || true # ---- SAVE RULES ---- mkdir -p /etc/iptables iptables-save > /etc/iptables/rules.v4 ip6tables-save > /etc/iptables/rules.v6 # Enable persistence systemctl enable netfilter-persistent echo "iptables rules saved and persistent." echo "Current INPUT policy: $(iptables -L INPUT | head -1)" echo "Current OUTPUT policy: $(iptables -L OUTPUT | head -1)" # --------------------------------------------------------------------------- # SC-8 / SC-13 SSH hardening # --------------------------------------------------------------------------- banner "SC-8 / SC-13: SSH hardening" cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak cat > /etc/ssh/sshd_config.d/nist-hardening.conf <= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe && mv /etc/ssh/moduli.safe /etc/ssh/moduli # Test and restart (Ubuntu 24.04 uses 'ssh' not 'sshd') sshd -t && systemctl restart ssh # Set permissions on SSH host key files (CIS 5.2.1-5.2.2 / Wazuh 35641, 35642) find /etc/ssh -name 'ssh_host_*_key' -exec chmod 600 {} \; find /etc/ssh -name 'ssh_host_*_key.pub' -exec chmod 644 {} \; chown root:root /etc/ssh/ssh_host_* chmod 600 /etc/ssh/sshd_config # --------------------------------------------------------------------------- # SC-28 Filesystem mount hardening # --------------------------------------------------------------------------- banner "SC-28 / MP-2: Filesystem mount hardening" # Harden /dev/shm if grep -q '/dev/shm' /etc/fstab; then sed -i 's|^\(.*\s/dev/shm\s.*\)defaults\(.*\)|\1defaults,noexec,nosuid,nodev\2|' /etc/fstab else echo "tmpfs /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0" >> /etc/fstab fi ############################################################################### # 5. KERNEL HARDENING (SC / SI) ############################################################################### banner "SC-3 / SC-39 / SI-16: Kernel parameter hardening" cat > /etc/sysctl.d/99-nist-hardening.conf <<'SYSCTL' # ----- Network (SC-5 / SC-7) ----- net.ipv4.ip_forward = 0 net.ipv6.conf.all.forwarding = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv6.conf.all.accept_ra = 0 net.ipv6.conf.default.accept_ra = 0 net.ipv4.tcp_timestamps = 0 net.ipv4.tcp_max_syn_backlog = 4096 # ----- Kernel (SC-3 / SI-16) ----- kernel.kptr_restrict = 2 kernel.dmesg_restrict = 1 kernel.perf_event_paranoid = 3 kernel.randomize_va_space = 2 kernel.yama.ptrace_scope = 2 fs.suid_dumpable = 0 kernel.unprivileged_bpf_disabled = 1 net.core.bpf_jit_harden = 2 kernel.unprivileged_userns_clone = 0 # Additional CIS hardened parameters fs.protected_fifos = 2 fs.protected_hardlinks = 1 fs.protected_regular = 2 fs.protected_symlinks = 1 SYSCTL sysctl --system # Disable core dumps via limits cat > /etc/security/limits.d/nist-coredump.conf <<'EOF' * hard core 0 EOF # Systemd core dump disable mkdir -p /etc/systemd/coredump.conf.d cat > /etc/systemd/coredump.conf.d/disable.conf <<'EOF' [Coredump] Storage=none ProcessSizeMax=0 EOF ############################################################################### # 6. CONFIGURATION MANAGEMENT (CM) ############################################################################### # --------------------------------------------------------------------------- # CM-7 Disable unnecessary services and kernel modules # --------------------------------------------------------------------------- banner "CM-7: Disable unnecessary services and protocols" DISABLE_SVCS=( avahi-daemon cups bluetooth isc-dhcp-server rpcbind nfs-server vsftpd apache2 nginx ModemManager rsync ) for svc in "${DISABLE_SVCS[@]}"; do if systemctl list-unit-files "${svc}.service" &>/dev/null; then systemctl disable --now "$svc" 2>/dev/null || true systemctl mask "$svc" 2>/dev/null || true fi done # Disable uncommon filesystems (CIS 1.1.1.x / Wazuh 35509) cat > /etc/modprobe.d/cis-filesystems.conf <<'EOF' install cramfs /bin/false install freevxfs /bin/false install jffs2 /bin/false install hfs /bin/false install hfsplus /bin/false install squashfs /bin/false install udf /bin/false install afs /bin/false install ceph /bin/false install cifs /bin/false install exfat /bin/false install fat /bin/false install fscache /bin/false install fuse /bin/false install gfs2 /bin/false install nfs_common /bin/false install nfsd /bin/false install smbfs_common /bin/false blacklist cramfs blacklist freevxfs blacklist jffs2 blacklist hfs blacklist hfsplus blacklist squashfs blacklist udf blacklist afs blacklist ceph blacklist cifs blacklist exfat blacklist fat blacklist fscache blacklist fuse blacklist gfs2 blacklist nfs_common blacklist nfsd blacklist smbfs_common EOF # Disable uncommon network protocols (CIS 3.1.x / Wazuh 35604-35607) cat > /etc/modprobe.d/cis-network-protocols.conf <<'EOF' install dccp /bin/false install sctp /bin/false install rds /bin/false install tipc /bin/false blacklist dccp blacklist sctp blacklist rds blacklist tipc EOF # Disable USB storage (MP-7) cat > /etc/modprobe.d/cis-usb-storage.conf <<'EOF' install usb-storage /bin/false blacklist usb-storage EOF # --------------------------------------------------------------------------- # CM-6 File permissions and access control # --------------------------------------------------------------------------- banner "CM-6: Critical file permissions" chmod 600 /etc/shadow /etc/gshadow chmod 644 /etc/passwd /etc/group chmod 600 /boot/grub/grub.cfg 2>/dev/null || true chmod 700 /root chmod 600 /etc/crontab chmod 700 /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.monthly /etc/cron.weekly chmod 600 /etc/ssh/sshd_config # Restrict cron and at to authorized users (CIS 5.1.8-5.1.9 / Wazuh 35600, 35601) touch /etc/cron.allow /etc/at.allow chmod 640 /etc/cron.allow /etc/at.allow chown root:root /etc/cron.allow /etc/at.allow echo "root" > /etc/cron.allow echo "$ADMIN_USER" >> /etc/cron.allow echo "root" > /etc/at.allow echo "$ADMIN_USER" >> /etc/at.allow # Remove cron.deny and at.deny (CIS wants allow-files only) rm -f /etc/cron.deny /etc/at.deny # Fix log file permissions (CIS 4.2.3 / Wazuh 35722) find /var/log -type f -exec chmod g-wx,o-rwx {} + find /var/log -type d -exec chmod g-wx,o-rwx {} + # Restrict su to sudo group (AC-6 / Wazuh 35668) # Remove existing pam_wheel lines and add the correct one sed -i '/pam_wheel.so/d' /etc/pam.d/su echo "auth required pam_wheel.so use_uid group=sudo" >> /etc/pam.d/su # --------------------------------------------------------------------------- # Sudo logging (CIS 5.3.4 / Wazuh 35664) # --------------------------------------------------------------------------- banner "CM-6: Sudo logging" cat > /etc/sudoers.d/nist-logging <<'EOF' Defaults logfile="/var/log/sudo.log" Defaults log_input,log_output Defaults iolog_dir="/var/log/sudo-io" EOF chmod 440 /etc/sudoers.d/nist-logging mkdir -p /var/log/sudo-io ############################################################################### # 7. SYSTEM AND INFORMATION INTEGRITY (SI) ############################################################################### # --------------------------------------------------------------------------- # SI-2 Automatic security updates # --------------------------------------------------------------------------- banner "SI-2: Automatic security updates" cat > /etc/apt/apt.conf.d/50unattended-upgrades <<'EOF' Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; "${distro_id}ESMApps:${distro_codename}-apps-security"; "${distro_id}ESM:${distro_codename}-infra-security"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::Mail "root"; EOF cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; EOF # --------------------------------------------------------------------------- # SI-7 AIDE file integrity monitoring # --------------------------------------------------------------------------- banner "SI-7: AIDE file integrity database" # Configure AIDE to also cover audit tools (CIS 4.1.4.11 / Wazuh 35760) cat > /etc/aide/aide.conf.d/99_nist_custom <<'EOF' # NIST 800-53 SI-7: File integrity monitoring /boot Full /bin Full /sbin Full /lib Full /lib64 Full /usr/bin Full /usr/sbin Full /usr/lib Full /etc Full !/etc/mtab !/etc/adjtime !/var/log !/var/spool !/var/cache # CIS 4.1.4.11 - Audit tool integrity /sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 /sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 /sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 /sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 /sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 /sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 EOF aideinit -y -f 2>/dev/null || true cat > /etc/cron.daily/aide-check <<'AIDECRON' #!/bin/bash /usr/bin/aide --check --config=/etc/aide/aide.conf | /usr/bin/mail -s "AIDE report: $(hostname)" root 2>/dev/null AIDECRON chmod 700 /etc/cron.daily/aide-check ############################################################################### # 8. LOGGING (AU / Wazuh 35708, 35719, 35720) ############################################################################### banner "AU: Logging configuration" # Journald rotation (CIS 4.2.1.3 / Wazuh 35708) mkdir -p /etc/systemd/journald.conf.d cat > /etc/systemd/journald.conf.d/nist.conf <<'EOF' [Journal] Compress=yes Storage=persistent ForwardToSyslog=yes SystemMaxUse=500M SystemKeepFree=1G SystemMaxFileSize=50M MaxRetentionSec=1month EOF systemctl restart systemd-journald # Rsyslog file creation mode (CIS 4.2.2.4 / Wazuh 35719) cat > /etc/rsyslog.d/50-nist.conf <<'EOF' # CIS 4.2.2.4: Ensure rsyslog log file creation mode is configured $FileCreateMode 0640 $DirCreateMode 0750 $Umask 0027 EOF # Remote logging placeholder (CIS 4.2.2.6 / Wazuh 35720) # Uncomment and edit if shipping to a remote syslog server # If using Wazuh, this is handled by the Wazuh agent instead cat > /etc/rsyslog.d/99-remote.conf <<'EOF' # CIS 4.2.2.6: Forward logs to remote host # Uncomment the line below and set your syslog/SIEM server # *.* action(type="omfwd" target="your-siem.example.com" port="514" protocol="tcp") EOF systemctl restart rsyslog ############################################################################### # 9. AppArmor ENFORCEMENT (AC-3 / AC-6) ############################################################################### banner "AC-3 / AC-6: AppArmor enforcement" systemctl enable apparmor aa-enforce /etc/apparmor.d/* 2>/dev/null || true # Ensure AppArmor is in bootloader config (CIS 1.3.1.2 / Wazuh 35537) GRUB_DEFAULT="/etc/default/grub" if ! grep -q 'apparmor=1' "$GRUB_DEFAULT"; then sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 apparmor=1 security=apparmor"/' "$GRUB_DEFAULT" fi ############################################################################### # 10. GRUB / BOOT HARDENING (SI-7 / AC-3) ############################################################################### banner "SI-7 / AC-3: GRUB boot hardening" # Enable auditing for processes prior to auditd (CIS 4.1.1.3 / Wazuh 35725) # Enable audit_backlog_limit (CIS 4.1.1.4 / Wazuh 35726) if ! grep -q 'audit=1' "$GRUB_DEFAULT"; then sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 audit=1 audit_backlog_limit=8192"/' "$GRUB_DEFAULT" fi # Disable recovery mode sed -i 's/^#\?GRUB_DISABLE_RECOVERY=.*/GRUB_DISABLE_RECOVERY="true"/' "$GRUB_DEFAULT" update-grub 2>/dev/null || true # GRUB password (CIS 1.4.1 / Wazuh 35540) # ----------------------------------------------------------------------- # IMPORTANT: Generate your own hash: grub-mkpasswd-pbkdf2 # Then replace below and uncomment the block. # ----------------------------------------------------------------------- # cat > /etc/grub.d/40_custom <<'GRUBPW' # #!/bin/sh # exec tail -n +3 $0 # set superusers="grubadmin" # password_pbkdf2 grubadmin # GRUBPW # chmod 755 /etc/grub.d/40_custom # update-grub ############################################################################### # 11. PROXMOX TEMPLATE PREPARATION ############################################################################### banner "Proxmox template preparation" # Enable QEMU guest agent systemctl enable qemu-guest-agent # Cloud-init config cat > /etc/cloud/cloud.cfg.d/99_proxmox.cfg <<'EOF' datasource_list: [NoCloud, ConfigDrive] EOF # Machine-id regenerated on clone truncate -s 0 /etc/machine-id rm -f /var/lib/dbus/machine-id ln -s /etc/machine-id /var/lib/dbus/machine-id 2>/dev/null || true # Clear SSH host keys (regenerated on first boot) rm -f /etc/ssh/ssh_host_* # Firstboot service to regenerate keys and AIDE DB cat > /etc/systemd/system/firstboot-harden.service <<'SVC' [Unit] Description=First-boot tasks for hardened template ConditionPathExists=!/var/lib/firstboot-done After=network-online.target [Service] Type=oneshot ExecStart=/bin/bash -c '\ dpkg-reconfigure openssh-server && \ systemctl restart ssh && \ find /etc/ssh -name "ssh_host_*_key" -exec chmod 600 {} \; && \ find /etc/ssh -name "ssh_host_*_key.pub" -exec chmod 644 {} \; && \ chown root:root /etc/ssh/ssh_host_* && \ aideinit -y -f && \ touch /var/lib/firstboot-done' RemainAfterExit=yes [Install] WantedBy=multi-user.target SVC systemctl enable firstboot-harden.service # Rebuild initramfs with module blacklists update-initramfs -u # Clean up apt-get autoremove -y apt-get clean rm -rf /tmp/* /var/tmp/* find /var/log -type f -exec truncate -s 0 {} \; history -c ############################################################################### # SUMMARY ############################################################################### banner "NIST 800-53 Hardening v2 Complete" cat < ║ ╚═══════════════════════════════════════════════════════════════════╝ SUMMARY echo "" echo "Full log: $LOG" echo "Reboot recommended: shutdown -r now"