Cybersecurity-Projects/PROJECTS/beginner/linux-cis-hardening-auditor/src/checks/03_network.sh

467 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
# ©AngelaMos | 2026
# 03_network.sh
#
# CIS Section 3 checks: Network Configuration
#
# Implements controls 3.1.1-3.4.5 auditing kernel network parameters,
# host firewall policy, and uncommon protocol modules. Sysctl checks
# cover IP forwarding, ICMP send_redirects, source routing acceptance,
# ICMP redirect acceptance, martian packet logging, broadcast ICMP
# ignore, bogus ICMP response ignore, reverse path filtering, TCP
# SYN cookies, and IPv6 router advertisement acceptance. Firewall
# checks verify iptables installation, default deny policies on
# INPUT/FORWARD/OUTPUT chains (from live iptables or rules.v4 file),
# and open-port-to-rule coverage via ss. Validates wireless interface
# disabling through rfkill and checks modprobe.d for four uncommon
# protocol modules: DCCP, SCTP, RDS, and TIPC.
#
# Connects to:
# lib/registry.sh - record_result for each control
# lib/utils.sh - get_sysctl, package_is_installed, file_exists, run_cmd
check_3_1_1() {
local id="3.1.1"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.ip_forward") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.ip_forward"
elif [[ "$value" != "0" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.ip_forward = ${value} (expected 0)"
else
evidence="IP forwarding is disabled (net.ipv4.ip_forward = 0)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_1_2() {
local id="3.1.2"
local status="$STATUS_PASS"
local evidence=""
local val_all val_default
val_all=$(get_sysctl "net.ipv4.conf.all.send_redirects") || true
val_default=$(get_sysctl "net.ipv4.conf.default.send_redirects") || true
if [[ -z "$val_all" && -z "$val_default" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read send_redirects sysctl values"
elif [[ "$val_all" != "0" || "$val_default" != "0" ]]; then
status="$STATUS_FAIL"
evidence="send_redirects: all=${val_all:-unset} default=${val_default:-unset} (expected 0)"
else
evidence="Packet redirect sending disabled (all=0, default=0)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_1_3() {
local id="3.1.3"
local status="$STATUS_PASS"
local evidence=""
local val_all val_default
val_all=$(get_sysctl "net.ipv4.conf.all.accept_source_route") || true
val_default=$(get_sysctl "net.ipv4.conf.default.accept_source_route") || true
if [[ -z "$val_all" && -z "$val_default" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read accept_source_route sysctl values"
elif [[ "$val_all" != "0" || "$val_default" != "0" ]]; then
status="$STATUS_FAIL"
evidence="accept_source_route: all=${val_all:-unset} default=${val_default:-unset} (expected 0)"
else
evidence="Source routed packets not accepted (all=0, default=0)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_1_4() {
local id="3.1.4"
local status="$STATUS_PASS"
local evidence=""
local val_all val_default
val_all=$(get_sysctl "net.ipv4.conf.all.accept_redirects") || true
val_default=$(get_sysctl "net.ipv4.conf.default.accept_redirects") || true
if [[ -z "$val_all" && -z "$val_default" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read accept_redirects sysctl values"
elif [[ "$val_all" != "0" || "$val_default" != "0" ]]; then
status="$STATUS_FAIL"
evidence="accept_redirects: all=${val_all:-unset} default=${val_default:-unset} (expected 0)"
else
evidence="ICMP redirects not accepted (all=0, default=0)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_1() {
local id="3.2.1"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.conf.all.log_martians") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.conf.all.log_martians"
elif [[ "$value" != "1" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.conf.all.log_martians = ${value} (expected 1)"
else
evidence="Suspicious packets are logged (net.ipv4.conf.all.log_martians = 1)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_2() {
local id="3.2.2"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.icmp_echo_ignore_broadcasts") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.icmp_echo_ignore_broadcasts"
elif [[ "$value" != "1" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.icmp_echo_ignore_broadcasts = ${value} (expected 1)"
else
evidence="Broadcast ICMP requests ignored (net.ipv4.icmp_echo_ignore_broadcasts = 1)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_3() {
local id="3.2.3"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.icmp_ignore_bogus_error_responses") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.icmp_ignore_bogus_error_responses"
elif [[ "$value" != "1" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.icmp_ignore_bogus_error_responses = ${value} (expected 1)"
else
evidence="Bogus ICMP responses ignored (net.ipv4.icmp_ignore_bogus_error_responses = 1)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_4() {
local id="3.2.4"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.conf.all.rp_filter") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.conf.all.rp_filter"
elif [[ "$value" != "1" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.conf.all.rp_filter = ${value} (expected 1)"
else
evidence="Reverse Path Filtering enabled (net.ipv4.conf.all.rp_filter = 1)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_5() {
local id="3.2.5"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv4.tcp_syncookies") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv4.tcp_syncookies"
elif [[ "$value" != "1" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv4.tcp_syncookies = ${value} (expected 1)"
else
evidence="TCP SYN Cookies enabled (net.ipv4.tcp_syncookies = 1)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_2_6() {
local id="3.2.6"
local status="$STATUS_PASS"
local evidence=""
local value
value=$(get_sysctl "net.ipv6.conf.all.accept_ra") || true
if [[ -z "$value" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read net.ipv6.conf.all.accept_ra"
elif [[ "$value" != "0" ]]; then
status="$STATUS_FAIL"
evidence="net.ipv6.conf.all.accept_ra = ${value} (expected 0)"
else
evidence="IPv6 router advertisements not accepted (net.ipv6.conf.all.accept_ra = 0)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_3_1() {
local id="3.3.1"
local status="$STATUS_PASS"
local evidence=""
if package_is_installed "iptables"; then
evidence="iptables package is installed"
elif file_exists "/usr/sbin/iptables"; then
evidence="iptables binary found at /usr/sbin/iptables"
else
status="$STATUS_FAIL"
evidence="iptables is not installed"
fi
record_result "$id" "$status" "$evidence"
}
check_3_3_2() {
local id="3.3.2"
local status="$STATUS_PASS"
local evidence=""
local policy=""
local iptables_output
if iptables_output=$(run_cmd iptables -L INPUT -n 2>/dev/null); then
policy=$(echo "$iptables_output" | head -1 | grep -oP 'policy \K\w+') || true
fi
if [[ -z "$policy" ]]; then
local rules_file="${SYSROOT}/etc/iptables/rules.v4"
if [[ -f "$rules_file" ]]; then
policy=$(grep -E '^\s*:INPUT\s' "$rules_file" | awk '{print $2}') || true
fi
fi
if [[ -z "$policy" ]]; then
status="$STATUS_SKIP"
evidence="Unable to determine INPUT chain policy"
elif [[ "$policy" == "DROP" || "$policy" == "REJECT" ]]; then
evidence="INPUT chain default policy is ${policy}"
else
status="$STATUS_FAIL"
evidence="INPUT chain default policy is ${policy} (expected DROP or REJECT)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_3_3() {
local id="3.3.3"
local status="$STATUS_PASS"
local evidence=""
local policy=""
local iptables_output
if iptables_output=$(run_cmd iptables -L FORWARD -n 2>/dev/null); then
policy=$(echo "$iptables_output" | head -1 | grep -oP 'policy \K\w+') || true
fi
if [[ -z "$policy" ]]; then
local rules_file="${SYSROOT}/etc/iptables/rules.v4"
if [[ -f "$rules_file" ]]; then
policy=$(grep -E '^\s*:FORWARD\s' "$rules_file" | awk '{print $2}') || true
fi
fi
if [[ -z "$policy" ]]; then
status="$STATUS_SKIP"
evidence="Unable to determine FORWARD chain policy"
elif [[ "$policy" == "DROP" || "$policy" == "REJECT" ]]; then
evidence="FORWARD chain default policy is ${policy}"
else
status="$STATUS_FAIL"
evidence="FORWARD chain default policy is ${policy} (expected DROP or REJECT)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_3_4() {
local id="3.3.4"
local status="$STATUS_PASS"
local evidence=""
local policy=""
local iptables_output
if iptables_output=$(run_cmd iptables -L OUTPUT -n 2>/dev/null); then
policy=$(echo "$iptables_output" | head -1 | grep -oP 'policy \K\w+') || true
fi
if [[ -z "$policy" ]]; then
local rules_file="${SYSROOT}/etc/iptables/rules.v4"
if [[ -f "$rules_file" ]]; then
policy=$(grep -E '^\s*:OUTPUT\s' "$rules_file" | awk '{print $2}') || true
fi
fi
if [[ -z "$policy" ]]; then
status="$STATUS_SKIP"
evidence="Unable to determine OUTPUT chain policy"
elif [[ "$policy" == "DROP" || "$policy" == "REJECT" ]]; then
evidence="OUTPUT chain default policy is ${policy}"
else
status="$STATUS_FAIL"
evidence="OUTPUT chain default policy is ${policy} (expected DROP or REJECT)"
fi
record_result "$id" "$status" "$evidence"
}
check_3_3_5() {
local id="3.3.5"
local status="$STATUS_PASS"
local evidence=""
if [[ "$SYSROOT" != "/" ]]; then
status="$STATUS_SKIP"
evidence="Firewall rule coverage requires live system (test mode)"
record_result "$id" "$status" "$evidence"
return
fi
local open_ports
open_ports=$(run_cmd ss -tlnp 2>/dev/null | awk 'NR>1 {print $4}' | grep -oP '\d+$' | sort -un) || true
if [[ -z "$open_ports" ]]; then
evidence="No open TCP ports detected"
record_result "$id" "$status" "$evidence"
return
fi
local iptables_rules
iptables_rules=$(run_cmd iptables -L INPUT -n 2>/dev/null) || true
if [[ -z "$iptables_rules" ]]; then
status="$STATUS_SKIP"
evidence="Unable to read iptables INPUT rules"
record_result "$id" "$status" "$evidence"
return
fi
local uncovered=""
local port
for port in $open_ports; do
if ! echo "$iptables_rules" | grep -q "dpt:${port}\b"; then
if [[ -n "$uncovered" ]]; then
uncovered="${uncovered}, ${port}"
else
uncovered="${port}"
fi
fi
done
if [[ -n "$uncovered" ]]; then
status="$STATUS_FAIL"
evidence="Open ports without firewall rules: ${uncovered}"
else
evidence="All open ports have matching firewall rules"
fi
record_result "$id" "$status" "$evidence"
}
check_3_4_1() {
local id="3.4.1"
local status="$STATUS_PASS"
local evidence=""
if [[ "$SYSROOT" != "/" ]]; then
status="$STATUS_SKIP"
evidence="Wireless interface check requires live system (test mode)"
record_result "$id" "$status" "$evidence"
return
fi
local wireless_ifaces
wireless_ifaces=$(run_cmd ip link show 2>/dev/null | grep -oP '^\d+:\s+\Kwlan\S+') || true
if [[ -n "$wireless_ifaces" ]]; then
local all_blocked="true"
if run_cmd rfkill list wifi 2>/dev/null | grep -q "Soft blocked: yes"; then
evidence="Wireless interfaces found but blocked via rfkill"
else
all_blocked="false"
fi
if [[ "$all_blocked" == "false" ]]; then
status="$STATUS_FAIL"
evidence="Active wireless interfaces found: ${wireless_ifaces}"
fi
else
evidence="No wireless interfaces detected"
fi
record_result "$id" "$status" "$evidence"
}
_check_protocol_module_disabled() {
local id="$1"
local module="$2"
local status="$STATUS_PASS"
local evidence=""
local found_disabled="false"
for conf in "${SYSROOT}"/etc/modprobe.d/*.conf; do
[[ -f "$conf" ]] || continue
if grep -q "install ${module} /bin/true\|install ${module} /bin/false\|blacklist ${module}" "$conf"; then
found_disabled="true"
evidence="${module} disabled via ${conf}"
break
fi
done
if [[ "$found_disabled" == "false" ]]; then
if run_cmd lsmod | grep -q "^${module} "; then
status="$STATUS_FAIL"
evidence="${module} module is loaded"
else
status="$STATUS_FAIL"
evidence="No modprobe config found disabling ${module}"
fi
fi
record_result "$id" "$status" "$evidence"
}
check_3_4_2() { _check_protocol_module_disabled "3.4.2" "dccp"; }
check_3_4_3() { _check_protocol_module_disabled "3.4.3" "sctp"; }
check_3_4_4() { _check_protocol_module_disabled "3.4.4" "rds"; }
check_3_4_5() { _check_protocol_module_disabled "3.4.5" "tipc"; }