Chapter 22: Firewalls, IDS, IPS, and WAFs — Layers of Network Defense

"A castle with only one wall is a monument to optimism." — Bruce Schneier (paraphrased)

An HTTP request traveling from the internet to your application server passes through multiple inspection points: a firewall, an IPS, a load balancer, a WAF, and then the application itself. That is not overkill — each layer catches different things. The firewall blocks ports and protocols. The IPS catches known exploit patterns in network traffic. The WAF inspects HTTP specifically — request bodies, headers, cookies. The application validates business logic. Remove any one layer, and a specific class of attack sails through undetected.

This is defense in depth. This chapter shows you what each layer actually does under the hood, how to configure them, and — crucially — what they miss.


Packet Filtering Firewalls

The oldest and simplest form of firewall, dating back to the late 1980s. Packet filters examine individual packets against a rule set and make allow/deny decisions based on:

  • Source and destination IP address
  • Source and destination port
  • Protocol (TCP, UDP, ICMP)
  • Interface direction (inbound/outbound)
  • TCP flags (SYN, ACK, FIN, RST)

They operate at Layer 3 (Network) and Layer 4 (Transport) of the OSI model. They see individual packets, not connections or application data.

flowchart TD
    A[Incoming Packet] --> B{Extract headers:<br/>Src IP, Dst IP,<br/>Src Port, Dst Port,<br/>Protocol, Flags}
    B --> C{Match Rule 1?<br/>ALLOW TCP dst 443}
    C -->|Match| D[ALLOW — forward packet]
    C -->|No match| E{Match Rule 2?<br/>ALLOW TCP dst 80}
    E -->|Match| D
    E -->|No match| F{Match Rule 3?<br/>DENY TCP dst 22<br/>from external}
    F -->|Match| G[DENY — drop packet]
    F -->|No match| H{Match Rule N?}
    H -->|No match| I{Default Policy}
    I -->|DROP| G
    I -->|ACCEPT| D

    style D fill:#2ecc71,stroke:#27ae60,color:#fff
    style G fill:#ff6b6b,stroke:#c0392b,color:#fff

Fundamental limitation: Packet filters see individual packets, not connections. They cannot determine whether a packet is part of a legitimate established session or a spoofed response. An attacker can craft packets with the ACK flag set to bypass rules that only block SYN packets — the firewall sees an ACK and assumes it is part of an existing connection.


Stateful Inspection Firewalls

Stateful firewalls maintain a connection tracking table (also called a state table) that records active sessions. They understand the lifecycle of a TCP connection — the three-way handshake, the established state, and proper teardown.

stateDiagram-v2
    [*] --> NEW: SYN packet arrives<br/>Check rules for NEW connections
    NEW --> SYN_SENT: Rule allows → add to state table
    NEW --> DROPPED: Rule denies → drop

    SYN_SENT --> ESTABLISHED: SYN-ACK + ACK seen<br/>Three-way handshake complete
    ESTABLISHED --> ESTABLISHED: Data packets<br/>Auto-allowed (in state table)
    ESTABLISHED --> TIME_WAIT: FIN/RST seen<br/>Connection closing
    TIME_WAIT --> [*]: Timeout → remove from table

    state "Connection Tracking Table" as CTT {
        [*] --> Entry1: Src: 203.0.113.50:49152<br/>Dst: 10.0.1.100:443<br/>State: ESTABLISHED<br/>Timeout: 3600s
        [*] --> Entry2: Src: 198.51.100.7:51234<br/>Dst: 10.0.1.100:80<br/>State: SYN_RECV<br/>Timeout: 120s
        [*] --> Entry3: Src: 10.0.2.50:38921<br/>Dst: 93.184.216.34:443<br/>State: ESTABLISHED<br/>Timeout: 3600s
    }

The key advantage: return traffic for an established connection is automatically allowed without needing an explicit rule. This means you only need to write rules for new connections — simplifying rule sets and preventing spoofed response packets.

