bash Hardening March 5, 2025

Firewall Audit Script

Automated script to audit iptables/nftables and UFW firewall rules, detect overly permissive configurations, and generate a security report.

firewalliptablesufwaudithardening

Description

This Bash script performs an automated security audit of Linux firewall configurations. It detects whether the system uses iptables, nftables, or UFW, then analyzes the active ruleset for common misconfigurations: overly permissive ACCEPT rules, weak default policies, and dangerous open ports. A color-coded summary report with a numeric security score is generated at the end to help administrators quickly assess their firewall posture.

Features

  • Auto-detection of the active firewall backend (iptables, nftables, UFW)
  • Rule enumeration with formatted, human-readable output
  • Permissive rule flagging — catches 0.0.0.0/0 ACCEPT and equivalent wide-open rules
  • Default policy check — verifies INPUT, FORWARD, and OUTPUT chain policies
  • Dangerous port scan — flags commonly exploited ports left open (Telnet, FTP, SMB, RDP, etc.)
  • Security score (0–100) with color-coded grade
  • No dependencies beyond standard coreutils and the firewall tools themselves
  • Works on Debian, Ubuntu, RHEL, Fedora, Arch, and derivatives

Usage

# Make executable
chmod +x firewall-audit.sh

# Run as root (required to read firewall rules)
sudo ./firewall-audit.sh

# Save report to file (strips ANSI colors automatically)
sudo ./firewall-audit.sh | tee report.txt

# Quick one-liner from curl (inspect before running!)
curl -sL https://example.com/firewall-audit.sh | sudo bash

Example Output

=== Firewall Audit Report ===
[*] Detected firewall: ufw (iptables backend)
[*] Enumerating rules...

[!] WARNING: Rule allows 0.0.0.0/0 → ACCEPT on INPUT chain
[!] WARNING: Port 23 (telnet) is open — consider closing
[✓] Default INPUT policy: DROP
[✓] Default FORWARD policy: DROP
[!] Default OUTPUT policy: ACCEPT (consider restricting)

── Summary ──────────────────────
  Findings:  3 warnings, 2 passed
  Score:     72/100  [FAIR]
══════════════════════════════════

Source Code

#!/usr/bin/env bash
# firewall-audit.sh — Automated Linux Firewall Auditor
# Author: FuryBee | License: MIT
set -euo pipefail

# ── Colors ──────────────────────────────────────────────
RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'
CYN='\033[0;36m'; BLD='\033[1m'; RST='\033[0m'

# ── Globals ─────────────────────────────────────────────
SCORE=100
WARNINGS=0
PASSES=0
DANGEROUS_PORTS=(21 23 25 69 111 135 139 445 512 513 514 1433 1434 3389 5900)
PORT_NAMES=([21]="ftp" [23]="telnet" [25]="smtp-relay" [69]="tftp"
            [111]="rpcbind" [135]="msrpc" [139]="netbios" [445]="smb"
            [512]="rexec" [513]="rlogin" [514]="rsh" [1433]="mssql"
            [1434]="mssql-udp" [3389]="rdp" [5900]="vnc")

warn()  { ((WARNINGS++)); echo -e "  ${YEL}[!] WARNING:${RST} $1"; }
pass()  { ((PASSES++));   echo -e "  ${GRN}[✓]${RST} $1"; }
info()  { echo -e "  ${CYN}[*]${RST} $1"; }
header(){ echo -e "\n${BLD}$1${RST}"; }

die() { echo -e "${RED}[✗] $1${RST}" >&2; exit 1; }

# ── Root check ──────────────────────────────────────────
[[ $EUID -eq 0 ]] || die "This script must be run as root."

# ── Detect firewall ─────────────────────────────────────
detect_firewall() {
    if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
        echo "ufw"
    elif command -v nft &>/dev/null && nft list ruleset 2>/dev/null | grep -q "table"; then
        echo "nftables"
    elif command -v iptables &>/dev/null && iptables -L -n 2>/dev/null | grep -q "Chain"; then
        echo "iptables"
    else
        echo "none"
    fi
}

# ── Check default policies (iptables) ──────────────────
check_iptables_policies() {
    header "── Default Chain Policies ──"
    for chain in INPUT FORWARD OUTPUT; do
        policy=$(iptables -L "$chain" -n 2>/dev/null | head -1 | awk -F'[()]' '{print $2}' | xargs)
        if [[ "$policy" == "DROP" || "$policy" == "REJECT" ]]; then
            pass "Default ${chain} policy: ${policy}"
        elif [[ "$policy" == "ACCEPT" ]]; then
            if [[ "$chain" == "OUTPUT" ]]; then
                warn "Default ${chain} policy: ACCEPT (consider restricting egress)"
                ((SCORE-=5))
            else
                warn "Default ${chain} policy: ACCEPT — should be DROP or REJECT"
                ((SCORE-=15))
            fi
        else
            warn "Could not determine ${chain} policy (got: '${policy}')"
            ((SCORE-=5))
        fi
    done
}

