Securing hosts with PF


PF is OPENBSD’S default Packet filtering for TCP/IP packets and NAT. PF has been ported to FreeBSD and it is currently at version 4.5 on FreeBSD 9.0-RELEASE.

Today, we will see how we can use PF to effectively secure a FreeBSD host on the Internet.
Before we even start talking about PF, it is essential to point out a few things about network firewalls.

  • Network firewalls as opposed to packet filtering devices use statefull inspection in order to decide if a packet is allowed to pass or not.
  • Statefull inspection was inspired by the concept of state and the 3-way handshake which we find only on TCP type base connections.
  • Statefull inspection has progressed a lot over the time, making it somehow possible to maintain it on stateless protocols such us UDP or ICMP.

Network firewalls work mainly on layers 3 and 4 of the OSI model. Therefore, application inspection is not possible since it requires the ability to inspect at the layer 7.

General DO & DON’T

  • Log everything! (except Internet noise)
  • Be similarly restrictive to inbound and outbound traffic!
  • Process your most used rules first, respect your CPU and memory!
  • Don’t be tempted into using exotic rules that you read somewhere to prevent port scans!

Let’s start by building our first policy for a webserver:

### macro definitions
ext_if = "em0"
dns_server = ""
webservices = "{80, 443, 22}"
icmp_types = "echoreq"
netbios_tcp = "{445, 137, 138, 139}"
netbios_udp = "{445, 137, 138, 139}"
tcp_out = "{5999, 80, 21}"

First we have define our macros. Macros are variables in a sense, holding information regarding our interfaces and tcp / udp ports. It is always a good idea to group port numbers since it can greatly reduce our rules

### all incoming traffic on external interface is normalized and fragmented
### packets are reassembled.
scrub in on $ext_if all fragment reassemble
### exercise antispoofing on the external interface, but add the local
### loopback interface as an exception, to prevent services utilizing the
### local loop from being blocked accidentally.
set skip on lo0
antispoof for $ext_if inet

Next we tell the firewall to reassemble all fragmented packets, skip any packet filtering rules for the loopback interface and finally to check for spoofed IP addresses on the external interface.

### get rid quick of Internet noise like microsoft netbios service.
### This accounts to 80% of dropped traffic. We don't need to log this also
block in quick on $ext_if proto tcp from any to any port $netbios_tcp
block in quick on $ext_if proto udp from any to any port $netbios_udp

This part you don’t usually find in PF rules but I think that it is worth having it.
If you look at the logs on an Internet faced firewall, you will see that most of the dropped traffic is netbios. These are usually Zombie hosts infected by some nasty old windows virus.
By blocking them first without logging this traffic, we relax both our firewall from having to read all the rules before it drops this. We also maintain cleaner logs.

###clean up rule
block log all

Finally, we tell our firewall to block any traffic, inbound and outbound, that doesn’t much a rule. We also like to log everything.

### set a rule that allows inbound traffic with synproxy handshaking.
pass in quick log on $ext_if proto tcp from any to any port $webservices flags S/SA synproxy state
pass in quick log inet proto icmp all icmp-type $icmp_types keep-state

Like I mentioned earlier, the most used rules should come first. Since this is a web server, we allow http, https and ssh incoming traffic. We also like to be able to ping our server. Notice that I am using the “synproxy state” keyword. That way I am instructing the firewall to proxy the 3-way tcp handshake, keeping syn flood attacks away.

### keep state on any outbound tcp, udp or icmp traffic. modulate the isn of
### outgoing packets. (initial sequence number) broken operating systems
### sometimes don't randomize this number, making it guessable.
pass out quick log on $ext_if proto tcp from any to any port $tcp_out modulate state
pass out quick log on $ext_if proto udp from any to $dns_server port domain modulate state
pass out quick log inet proto icmp all icmp-type $icmp_types keep-state

Finally, I allow my webserver outbound access for http, ftp and cvs. This could and should become stricter and allow this type of communication only to certain destinations.

This is just an example of a host based firewall. A real firewall would have at least two interfaces and would probably perform NAT. The point is that an effective policy is a policy that will not create extra burden, will be clean and easy to read and maintain.

Always start by processing your most used rules first. Group ports and servers and log all interesting traffic.

Powered by BareBSD

3 Responses

  1. Geoff says:

    Its probably worth noting that the “synproxy state” option isn’t compatible with IPv6, at least on 8.2 and earlier, in 9.0 with pf 4.5 it might work but I’ve not tested.

  2. Hakish o Nukama says:

    There is a little hyphen destroying your pf rules:

    pass in quick log inet proto icmp all icmp-type $icmp_types keep-state

    Working rule:
    pass in quick log inet proto icmp all icmp-type $icmp_types keep state

Leave a Reply