Week 14: OS Hardening, Benchmarks, and Configuration Management¶
Overview¶
OS hardening is the systematic process of reducing a system's attack surface by eliminating unnecessary features, tightening configurations, and applying defensive controls. The fundamental philosophy is simple: every unnecessary service, open port, enabled feature, and default credential is a potential vulnerability. A freshly installed operating system is optimized for compatibility and ease of use, not security. Hardening reverses these defaults in a principled, documented, and repeatable way.
This chapter examines the CIS Benchmark standard — the most widely used OS hardening framework — and provides specific, actionable hardening controls across five categories: authentication, network, filesystem, logging, and patching.
CIS Benchmarks: The Gold Standard¶
The Center for Internet Security (CIS) publishes freely available hardening benchmarks for virtually every major OS: Ubuntu, RHEL/CentOS, Windows Server, macOS, Debian, Fedora, and more. These benchmarks represent community consensus among security professionals, vendors, and government agencies.
Benchmark Structure¶
Each CIS Benchmark is organized into two levels:
| Level | Description | Use Case |
|---|---|---|
| Level 1 | "Pragmatic" baseline — controls with minimal operational impact, suitable for all environments | General production systems |
| Level 2 | "Defense in depth" — more restrictive controls that may impact functionality | High-security environments, regulated industries |
Each control is labeled Scored (compliance tools can check it automatically) or Not Scored (requires manual assessment).
CIS-CAT Assessment Tool¶
# CIS-CAT Lite (free) — assess a system against a benchmark
./CIS-CAT.sh -a -b benchmarks/CIS_Ubuntu_Linux_22.04_LTS_Benchmark_v1.0.0-xccdf.xml \
-o /tmp/cis-report
# OpenSCAP — open-source alternative
oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_level2_server \
--results /tmp/oscap-results.xml \
--report /tmp/oscap-report.html \
/usr/share/xml/scap/ssg/content/ssg-ubuntu2204-xccdf.xml
Category 1: Authentication Hardening¶

