Topic 20

Firewalls and Filtering in Practice

Firewalls

A firewall enforces which traffic is allowed to flow, and the whole security value comes from a default-deny posture: permit what is needed, drop everything else. This topic moves from the concept to a real, readable nftables ruleset on a Meridian host — stateful rules, ingress and egress filtering — and to the difference between packet-filtering, stateful, and application-layer firewalls.

Egress filtering, the part most teams skip, gets special attention here, because restricting outbound traffic is what strangles command-and-control and slows exfiltration when a host is already compromised.

Default-Deny and Rule Order

A firewall is only as good as its default. You start by dropping everything, then allow the specific flows a host actually needs — because you can never enumerate everything bad, so a deny-list is always incomplete. Rules are evaluated in order, so a broad early allow can nullify every rule after it; getting the order right matters as much as getting the rules right.

Stateful Filtering

A stateful firewall tracks connections, so it can allow the return traffic of an established session without a separate rule. This is why you allow "new inbound to port 443" and let the replies flow automatically, and why ct state appears throughout a real ruleset. Without state, you would have to open wide port ranges for return traffic, which is exactly the broad exposure default-deny exists to avoid.

Ingress vs Egress

Most teams filter inbound traffic carefully and leave outbound wide open — which lets malware call home and exfiltrate freely once it lands. Restricting egress to known destinations is one of the highest-value, least-used controls in security: it breaks command-and-control, slows data theft, and turns a quiet compromise into one that trips against a wall it did not expect.

A Meridian host firewall in nftables — default-deny, stateful, egress-controlled
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;   # default-deny inbound
    ct state established,related accept                # stateful: allow return traffic
    iif lo accept
    ip saddr 10.20.0.0/24 tcp dport 22 accept          # SSH only from the admin subnet
    tcp dport 443 accept                               # the app
  }
  chain output {
    type filter hook output priority 0; policy drop;   # default-deny OUTBOUND too
    ct state established,related accept
    ip daddr 10.20.5.10 tcp dport 5432 accept          # only to the database
    tcp dport { 53, 443 } ip daddr @update_servers accept  # DNS + updates to known hosts
  }
}

This ruleset drops all inbound and outbound by default, allows established return traffic statefully, permits SSH only from the admin subnet and HTTPS to the app, and — crucially — constrains egress so the host can reach only the database and a known set of update servers. A web shell dropped on this host cannot open a command-and-control channel to an arbitrary internet address, because the output chain simply drops it.

Layers of Firewall

Firewalls come in layers that see progressively more and cost progressively more. A packet filter (nftables, iptables) works at layer 3 and 4 with no connection memory; a stateful firewall tracks connections; and an application-layer or next-generation firewall understands HTTP and can enforce app-aware policy — the web application firewall of Chapter 6. They compose; a WAF is not a substitute for the lower layers, it sits on top of them.

Packet-Filter vs Stateful vs Application-Layer

Packet filter — fast, per-packet layer 3/4 rules, no connection memory.

Stateful — tracks connections and allows return traffic safely; the modern baseline.

Application-layer (WAF / NGFW) — inspects layer 7 (HTTP, TLS SNI), can block SQLi/XSS patterns and enforce app policy; richer and heavier, and not a replacement for the lower layers.

Common Mistakes
  • A permissive default — allow, with a few denies — instead of default-deny, when you can never enumerate everything bad to block.
  • Filtering ingress only and leaving egress unrestricted, giving malware a free channel for command-and-control and exfiltration.
  • Rule-order mistakes: a broad allow above specific denies, or a cleanup rule that never matches because an earlier rule already accepted the traffic.
  • Relying on a host firewall alone or a network firewall alone, instead of layering both, so one bypass is total.
  • Opening wide port ranges for return traffic instead of using stateful established/related rules.
Best Practices
  • Default-deny in both directions and allow only the specific, documented flows each host needs.
  • Use stateful rules and explicitly allow established and related return traffic rather than opening wide port ranges.
  • Restrict egress to known destinations to break command-and-control and slow exfiltration.
  • Layer host and network firewalls, keep rules under version control, and review them as the environment changes.
  • Put internet-facing web apps behind a WAF as an added layer, not as a replacement for L3/L4 filtering.
Comparable toolsHost / network nftables · iptables · ufw/firewalld · cloud security groupsNext-gen Palo Alto · pfSense/OPNsenseApp layer WAF (ModSecurity) — Chapter 6

Knowledge Check

Why is default-deny the foundation of a firewall's security value?

  • You cannot enumerate every bad flow
  • It is much faster for the firewall to process than an allow-list
  • It lets the firewall skip stateful connection tracking
  • It only applies to outbound traffic

Why is restricting egress one of the highest-value, least-used firewall controls?

  • It breaks command-and-control and slows exfiltration
  • It speeds up inbound connections to the web app
  • It removes the need for any inbound rules
  • It is strictly required for stateful connection tracking to function

What does a stateful firewall let you avoid that a stateless one forces on you?

  • Opening wide port ranges for return traffic, since it tracks connections
  • Writing any explicit allow rules for outbound traffic at all
  • Using a default-deny policy
  • Inspecting HTTP payloads for attacks

You got correct