# Stateful firewall rules (conceptual)
# Rule 1: Allow return traffic for established connections
ALLOW state=ESTABLISHED,RELATED

# Rule 2: Allow new HTTPS connections from anywhere
ALLOW state=NEW proto=TCP dst_port=443

# Rule 3: Allow new SSH from management network only
ALLOW state=NEW proto=TCP src=10.0.0.0/24 dst_port=22

# Default: Drop everything else
DROP all

State table exhaustion: The state table has a limited size. A SYN flood attack sends millions of SYN packets without completing the handshake, filling the state table with half-open connections. When the table is full, no new connections can be tracked — even legitimate ones. Defense: SYN cookies, connection rate limiting, and large state tables.


iptables: The Linux Firewall — Chain Traversal

On Linux, the firewall is built into the kernel via Netfilter. Here is exactly how a packet traverses the system.

iptables Chain Traversal

flowchart TD
    A[Packet arrives<br/>on network interface] --> B[PREROUTING chain<br/>nat table: DNAT]
    B --> C{Destination<br/>is this host?}
    C -->|Yes| D[INPUT chain<br/>filter table]
    C -->|No| E[FORWARD chain<br/>filter table]
    D --> F{Rules match?}
    F -->|ACCEPT| G[Local Process]
    F -->|DROP| H[Packet discarded]
    E --> I{Rules match?}
    I -->|ACCEPT| J[POSTROUTING chain<br/>nat table: SNAT/MASQ]
    I -->|DROP| H
    J --> K[Packet forwarded<br/>out another interface]

    G --> L[Local process<br/>generates response]
    L --> M[OUTPUT chain<br/>filter table]
    M --> N{Rules match?}
    N -->|ACCEPT| O[POSTROUTING chain<br/>nat table: SNAT]
    N -->|DROP| H
    O --> P[Packet sent out<br/>network interface]

    style H fill:#ff6b6b,stroke:#c0392b,color:#fff
    style G fill:#2ecc71,stroke:#27ae60,color:#fff
    style K fill:#2ecc71,stroke:#27ae60,color:#fff

Complete iptables Server Configuration

#!/bin/bash
# firewall.sh — Production server firewall rules
# Apply with: sudo bash firewall.sh

# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F

# Set default policies — DROP everything, then allowlist
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow loopback interface (critical for local services)
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow established and related connections (stateful)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Drop invalid packets (malformed, out-of-state)
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Anti-spoofing: drop packets with source matching our own IP
iptables -A INPUT -s 10.0.1.100 ! -i lo -j DROP

# SSH from management network only, rate limited
iptables -A INPUT -s 10.0.0.0/24 -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -m recent --set --name SSH
iptables -A INPUT -s 10.0.0.0/24 -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
iptables -A INPUT -s 10.0.0.0/24 -p tcp --dport 22 \
  -m conntrack --ctstate NEW -j ACCEPT

# HTTP and HTTPS from anywhere
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

# ICMP (ping) with rate limiting — 1 per second, burst of 4
iptables -A INPUT -p icmp --icmp-type echo-request \
  -m limit --limit 1/s --limit-burst 4 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

# SYN flood protection
iptables -A INPUT -p tcp --syn -m limit --limit 25/s --limit-burst 50 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

# Log dropped packets for debugging (rate limited to prevent log flooding)
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 \
  -j LOG --log-prefix "IPTABLES-DROP: " --log-level 4

# Drop everything else (explicit, matches default policy)
iptables -A INPUT -j DROP

echo "Firewall rules applied. $(iptables -L -n | wc -l) rules active."
Always allow loopback (`-i lo`) and established connections (`ESTABLISHED,RELATED`) before setting a default DROP policy. Without these, you will lock yourself out of the server and break all outbound connections. If working remotely via SSH, use a safety net:

~~~bash
# Safety net: flush rules in 5 minutes if you lose access
echo "iptables -F && iptables -P INPUT ACCEPT" | at now + 5 minutes