# ── Flag permissive rules ──────────────────────────────
check_permissive_rules() {
    header "── Overly Permissive Rules ──"
    local found=0
    while IFS= read -r line; do
        if echo "$line" | grep -qiE "0\.0\.0\.0/0.*ACCEPT|ACCEPT.*0\.0\.0\.0/0"; then
            chain=$(echo "$line" | awk '{print $1}')
            proto=$(echo "$line" | awk '{print $2}')
            warn "Wide-open ACCEPT in ${chain} chain (proto: ${proto}) → ${line}"
            ((SCORE-=10))
            found=1
        fi
    done < <(iptables -L -n -v 2>/dev/null | grep -i "ACCEPT" || true)
    [[ $found -eq 0 ]] && pass "No overly permissive 0.0.0.0/0 ACCEPT rules found"
}

# ── Check dangerous open ports ─────────────────────────
check_dangerous_ports() {
    header "── Dangerous Open Ports ──"
    local found=0
    local open_ports
    open_ports=$(ss -tlnH 2>/dev/null | awk '{print $4}' | grep -oE '[0-9]+$' | sort -un)

    for port in "${DANGEROUS_PORTS[@]}"; do
        if echo "$open_ports" | grep -qw "$port"; then
            local name="${PORT_NAMES[$port]:-unknown}"
            warn "Port ${port} (${name}) is listening — consider closing it"
            ((SCORE-=8))
            found=1
        fi
    done
    [[ $found -eq 0 ]] && pass "No commonly dangerous ports are listening"
}

# ── UFW-specific audit ─────────────────────────────────
audit_ufw() {
    header "── UFW Rule Listing ──"
    ufw status verbose | while IFS= read -r line; do
        echo "    $line"
    done
    echo ""
    # UFW uses iptables under the hood
    check_iptables_policies
    check_permissive_rules
    check_dangerous_ports
}

# ── nftables-specific audit ────────────────────────────
audit_nftables() {
    header "── nftables Ruleset ──"
    nft list ruleset 2>/dev/null | head -60 | while IFS= read -r line; do
        echo "    $line"
    done
    echo ""
    # Check for accept-all patterns in nft
    header "── Overly Permissive Rules (nft) ──"
    if nft list ruleset 2>/dev/null | grep -qiE "ip saddr 0\.0\.0\.0/0.*accept"; then
        warn "Found wide-open accept rule in nftables ruleset"
        ((SCORE-=10))
    else
        pass "No obvious permissive catch-all in nftables"
    fi
    check_dangerous_ports
}

# ── iptables-specific audit ────────────────────────────
audit_iptables() {
    header "── iptables Rule Listing ──"
    iptables -L -n -v --line-numbers 2>/dev/null | head -80 | while IFS= read -r line; do
        echo "    $line"
    done
    echo ""
    check_iptables_policies
    check_permissive_rules
    check_dangerous_ports
}

# ── Score display ──────────────────────────────────────
print_score() {
    [[ $SCORE -lt 0 ]] && SCORE=0
    local color grade
    if   [[ $SCORE -ge 90 ]]; then color="$GRN"; grade="EXCELLENT"
    elif [[ $SCORE -ge 70 ]]; then color="$YEL"; grade="FAIR"
    elif [[ $SCORE -ge 50 ]]; then color="$YEL"; grade="POOR"
    else                            color="$RED"; grade="CRITICAL"
    fi
    echo ""
    echo -e "${BLD}── Summary ──────────────────────────────${RST}"
    echo -e "  Findings:  ${YEL}${WARNINGS} warning(s)${RST}, ${GRN}${PASSES} passed${RST}"
    echo -e "  Score:     ${color}${SCORE}/100  [${grade}]${RST}"
    echo -e "${BLD}═════════════════════════════════════════${RST}"
}

# ── Main ────────────────────────────────────────────────
main() {
    echo -e "\n${BLD}=== Firewall Audit Report ===${RST}"
    FW=$(detect_firewall)

    if [[ "$FW" == "none" ]]; then
        die "No active firewall detected. Your system may be unprotected!"
    fi
    info "Detected firewall: ${BLD}${FW}${RST}"
    info "Enumerating rules..."

    case "$FW" in
        ufw)      audit_ufw      ;;
        nftables) audit_nftables  ;;
        iptables) audit_iptables  ;;
    esac

    print_score
}

main "$@"

Notes

  • Root privileges are required because reading firewall rules needs elevated access. The script exits immediately if not run as root.
  • The security score starts at 100 and deductions are applied for each finding. The scale is: 90+ Excellent, 70–89 Fair, 50–69 Poor, < 50 Critical.
  • The dangerous ports list is opinionated — you may need ports like 25 (SMTP) open if you run a mail server. Adjust the DANGEROUS_PORTS array to match your environment.
  • On systems running UFW, the script inspects the underlying iptables rules as well, since UFW is a frontend for iptables/nftables.
  • For nftables, the script prints the first 60 lines of the ruleset to avoid flooding the terminal on complex configurations. Pipe to a file for the full output.
  • The script is read-only — it never modifies any firewall rules. It is safe to run on production systems.
  • Consider running this script as a cron job or integrating it into your CI/CD pipeline for continuous compliance monitoring.