Extended Brain Storage

OpenBSD: Packet Filtering

Posted on September 17, 2016

It is strongly recommended to handle incoming and outgoing packets across the networks in order to deal with various types of attacks. Since version 3.0, OpenBSD has come with a packet filtering mechanism called the pf...

The Default Setup

The packet filtering needs to be set up in the /etc/pf.conf file. By default, OpenBSD comes with the following setup:

$ cat /etc/pf.conf
#       $OpenBSD: pf.conf,v 1.54 2014/08/23 05:49:42 deraadt Exp $
#
# See pf.conf(5) and /etc/examples/pf.conf

set skip on lo

block return    # block stateless traffic
pass            # establish keep-state

# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010

The syntax is pretty self-explanatory:


Packet Filtering Principle

In a nutshell, the packet filter (pf) logic is:

Those who suffer from "professional deformation" (Cisco PIX and ASA appliances esp.) may like to apply the rules in the "reverse logic" order:

block drop in quick on egress inet from <martians> to any

Within traffic normalisation, the packet filtering process deals with verifying packets, packet fragments, spoof traffic, and other irregularities. Scrubbing involves sanitising packet content using:

In order to enforce the aformentioned normalisation on all interfaces, the following rule can be used:

match in all scrub (no-df random-id max-mss 1460)

Spoofed traffic blocking should be automatically applied in any environment. OpenBSD takes care of it using the antispoof directive, which:

antispoof for lo0

which expands to:

block drop in on ! lo0 inet from 127.0.0.1/8 to any
block drop in on ! lo0 inet6 from ::1 to any

The antispoof directive is not a silver bullet, and it has the following caveats:

Tables are named structures which can hold (within braces {...}) a collection (list) of addresses and networks. For simplicity, the following IPv4 example considers multicast traffic as martian:

table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 \
                   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.168.0.0/16 \
                   198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/3 }

The comprehensive and detailed information can be found in $ man pf.conf or online.


Usage

The packet filter is enabled by default. Should it be not running, it can be enabled using:

$ pfctl -e

If necessary, it can be also disabled using:

$ pfctl -d

The currently loaded filter rules can be shown using (more details in the man page):

$ pfctl -s rules
# or simply:
$ pfctl -s r
$ pfctl -s all # to show everything

Parsing (and checking) the filtering rules without actually loading them can be done by:

$ pfctl -nf /etc/pf.conf

Replacing the current ruleset with the rules contained in the configuration file can be done by:

$ pfctl -f /etc/pf.conf

The default configuration file can be removed right away or moved as follows:

$ mv /etc/pf.conf /etc/pf.conf.ORIGINAL

Clearing PF rules or counters can be accomplished as follows:

$ pfctl -F all # or just {rules|queue|nat|info}
$ pfctl -z # clear all counters

IPv4 Packet Filtering Example

The following example considers that:

$ pfctl -t bruteforce -T show | awk '{print $1}' > /etc/bruteforce

Suffering from "professional deformation" (as discussed in "Packet Filtering Principle") and considering the previously defined requirements, a new configuration file can be created as follows:

$ vi /etc/pf.conf
### Macros definition
table <trusted_peers> { IP-ADDRESS NETWORK-ADDRESS/PREFIX-LENGTH }
table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16     \
                   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.168.0.0/16 \
                   198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/3 }
table <bruteforce> persist file "/etc/bruteforce"
icmp_types = "{ echoreq, unreach, echorep, timex }" # these four are enough
ssh_port = "1234"                       # ssh may run on a non-default port

### Default network hygiene
set block-policy drop                   # { drop | return }
#set loginterface egress                # if necessary for troubleshooting
set skip on { lo0 enc0 }                # ignore pf rules on lo0 and enc0
match in all scrub (no-df max-mss 1460) # random-id blocks tcptraceroute
match in on egress scrub (no-df random-id max-mss 1460)
  ### MTU and MSS calculation:
  # MTU = Ethernet - IP = 1500 - 20 = 1480
  # MSS = Ethernet - IP - TCP = 1500 - 20 - 20 = 1460
#antispoof quick for { egress }         # will be applied manually for all

### Bruteforce, spoofing and misconfiguration prevention
# relentless denial of spoofers on egress interface
block drop in quick on egress inet from <martians> to any
# polite rejection of misconfigurations to the Internet
block return out quick on egress inet from any to <martians>
# permission to "trusted peers"
pass in quick on egress inet proto { esp, tcp, udp } from <trusted_peers> to (egress)
# relentless denial of bruteforcers
block drop in quick on egress inet from <bruteforce> to any

### Allowed outgoing IPv4 traffic
pass out quick inet

### Allowed services on egress and filter out bruteforce
pass in quick on any inet proto icmp all icmp-type $icmp_types
pass in quick on egress inet proto tcp from any to (egress) port $ssh_port \
        flags S/SA keep state \
        (max-src-conn 3, max-src-conn-rate 1/2, \
        overload <bruteforce> flush global)