# Now apply your new rules — if you get locked out,
# the at job restores access in 5 minutes
sudo bash firewall.sh

# If everything works, cancel the safety net
atrm $(atq | tail -1 | awk '{print $1}')
~~~

nftables: The Modern Replacement

nftables replaces iptables with a cleaner syntax, better performance, and unified handling of IPv4, IPv6, and ARP:

#!/usr/sbin/nft -f
# /etc/nftables.conf

flush ruleset

table inet filter {
    # Rate limiting set for SSH
    set ssh_meter {
        type ipv4_addr
        flags dynamic
        timeout 60s
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # Loopback
        iifname "lo" accept

        # Connection tracking
        ct state established,related accept
        ct state invalid drop

        # SSH from management with rate limiting
        ip saddr 10.0.0.0/24 tcp dport 22 ct state new \
            meter ssh_meter { ip saddr limit rate 3/minute burst 5 packets } accept

        # Web traffic
        tcp dport { 80, 443 } ct state new accept

        # ICMP rate limited
        icmp type echo-request limit rate 1/second burst 4 packets accept

        # SYN flood protection
        tcp flags syn limit rate 25/second burst 50 packets accept

        # Log and count before dropping
        limit rate 5/minute burst 10 packets \
            log prefix "nft-drop: " counter drop

        # Counter for all other drops
        counter drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
# Apply nftables configuration
sudo nft -f /etc/nftables.conf

# List current ruleset
sudo nft list ruleset

# List with handles (for deletion)
sudo nft -a list chain inet filter input

# Add a rule dynamically
sudo nft add rule inet filter input tcp dport 8080 accept

# Delete a rule by handle number
sudo nft delete rule inet filter input handle 15

# Monitor rule hit counters
sudo nft list chain inet filter input | grep counter
Practice firewall rules on a test VM:

~~~bash
# Start with a permissive policy and logging
sudo iptables -P INPUT ACCEPT
sudo iptables -A INPUT -j LOG --log-prefix "FW-AUDIT: "

# Watch what traffic arrives
sudo tail -f /var/log/kern.log | grep "FW-AUDIT"

# From another machine, scan the target
nmap -sS -p 22,80,443,3306,5432,6379 target-ip

# Now apply restrictive rules and test again
sudo iptables -P INPUT DROP
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -j LOG --log-prefix "FW-DROP: "
sudo iptables -A INPUT -j DROP

# Re-scan — only port 443 should show as open
nmap -sS -p 22,80,443,3306,5432,6379 target-ip
~~~

Intrusion Detection Systems (IDS)

Firewalls decide what traffic is allowed based on headers. An IDS examines traffic content and behavior to determine what is malicious. It monitors network traffic (or host activity) and generates alerts when it detects potential attacks. It does not block traffic — it is a passive monitoring system.

NIDS vs HIDS

graph LR
    subgraph "Network IDS (NIDS)"
        TAP[Network TAP<br/>or Mirror Port] --> SNORT[Snort / Suricata / Zeek]
        SNORT --> ALERTS1[Alerts → SIEM]
        NOTE1[Sees: All network traffic<br/>Cannot see: Encrypted content<br/>Deployment: Network tap/span port]
    end

    subgraph "Host IDS (HIDS)"
        AGENT[Agent on Server] --> OSSEC[OSSEC / Wazuh / AIDE]
        OSSEC --> ALERTS2[Alerts → SIEM]
        NOTE2[Sees: File changes, processes,<br/>system calls, decrypted content<br/>Deployment: Agent per server]
    end

Snort Rule Anatomy

Snort rules are the lingua franca of network intrusion detection. Understanding their structure is essential for both writing custom rules and tuning false positives:

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
    msg:"SQL Injection attempt - UNION SELECT";
    flow:to_server,established;
    content:"UNION"; nocase;
    content:"SELECT"; nocase; distance:0; within:20;
    pcre:"/UNION\s+(ALL\s+)?SELECT/i";
    classtype:web-application-attack;
    sid:1000001; rev:3;
    reference:url,owasp.org/www-community/attacks/SQL_Injection;
    metadata:severity high, confidence medium;
)

Rule breakdown:

ComponentMeaning
alertAction — generate alert (vs drop, reject, pass)
tcpProtocol
$EXTERNAL_NET anySource IP (any external) and port (any)
->Direction (one-way)
$HTTP_SERVERS $HTTP_PORTSDestination — defined variables
msg:Alert message text
flow:to_server,establishedOnly match on established TCP connections going to server
content:"UNION"Byte pattern to match in payload
nocaseCase-insensitive matching
distance:0; within:20"SELECT" must appear within 20 bytes after "UNION"
pcre:Perl-Compatible Regular Expression for complex matching
classtype:Attack category classification
sid:Unique rule ID (1000000+ for custom rules)
rev:Rule revision number

Additional Snort rule examples:

# Detect Shellshock (CVE-2014-6271)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
    msg:"SHELLSHOCK attempt in HTTP header";
    flow:to_server,established;
    content:"() {"; fast_pattern;
    content:";";
    sid:1000002; rev:1;
    classtype:attempted-admin;
)