Password Policy¶
Configure /etc/security/pwquality.conf:
# /etc/security/pwquality.conf — CIS-compliant password policy
minlen = 14 # minimum 14 characters
dcredit = -1 # at least 1 digit required
ucredit = -1 # at least 1 uppercase required
lcredit = -1 # at least 1 lowercase required
ocredit = -1 # at least 1 special character required
maxrepeat = 3 # no more than 3 consecutive identical chars
maxclassrepeat = 4 # no more than 4 consecutive chars from same class
difok = 4 # minimum 4 chars different from last password
Configure password aging in /etc/login.defs:
PASS_MAX_DAYS 90 # passwords expire after 90 days
PASS_MIN_DAYS 7 # minimum 7 days before password can be changed
PASS_WARN_AGE 14 # warn 14 days before expiry
Account Lockout¶
Configure pam_faillock in /etc/security/faillock.conf:
deny = 5 # lock after 5 consecutive failures
fail_interval = 900 # count failures within 15-minute window
unlock_time = 900 # auto-unlock after 15 minutes (0 = admin manual unlock)
even_deny_root = yes # apply lockout even to root (controversial but CIS L2)
audit # log failed attempts to audit log
And in /etc/pam.d/common-auth (Debian/Ubuntu):
auth required pam_faillock.so preauth silent
auth [default=die] pam_faillock.so authfail
auth sufficient pam_unix.so
auth [default=die] pam_faillock.so authsucc
Disabling Unused Accounts¶
# Lock accounts that should not be able to log in
passwd -l mail # lock the account
usermod -s /sbin/nologin daemon # set shell to nologin
# Find all accounts with UID >= 1000 (normal users) and verify each is needed
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3, $7}' /etc/passwd
# Find accounts with empty passwords (security risk)
awk -F: '$2 == "" {print $1}' /etc/shadow
Sudoers Hardening¶
# /etc/sudoers — CIS-compliant configuration
# Never use NOPASSWD in production:
# BAD: deployer ALL=(ALL) NOPASSWD: ALL
# GOOD: require password, restrict to specific commands
deployer ALL=(root) /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
# Require TTY (prevents sudo from scripts/cron abuse)
Defaults requiretty
# Log all sudo commands
Defaults logfile=/var/log/sudo.log
Defaults log_input, log_output
# Limit sudo session timeout
Defaults timestamp_timeout=5 # re-require password after 5 minutes
Category 2: Network Hardening¶
Disabling Unnecessary Services¶
# Identify all listening services
ss -tlnp # TCP
ss -ulnp # UDP
# or
netstat -tlnp
# Disable and mask unneeded services (masking prevents accidental re-enable)
systemctl disable --now avahi-daemon cups bluetooth rpcbind
systemctl mask avahi-daemon cups bluetooth rpcbind
# Audit all enabled services
systemctl list-unit-files --state=enabled
Host-Based Firewall with nftables¶
# /etc/nftables.conf — CIS-compliant baseline
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state invalid drop comment "drop invalid packets"
ct state {established, related} accept comment "allow tracked connections"
iif lo accept comment "allow loopback"
ip protocol icmp icmp type { echo-reply, destination-unreachable,
echo-request, time-exceeded } accept comment "allow necessary ICMP"
tcp dport 22 ct state new \
limit rate 15/minute accept comment "SSH with rate limiting"
# Log and drop everything else
log prefix "nftables DROP: " counter drop
}
chain output {
type filter hook output priority 0; policy accept;
# Restrict outbound if needed:
# tcp dport != { 80, 443, 53, 22 } reject
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}
Kernel Network Hardening (sysctl)¶
cat >> /etc/sysctl.d/99-network-hardening.conf << 'EOF'
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
# Disable IP forwarding (unless this is a router)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# Disable ICMP redirects (prevent routing manipulation)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
# Enable reverse path filtering (prevent IP spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Disable broadcast ICMP (smurf attack prevention)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Log martian packets (packets with impossible source addresses)
net.ipv4.conf.all.log_martians = 1
EOF
sysctl --system
Category 3: Filesystem Hardening¶
Mount Options¶
# /etc/fstab — add noexec, nosuid, nodev to appropriate partitions
# /tmp — no executable files, no setuid programs, no device files
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0
# /var — no executable or setuid (data partition)
/dev/sda3 /var ext4 defaults,nosuid,nodev 0 2
# /home — users should not be able to run setuid or device files
/dev/sda4 /home ext4 defaults,noexec,nosuid,nodev 0 2
# /proc — hardening options
proc /proc proc defaults,hidepid=2,gid=proc 0 0
The hidepid=2 option on /proc prevents non-root users from seeing other users' process information.
AIDE File Integrity Monitoring¶
# /etc/aide/aide.conf — configure what to monitor
/etc CONTENT_EX # monitor /etc with extended attributes
/bin CONTENT_EX # system binaries
/sbin CONTENT_EX
/lib CONTENT_EX
/usr/bin CONTENT_EX
/boot CONTENT_EX # bootloader and kernel
# Exclusions (files that legitimately change):
!/var/log/.*
!/etc/mtab
!/etc/adjtime
# Initialize baseline after clean install:
aide --init
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Daily check via cron:
# 0 3 * * * root /usr/bin/aide --check | /usr/bin/mail -s "AIDE $(hostname)" soc@corp.com
SUID/SGID Audit¶
# Find all SUID binaries (run as file owner regardless of caller UID)
find / -perm -4000 -type f 2>/dev/null | sort
# Find all SGID binaries
find / -perm -2000 -type f 2>/dev/null | sort
# Remove SUID from binaries that don't need it (example: mount)
chmod u-s /usr/bin/mount # if always run by root in your environment
# Review and document all SUID/SGID binaries — any unrecognized entry is suspicious
Category 4: Logging and Monitoring¶
auditd CIS Compliance Rules¶
# /etc/audit/rules.d/99-cis.rules
# Delete all existing rules
-D
# Set buffer size
-b 8192
# Actions on audit failure (2=kernel panic — use 1 for log, 2 for paranoid)
-f 1
# Time changes
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change
-a always,exit -F arch=b64 -S clock_settime -k time-change
-w /etc/localtime -p wa -k time-change
# User/group modifications
-w /etc/group -p wa -k identity
-w /etc/passwd -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/security/opasswd -p wa -k identity
# Network configuration changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale
-w /etc/hosts -p wa -k system-locale
# Login/logout events
-w /var/log/wtmp -p wa -k logins
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock -p wa -k logins
# Privileged command execution (all setuid/setgid programs)
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k priv_cmd
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k priv_cmd
# Kernel module loading
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules
# Make rules immutable (requires reboot to change — highest security)
-e 2
Remote Syslog¶
# rsyslog — forward to remote SIEM
# /etc/rsyslog.d/50-remote.conf
*.* action(type="omfwd" target="siem.corp.com" port="514" protocol="tcp"
action.resumeRetryCount="100"
queue.type="linkedList" queue.size="20000"
queue.filename="fwdRule1" queue.saveonshutdown="on")
Category 5: Windows Server Hardening¶
PowerShell hardening commands for Windows Server:
# Disable SMBv1 (EternalBlue/WannaCry vector)
Set-SmbServerConfiguration -EnableSMB1Protocol $false -Force
Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol
# Enable Windows Defender Application Control (WDAC)
# Apply Microsoft Security Baseline via LGPO.exe
.\LGPO.exe /g "Windows Server 2022 Security Baseline\GPOs\"
# PowerShell Constrained Language Mode (prevents most PS-based attacks)
# Set via AppLocker or WDAC policy — CLM activates automatically
# Disable unnecessary features
Disable-WindowsOptionalFeature -Online -FeatureName Telnet-Client
Disable-WindowsOptionalFeature -Online -FeatureName TFTP
Remove-WindowsFeature FS-SMB1
# Audit LAPS (Local Administrator Password Solution) deployment
Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwd |
Where-Object {$_."ms-Mcs-AdmPwd" -ne $null} | Select Name
# Windows Firewall — default deny inbound
netsh advfirewall set allprofiles firewallpolicy blockinbound,allowoutbound
# Disable autorun/autoplay
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" `
-Name "NoDriveTypeAutoRun" -Value 255
Automated Hardening with Ansible¶
# ansible/hardening.yml — dev-sec.io os-hardening role
---
- hosts: all
become: yes
vars:
os_auth_pw_max_age: 90
os_auth_pw_min_age: 7
os_auth_retries: 5
os_security_kernel_enable_sysrq: false
os_security_users_allow: ['vagrant']
network_ipv6_enable: false
roles:
- role: dev-sec.os-hardening
- role: dev-sec.ssh-hardening
Run the assessment:
# OpenSCAP — generate HTML compliance report
oscap xccdf eval \
--profile cis_level1_server \
--results results.xml \
--report report.html \
ssg-rhel9-xccdf.xml
# View score summary
oscap info results.xml | grep -A5 "Score:"
Configuration Drift Detection¶
Configuration drift occurs when systems diverge from their hardened baseline over time due to manual changes, software updates, or ad-hoc fixes. Detection strategies:
- AIDE — detects filesystem changes daily
- Ansible —
--checkmode shows what would change (shows drift) - Puppet/Chef — continuously enforce desired state, report deviations
- AWS Config / Azure Policy — cloud-native compliance monitoring
- Tripwire Enterprise — commercial SIEM-integrated integrity monitoring
# Use Ansible in check mode to detect drift
ansible-playbook hardening.yml --check --diff | grep "changed"
Key Terms¶
| Term | Definition |
|---|---|
| CIS Benchmark | Center for Internet Security hardening guide; community-consensus security baseline |
| CIS Level 1 | Pragmatic controls with minimal operational impact |
| CIS Level 2 | Defense-in-depth controls; higher security, possible functionality impact |
| CIS-CAT | CIS Configuration Assessment Tool; automated benchmark scanning |
| OpenSCAP | Open-source SCAP implementation for automated compliance assessment |
| pam_faillock | PAM module enforcing account lockout after repeated failed logins |
| pam_pwquality | PAM module enforcing password complexity requirements |
| noexec | Mount option preventing execution of binaries from a partition |
| nosuid | Mount option preventing setuid bit from taking effect on a partition |
| AIDE | Advanced Intrusion Detection Environment; file integrity baseline tool |
| Configuration drift | Uncontrolled divergence of a system from its security baseline |
| hidepid=2 | /proc mount option hiding other users' process directories |
| auditd | Linux kernel audit daemon; central to CIS logging requirements |
| sudoers | Configuration file controlling sudo privilege delegation |
| NOPASSWD | Dangerous sudo option allowing privilege escalation without authentication |
| Ansible | Infrastructure-as-code tool; enables idempotent hardening automation |
| SMBv1 | Legacy Microsoft file sharing protocol; EternalBlue/WannaCry vulnerability |
| LAPS | Local Administrator Password Solution; unique passwords per Windows machine |
| sysctl | Interface for reading/writing Linux kernel parameters at runtime |
Review Questions¶
- Conceptual: Explain the difference between CIS Benchmark Level 1 and Level 2. In what business context might you apply Level 2 despite potential functionality tradeoffs?
- Hands-on Lab: Run
oscap xccdf evalagainst a RHEL/CentOS or Ubuntu system using the CIS profile. Document the five most critical failing controls and the remediation commands for each. - Analytical: A system administrator sets
Defaults NOPASSWD: ALLin sudoers for a service account. Describe the specific attack scenarios this enables and how to remediate it. - Conceptual: Explain why adding
noexecto the/tmpmount prevents certain attack scenarios. What class of attacks does it mitigate, and what attack technique can bypass it? - Hands-on Lab: Use
findto enumerate all SUID binaries on a Linux system. Research each one and document: (a) its purpose, (b) whether its SUID bit is required, and (c) whether it has any known CVEs. - Conceptual: What is configuration drift, and why is it a security problem even if a system was properly hardened at deployment? What tools can detect and remediate drift?
- Analytical: Design a complete auditd ruleset for a PCI-DSS compliant Linux system. Which events must be logged under PCI-DSS requirement 10?
- Hands-on Lab: Write an Ansible playbook that enforces three CIS Level 1 controls: (a)
/tmpmounted with noexec/nosuid/nodev, (b)PermitRootLogin noin sshd_config, and (c) auditd enabled and running. - Conceptual: Explain why Windows SMBv1 is still in the hardening checklists in 2024. Why hasn't it been eliminated from the threat surface? What organizational challenges persist?
- Analytical: Compare manual hardening, Ansible automation, and commercial tools (CIS-CAT Pro, Tenable.io). For each approach, identify scenarios where it is the appropriate choice.
Further Reading¶
- CIS Controls v8 — cisecurity.org/controls — framework connecting individual hardening controls to larger security program objectives
- DISA STIGs (Security Technical Implementation Guides) — public.cyber.mil/stigs — US DoD hardening requirements, often more stringent than CIS
- "Ansible for DevOps" — Jeff Geerling (LeanPub) — practical automation for hardening at scale
- Microsoft Security Compliance Toolkit — microsoft.com/en-us/download — Windows security baselines and LGPO tools
- NIST SP 800-123: "Guide to General Server Security" — nvlpubs.nist.gov — foundational server hardening methodology