### The rest of IPv4 traffic denied and logged
#block drop in quick log inet all       # if necessary for troubleshooting
#block drop out quick log inet all      # if necessary for troubleshooting
### The rest of IPv4 traffic denied and not logged
block drop in quick inet all
block drop out quick inet all

### The rest (all) of IPv6 traffic denied
block drop in quick inet6 all
block drop out quick inet6 all

Considering another example; an appliance acts as a home or SMB FW/router and requires the following setup:

The configuration reflecting the above is as follows:

$ vi /etc/pf.conf
### Macros definition
#egress = "em0"                         # WAN interface (automatically)
lan_if = "em1"                          # LAN interface
wlan_if = "athn0"                       # WLAN interface
dmz_if = "em2"                          # DMZ interface
table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16     \
                   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.168.0.0/16 \
                   198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/3 }

### Default network hygiene
set block-policy drop                   # { drop | return }
#set loginterface egress                # if necessary for troubleshooting
set skip on { lo0 enc0 }                # ignore pf rules on lo0 and enc0
match in all scrub (no-df max-mss 1452) # random-id blocks tcptraceroute
match in on egress scrub (no-df random-id max-mss 1452) # 1452 for PPPoE on em0
#antispoof quick for { egress }         # will be applied manually for all

### Anything on egress interface denied
# Note: ICMP should be allowed to comply with RFC
block drop in quick on egress inet from any to any
# polite rejection of misconfigurations to the Internet
block return out quick on egress inet from any to <martians>

### NAT
# * the quick makes it impossible to send out the traffic
# * the following rule allows the traffic to pass out of the router
match out on egress inet from ! egress to any nat-to (egress)

### Allowed outgoing IPv4 traffic
pass out quick inet

### Spoofing prevention on LAN, WLAN, DMZ traffic
pass in quick on $lan_if inet from $lan_if:network
pass in quick on $wlan_if inet from $wlan_if:network
# Allowed DMZ->LAN and DMZ->WLAN, the rest denied
pass in quick on $dmz_if inet from $dmz_if:network to $lan_if:network
pass in quick on $dmz_if inet from $dmz_if:network to $wlan_if:network
# Denied other DMZ->any traffic
block return in quick on $dmz_if inet all

### The rest of IPv4 traffic denied and logged
#block drop in quick log inet all       # if necessary for troubleshooting
#block drop out quick log inet all      # if necessary for troubleshooting
### The rest of IPv4 traffic denied and not logged
block drop in quick inet all
block drop out quick inet all

### The rest (all) of IPv6 traffic denied
block drop in quick inet6 all
block drop out quick inet6 all

Table Entries Expiration

A single IP-ADDRESS can be removed from the bruteforce table as follows:

$ pfctl -t bruteforce -T delete IP-ADDRESS

As time goes by, the bruteforce table will grow incrementally. Thus to prevent it from taking unreasonable amounts of memory, the ability to expire table entries not referenced in a specified number of seconds was introduced in OpenBSD 4.1. The following example removes entries not referenced for a day (86400 seconds):

$ pfctl -t bruteforce -T expire 86400

In order to automate the expiration process, cron can be utilised. The following example will run the command once a day (at 1:30 AM):

$ echo "pfctl -t bruteforce -T expire 86400" >> /etc/daily.local

Packet forwarding is not considered here. More details can be found in the [Packet Forwarding]({{< ref "#packet-forwarding" >}}) section.


IPv6 Packet Filtering Example

This part is a stub. The explanation is necessary...


Packet Forwarding

Packet forwarding is the process of sending packets out an interface. It is sometimes incorrectly referred to as routing. Routing is the process of looking up the interface to forward packets through. The packet filter does not itself forward packets between interfaces.

If desired, forwarding needs to be enabled by setting the kernel variables as follows:

$ sysctl net.inet.ip.forwarding=1   # for IPv4
$ sysctl net.inet6.ip6.forwarding=1 # for IPv6

In order to make the changes permanent (survive a system restart), the sysctl.conf file needs to be updated as follows:

$ echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf   # for IPv4
$ echo "net.inet6.ip6.forwarding=1" >> /etc/sysctl.conf # for IPv6

Troubleshooting

Considering the egress interface is named vio0, the traffic flowing through it can be monitored using tcpdump as follows:

$ tcpdump -venti vio0

Another example of capturing everything except for host 1.2.3.4 and its ssh traffic:

$ tcpdump -venti vio0 not ip host 1.2.3.4 and not tcp port 22

The packet filter can capture packets as well using a pseudo-device interface (pflog0 by default). After enabling the following rules (taken from the [Packet Filtering of IPv4]({{< ref "#packet-filtering-of-ipv4-traffic" >}}) section):

### Deny and log the rest of IPv4 traffic
block drop in quick log inet all       # if necessary for troubleshooting
block drop out quick log inet all      # if necessary for troubleshooting

the logged traffic can be observed live using:

$ tcpdump -venti pflog0

Tags: #OpenBSD #security #pf #packet #filter

⏴ Previous Post Next Post ⏵