# Detect directory traversal
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
    msg:"Directory Traversal attempt";
    flow:to_server,established;
    content:".."; content:"/";
    pcre:"/\.\.[\\\/]/";
    sid:1000003; rev:1;
    classtype:web-application-attack;
)

# Detect outbound connection to known C2 IP
alert tcp $HOME_NET any -> 198.51.100.0/24 any (
    msg:"Outbound connection to known C2 network";
    flow:to_server,established;
    sid:1000004; rev:1;
    classtype:trojan-activity;
)

Signature-Based vs Anomaly-Based Detection

AspectSignature-BasedAnomaly-Based
How it worksCompares traffic to known attack patternsEstablishes baseline of "normal," alerts on deviations
CatchesKnown attacks with high accuracyNovel/zero-day attacks
MissesZero-day attacks, unknown variantsAttacks that mimic normal behavior
False positivesLow (well-tuned signatures)Higher (normal variations trigger alerts)
MaintenanceConstant signature updates neededRequires training period and ongoing tuning
ExamplesSnort signatures, Suricata rulesML-based UEBA, statistical models

Intrusion Prevention Systems (IPS)

An IPS is an IDS that sits inline in the traffic path and can actively block traffic, not just alert.

graph LR
    subgraph "IDS Deployment (Passive)"
        T1[Traffic] -->|Original path| S1[Server]
        T1 -->|Copy via TAP/mirror| IDS1[IDS]
        IDS1 -->|Alerts only| SIEM1[SIEM]
    end

    subgraph "IPS Deployment (Inline)"
        T2[Traffic] --> IPS1[IPS]
        IPS1 -->|Clean traffic| S2[Server]
        IPS1 -->|Malicious traffic| DROP1[DROP]
        IPS1 -->|Alerts| SIEM2[SIEM]
    end

    style DROP1 fill:#ff6b6b,stroke:#c0392b,color:#fff

IPS deployment considerations:

ConcernImpactMitigation
False positivesBlock legitimate usersStart in IDS mode, tune rules, then enable blocking
PerformanceLatency added to every packetHardware acceleration, bypass mode, multi-threaded engines
Fail modeWhat happens when IPS crashes?Fail-open (less secure) vs fail-closed (causes outage)
Encrypted trafficCannot inspect HTTPS contentTLS termination before IPS, or certificate-based inspection
EvasionFragmentation, encoding tricksProtocol normalization, reassembly before inspection

The IPS must be confident before blocking. Most deployments start in IDS mode (alert only) for weeks or months, tune the signatures to eliminate false positives, then switch specific high-confidence rules to IPS mode (block). You never go from zero to "block everything the IPS flags" on day one. A false positive in IDS mode is an alert someone reviews. A false positive in IPS mode is a customer who cannot use your service.


