Memra

Default deny, and the rule that breaks it

◈ 9 cards

The two default policies, first-match-wins semantics, and an attack/patch arc that shows why a ruleset is only correct read top to bottom.

The default policy is a rule

Before any rule is written, one decision settles the character of the whole ruleset: what happens to a packet that matches nothing.

  • Default = discard. That which is not expressly permitted is prohibited. Everything is blocked to begin with and services are added one at a time as a case is made for each. It is conservative, it is more visible to users — who experience the firewall as an obstacle, at least until the ruleset settles — and it is what businesses and government prefer.
  • Default = forward. That which is not expressly prohibited is permitted. Easier on users, weaker, and it puts the administrator permanently on the back foot: each new threat has to be reacted to as it becomes known. It is not recommended.

This is the network-layer face of fail-safe defaults. And note that the default is not something outside the ruleset — a well-written ruleset ends with an explicit deny any any that states it, so that reading the rules tells you the policy.

First match decides

Rules are evaluated top to bottom, and the first one that matches decides. Nothing below it is consulted. That single sentence has two consequences worth internalising:

  1. Rule order is semantics, not style. A permissive rule placed above a restrictive one silently disables it. The ruleset still loads, still looks reasonable, and no longer does what you meant.
  2. A ruleset is only correct as a sequence. You cannot check a rule on its own; you check it against everything above it.

Worked example — build it, break it, patch it

The policy: inside hosts may browse the web, and one internal mail server may receive mail from outside. Nothing else.

Start with the outbound half. Rule 1 permits inside -> outside TCP to port 443. But a TCP conversation needs both directions, and the replies come back to whatever high-numbered port the client picked — the client port is chosen dynamically from 1024–65535 and has meaning only for the life of that connection. So rule 2 permits inbound TCP to any inside port above 1023.

The break. Rule 2 does not say replies. It says anything from outside to any inside port above 1023. An attacker binds a source port of 7331 and opens a connection to an internal administration console listening on 9090. Rule 2 permits it. The ruleset that was meant to allow web browsing has just published every high-numbered internal service to the Internet.

The patch. Add a source port column: inbound replies to browsing must come from port 443. Now the attacker's packets from 7331 do not match.

The break again. A source port is only a convention about what the sender chose to put in a field. Nothing stops an outsider binding their own tool to source port 443 and trying the same thing.

The real patch. Require the ACK flag. The ACK bit is set on every TCP segment except the one that opens a connection, so requiring it on inbound traffic admits only segments that claim to belong to a conversation somebody inside started. This is stateless statefulness: an approximation of connection state kept with no state at all. Which is a warning as much as a fix, and the next lesson takes it apart.

The three classic attacks

  • IP address spoofing — an outside packet carrying an inside source address, aimed at services that trust addresses. Countermeasure: discard packets whose source address is internal when they arrive on an external interface.
  • Source routing — the sender specifies the route in an IP option, hoping to slip past a filter that only inspects addresses. Countermeasure: discard every packet that uses the option. Do not try to analyse it.
  • Tiny fragments — split the TCP header so the ports land in a later fragment. Countermeasure: require a minimum first-fragment payload, and drop the remaining fragments of a rejected datagram.

And the standing weaknesses that no ruleset fixes: a packet filter cannot examine upper-layer data, so if an application is allowed then every function within it is allowed; its logging is thin, because it only ever knew the fields it decided on; it supports no real user authentication; and with so few variables driving the decision it is easy to misconfigure in a way that quietly permits what policy denies.

#DirSourceDestPortFlagAction1outinsideoutside443-permit2inoutsideinside>1023ACKpermit3inoutsidemail25-permit4outmailoutside>1023-permit5anyanyanyany-denyDrop the ACK on row 2 and every inside high port is public.
Read top to bottom: the first row that matches decides. Row 5 is not decoration — it is the default policy written down so that the ruleset states its own posture.
NORMAL ~/memra/learn/comp-400/writing-a-packet-filter-ruleset-and-its-default-policy utf-8 LF