Suricata: Modern Network Security Monitoring

Suricata is the modern open-source alternative to Snort with significant advantages:

  • Multi-threaded architecture — scales to 10+ Gbps on commodity hardware
  • Native protocol detection — identifies HTTP, TLS, DNS, SMB, etc. on any port
  • Built-in TLS/JA3/JA4 fingerprinting — identify clients by TLS behavior
  • EVE JSON logging — structured, parseable output for SIEM integration
  • Lua scripting — custom detection logic beyond signatures
  • Automatic protocol parsing — extracts HTTP headers, DNS queries, TLS certificates
# Install and configure Suricata
sudo apt install suricata
sudo suricata-update enable-source et/open
sudo suricata-update

# Run in live capture mode
sudo suricata -c /etc/suricata/suricata.yaml -i eth0

# Run against a pcap file for analysis
sudo suricata -r captured_traffic.pcap -l /var/log/suricata/

# View fast alerts
tail -f /var/log/suricata/fast.log

Suricata EVE JSON Logging

The EVE JSON log provides rich structured data for every event — far more useful than flat text logs:

{
    "timestamp": "2025-03-12T14:23:01.123456+0000",
    "flow_id": 1234567890,
    "event_type": "alert",
    "src_ip": "203.0.113.50",
    "src_port": 49152,
    "dest_ip": "10.0.1.100",
    "dest_port": 443,
    "proto": "TCP",
    "alert": {
        "action": "allowed",
        "gid": 1,
        "signature_id": 2000001,
        "rev": 5,
        "signature": "ET WEB_SERVER SQL Injection Attempt",
        "category": "Web Application Attack",
        "severity": 1
    },
    "http": {
        "hostname": "app.example.com",
        "url": "/api/users?id=1' UNION SELECT",
        "http_method": "GET",
        "http_user_agent": "sqlmap/1.7",
        "status": 200,
        "length": 1523
    },
    "tls": {
        "subject": "CN=app.example.com",
        "issuerdn": "CN=Let's Encrypt Authority X3",
        "ja3": {
            "hash": "e7d705a3286e19ea42f587b344ee6865"
        }
    }
}
# Parse EVE JSON for specific alert types
cat /var/log/suricata/eve.json | python3 -c "
import sys, json
for line in sys.stdin:
    evt = json.loads(line)
    if evt.get('event_type') == 'alert':
        print(f\"[{evt['alert']['severity']}] {evt['alert']['signature']}\")
        print(f\"  {evt['src_ip']}:{evt.get('src_port','')} -> \
{evt['dest_ip']}:{evt.get('dest_port','')}\")
        if 'http' in evt:
            print(f\"  URL: {evt['http'].get('url','')}\")
            print(f\"  UA:  {evt['http'].get('http_user_agent','')}\")
        print()
" 2>/dev/null
Set up Suricata with Emerging Threats rules on a test system:

~~~bash
# Install and update rules
sudo apt install suricata
sudo suricata-update enable-source et/open
sudo suricata-update

# Capture some traffic with tcpdump
sudo tcpdump -i eth0 -c 5000 -w /tmp/capture.pcap

# Analyze the capture with Suricata
sudo suricata -r /tmp/capture.pcap -l /tmp/suricata-output/

# View results
cat /tmp/suricata-output/fast.log
cat /tmp/suricata-output/eve.json | python3 -m json.tool | head -100

# Generate test traffic that triggers rules
# (against YOUR OWN test server only)
curl "http://your-test-server/page?id=1'+UNION+SELECT+1,2,3--"
curl -A "sqlmap/1.7" http://your-test-server/
curl "http://your-test-server/page?file=../../../etc/passwd"

# Check if Suricata detected them
grep "alert" /var/log/suricata/fast.log
~~~

Web Application Firewalls (WAFs)

Everything discussed so far works at the network and transport layers. WAFs work at Layer 7 — they understand HTTP specifically.

WAF Inspection Points

graph TD
    subgraph "HTTP Request Inspection"
        A["POST /api/users?search=admin HTTP/1.1"] -->|"1. URL path + query string"| CHECK1[SQL injection, path traversal,<br/>parameter tampering]
        B["Host: example.com"] -->|"2. Host header"| CHECK2[Host header injection,<br/>virtual host abuse]
        C["Cookie: session=abc123"] -->|"3. Cookies"| CHECK3[Session fixation,<br/>cookie injection]
        D["Content-Type: application/json"] -->|"4. Content-Type"| CHECK4[Content-type mismatch,<br/>multipart abuse]
        E["User-Agent: sqlmap/1.7"] -->|"5. User-Agent"| CHECK5[Known scanner/bot<br/>fingerprints]
        F["{\"name\":\"<script>alert(1)</script>\"}"] -->|"6. Request body"| CHECK6[XSS, injection,<br/>XXE, mass assignment]
    end

    CHECK1 --> DECISION{WAF Decision}
    CHECK2 --> DECISION
    CHECK3 --> DECISION
    CHECK4 --> DECISION
    CHECK5 --> DECISION
    CHECK6 --> DECISION

    DECISION -->|Clean| PASS[Forward to application]
    DECISION -->|Malicious| BLOCK[Block + Log + Alert]

    style BLOCK fill:#ff6b6b,stroke:#c0392b,color:#fff
    style PASS fill:#2ecc71,stroke:#27ae60,color:#fff

WAF Bypass Techniques

During a penetration test, the client proudly announced they had a top-tier cloud WAF. It was bypassed in four different ways within the first hour:

1. **Encoding bypass:** The WAF checked for `<script>` but not double-URL-encoded `%253Cscript%253E` or tag splitting `<scr<script>ipt>`. After the WAF passed it through, the web server decoded the double encoding.

2. **JSON body bypass:** The WAF inspected URL parameters and form bodies but did not parse JSON request bodies. The SQL injection payload was sent as a JSON field value — the WAF saw valid JSON and passed it through.

3. **Chunked transfer encoding:** `UNION SELECT` was split across two HTTP chunks: `UNI` and `ON SELECT`. The WAF inspected each chunk independently and found nothing suspicious. The web server reassembled the chunks before processing.

4. **HTTP/2 header manipulation:** The WAF inspected HTTP/1.1 traffic. A direct HTTP/2 connection with a crafted pseudo-header bypassed the WAF entirely.

The client thought the WAF was their primary defense. It was a speed bump.
Bypass TechniqueHow it worksExample
URL encodingEncode special characters%27%20OR%201%3D1
Double encodingEncode the percent signs%2527%2520OR
Unicode encodingUse Unicode representations\u0027 OR
Case variationMix upper/lowercaseSeLeCt, uNiOn
Comment injectionBreak keywords with SQL commentsSEL/**/ECT, UN/**/ION
Alternative syntaxUse functions instead of keywordsCHAR(83,69,76,69,67,84) for SELECT
HTTP parameter pollutionDuplicate parameters?id=1&id=UNION+SELECT
Chunked encodingSplit payload across chunksTransfer-Encoding: chunked
Protocol mismatchUse HTTP/2, WebSocketDirect H2 connection bypassing H1 WAF
Multipart abusePayload in file upload boundaryContent-Disposition: form-data; name="x'; DROP TABLE--"
Newline injectionBreak rules with \r\nSEL\r\nECT

ModSecurity with OWASP Core Rule Set (CRS)

ModSecurity is the most widely deployed open-source WAF engine:

# Install ModSecurity for Nginx
sudo apt install libmodsecurity3 libmodsecurity-dev

# Download OWASP Core Rule Set
git clone https://github.com/coreruleset/coreruleset.git /etc/modsecurity/crs
cp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf
# Nginx configuration with ModSecurity
load_module modules/ngx_http_modsecurity_module.so;

server {
    listen 443 ssl http2;
    server_name example.com;

    modsecurity on;
    modsecurity_rules_file /etc/modsecurity/main.conf;

    location / {
        proxy_pass http://backend;
    }
}

OWASP CRS Paranoia Levels:

graph LR
    subgraph "Paranoia Levels"
        PL1[Level 1<br/>Default] -->|"More rules"| PL2[Level 2]
        PL2 -->|"More rules"| PL3[Level 3]
        PL3 -->|"More rules"| PL4[Level 4]
    end

    PL1 --- D1["Low false positives<br/>Catches obvious attacks<br/>Good starting point"]
    PL2 --- D2["Moderate FPs<br/>Catches encoded attacks<br/>Needs some tuning"]
    PL3 --- D3["Higher FPs<br/>Catches obfuscated attacks<br/>Significant tuning needed"]
    PL4 --- D4["Many FPs<br/>Maximum detection<br/>Only for high-security apps<br/>with extensive tuning"]

    style PL1 fill:#2ecc71,stroke:#27ae60,color:#fff
    style PL2 fill:#f39c12,stroke:#e67e22,color:#fff
    style PL3 fill:#e74c3c,stroke:#c0392b,color:#fff
    style PL4 fill:#8e44ad,stroke:#6c3483,color:#fff

WAF deployment best practices:

  1. Deploy in detection mode first (log, don't block)
  2. Monitor logs for 2-4 weeks
  3. Create exclusion rules for legitimate traffic patterns (e.g., admin endpoints that legitimately contain SQL keywords)
  4. Gradually increase paranoia level
  5. Switch to blocking mode for high-confidence rules
  6. Keep some rules in detection-only for continued monitoring
  7. Review and tune monthly

Defense in Depth: The Complete Traffic Flow

Here is how all these layers work together in a production environment.

flowchart TD
    INTERNET[Internet] --> EDGE

    subgraph EDGE["Layer 1: Edge / DDoS Protection"]
        CF[Cloudflare / AWS Shield]
        CF_DESC["Volumetric attack mitigation<br/>IP reputation filtering<br/>Geographic blocking<br/>Bot management"]
    end

    EDGE --> FW

    subgraph FW["Layer 2: Perimeter Firewall (Stateful)"]
        FIREWALL[nftables / AWS Security Group]
        FW_DESC["Port/protocol filtering<br/>Allow only 80, 443 inbound<br/>Connection state tracking<br/>Anti-spoofing rules"]
    end

    FW --> IPS_L

    subgraph IPS_L["Layer 3: IPS (Suricata, Inline)"]
        IPS_E[Suricata IPS Engine]
        IPS_DESC["Known exploit signatures<br/>Protocol anomaly detection<br/>TLS/JA3 fingerprinting<br/>Drops matched attacks"]
    end

    IPS_L --> LB

    subgraph LB["Layer 4: Load Balancer / Reverse Proxy"]
        NGINX[Nginx / HAProxy / ALB]
        LB_DESC["TLS termination<br/>Request routing<br/>Connection rate limiting<br/>Header normalization"]
    end

    LB --> WAF_L

    subgraph WAF_L["Layer 5: WAF"]
        MODSEC[ModSecurity + OWASP CRS]
        WAF_DESC["SQL injection detection<br/>XSS detection<br/>Command injection detection<br/>Bot/scanner fingerprinting"]
    end

    WAF_L --> APP

    subgraph APP["Layer 6: Application"]
        APPSERVER[Application Code]
        APP_DESC["Parameterized queries<br/>Output encoding<br/>Input validation<br/>Authorization checks"]
    end

    APP --> DB

    subgraph DB["Layer 7: Database"]
        DATABASE[PostgreSQL / MySQL]
        DB_DESC["Least-privilege accounts<br/>Query audit logging<br/>Encryption at rest<br/>Row-level security"]
    end

    SIEM_M["Monitoring: IDS + SIEM + Log Aggregation"]
    EDGE -.-> SIEM_M
    FW -.-> SIEM_M
    IPS_L -.-> SIEM_M
    WAF_L -.-> SIEM_M
    APP -.-> SIEM_M
    DB -.-> SIEM_M

Why do you still need parameterized queries if the WAF catches SQL injection? Because the WAF might catch it. Over a dozen bypass techniques were just demonstrated above. The WAF might be misconfigured, have a rule gap, or face an encoding the signatures don't cover. Parameterized queries are immune to SQL injection — they work at the protocol level and cannot be bypassed regardless of encoding, obfuscation, or creative syntax. The WAF is a safety net. The application is the real defense. Both must be in place.

The principle of defense in depth comes from military fortification: moats, walls, keeps, and citadels. Each layer:

1. **Delays the attacker** — buying time for detection and response
2. **Reduces the attack surface** — each layer filters some attacks
3. **Provides redundancy** — if one layer fails, others still function
4. **Increases attacker cost** — bypassing multiple layers requires more skill, time, and resources

In mathematical terms: if each layer catches 90% of attacks, two layers catch 99%, and three layers catch 99.9%. The residual risk from any single layer is dramatically reduced by adding more layers.

No single layer is perfect. The combination provides security that no individual component can achieve alone. The attacker must bypass ALL layers; the defender only needs ONE layer to catch the attack.

Monitoring, Alerting, and Response

Detection without response is just expensive logging. All these systems must feed into a central monitoring pipeline:

# Suricata EVE → Filebeat → Elasticsearch → Kibana
# /etc/filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    paths:
      - /var/log/suricata/eve.json
    json.keys_under_root: true
    json.add_error_key: true

output.elasticsearch:
  hosts: ["https://elk.internal:9200"]
  index: "suricata-%{+yyyy.MM.dd}"

Key metrics to monitor and alert on:

LayerMetricAlert Threshold
FirewallDropped packets/sec> 10,000 (possible DDoS)
FirewallConnection table utilization> 80% (table exhaustion risk)
IDS/IPSHigh-severity alerts/hour> 0 (investigate immediately)
IDS/IPSUnique source IPs triggering alertsSudden spike
WAFBlocked requests/min> baseline + 3 std dev
WAFSQL injection rule triggersAny (investigate)
ApplicationAuth failure rate> 100/min per IP
Application500 error rate> 1% of requests

What You've Learned

This chapter covered the layered network defense systems that protect traffic from the internet to the application:

  • Packet filtering firewalls operate at Layers 3-4, filtering by IP, port, and protocol. They are fast but cannot inspect application content or track connection state.

  • Stateful inspection firewalls track TCP connection state via a state table, automatically allowing return traffic for established sessions and preventing spoofed packets.

  • iptables/nftables are the Linux kernel firewall tools. The chain traversal path (PREROUTING -> INPUT/FORWARD -> OUTPUT -> POSTROUTING) determines when and how rules are applied. Design rules with default deny, explicit allows, and rate limiting.

  • IDS (Snort, Suricata, Zeek) monitors traffic and generates alerts using signature-based rules and anomaly detection. It is passive — it copies traffic via a TAP or mirror port. Suricata's EVE JSON logging provides rich structured data for SIEM integration.

  • IPS sits inline and actively blocks traffic matching attack signatures. It must be carefully tuned — start in IDS mode, eliminate false positives, then enable blocking for high-confidence rules.

  • WAFs inspect HTTP traffic specifically, catching SQL injection, XSS, and other web application attacks. They can be bypassed through encoding, chunked transfer, protocol tricks, and obfuscation. They are a safety net, not a primary defense.

  • Defense in depth layers these systems so that each catches what the others miss. No single layer is sufficient. The attacker must bypass all layers; the defender needs only one to succeed.

Firewalls control what traffic enters, IDS/IPS identifies malicious traffic, WAFs inspect web-specific attacks, and the application handles business logic security. Each one has blind spots, so you need all of them — and you need to monitor all of them. The most sophisticated security stack in the world is useless if nobody is watching the alerts. Detection without response is just expensive logging.