# PF syntax



## gpatrick (Dec 9, 2011)

In another thread, J65nko posted a snippet of code from someone else about passing traffic on the firewall, and if it was cracked they wouldn't need root, they could DOS as a non-root user.  I am not a PF expert and cobbled my configuration from books on PF, so even though I also have the line being referred to (pass out quick on $ext_if inet from $ext_if to any flags S/SA modulate state).  I am certain it came from one of three books on PF that I used.

This works, but since that previous line is said to be a problem, I would like to have others with far more experience with PF than me to review my conf file and what changes need or should be made.


```
# Macros
ext_if="axe0"
prv_if="axe1"
dmz_if="axe2"

# prv_hosts -- the list of addresses of hosts on the screened LAN
prv_hosts = "{192.168.1.10, 192.168.1.15, 192.168.1.20 192.168.1.21 192.168.1.22}"

# dmz_hosts -- the list of addresses of hosts in the DMZ
dmz_hosts = "{192.168.2.21/32, 192.168.2.22/32, 192.168.2.23/32, 192.168.2.26/32, 192.168.2.27/32, 192.168.2.28/32}"

# dmz_www -- the address of the WWW server in the DMZ
dmz_www = "192.168.2.21/32"

# dmz_smtp -- the address of the SMTP server in the DMZ
dmz_smtp = "192.168.2.22/32"

# dmz_dns -- the address of the DNS server in the DMZ
dmz_dns = "192.168.2.23/32"

# Tables
table <rfc1918> const { 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, 0.0.0.0/8, 240.0.0.0/4 }
table <bruteforce> persist 
# all IP addresses assigned to the firewall
table <firewall> const { self }

# Options
set require-order yes
set block-policy drop
set optimization aggressive
set loginterface $ext_if
set fingerprints "/etc/pf.os"
set ruleset-optimization none
set state-policy if-bound
set timeout { frag 30, tcp.established 120 }
set timeout { tcp.first 30, tcp.closing 30, tcp.closed 30, tcp.finwait 30 }
set timeout { udp.first 30, udp.single 30, udp.multiple 30 }
set timeout { other.first 30, other.single 30, other.multiple 30 }

################ Queueing ##################################
altq on $ext_if bandwidth 984Kb hfsc queue { q_pri, q_def, q_mus, q_tor }
 queue q_pri bandwidth 49%           priority 7 hfsc
 queue q_def bandwidth 49%           priority 5 hfsc (linkshare 49%) {q_smtp,q_http,ssh_login,q_def1}
   queue ssh_login bandwidth 96%       priority 5 hfsc
   queue q_http    bandwidth 1%        priority 4 hfsc
   queue q_smtp    bandwidth 1%        priority 4 hfsc
   queue q_def1    bandwidth 1%        priority 3 hfsc (default)
 queue q_mus bandwidth  1% qlimit 200  priority 4 hfsc
 queue q_tor bandwidth  1% qlimit 25   priority 3 hfsc (upperlimit 272Kb)

################ Translation (NAT)  ################################## 
# Translation rules are first match

# NAT internal hosts
nat on $ext_if inet from ! $ext_if to any -> $ext_if

# redirect connections to port 80 (HTTP) to DMZ
rdr on $prv_if inet proto tcp from $prv_hosts to $ext_if port {www, https} -> $dmz_www port 80
rdr on $ext_if inet proto tcp from any to $ext_if port {www, https} -> $dmz_www port 80

# redirect connections to port 25 (SMTP) to DMZ
#rdr on $prv_if inet proto tcp from $prv_hosts to $ext_if port smtp -> $dmz_smtp port smtp 
rdr on $ext_if inet proto tcp from any to $ext_if port smtp -> $dmz_smtp port smtp 
#rdr pass on $ext_if proto tcp from any to $ext_if port 25 -> $dmz_smtp port 25

# redirect connections to port 53 (DNS) to DMZ
rdr on $ext_if inet proto {tcp, udp} from any to $ext_if port 53 -> $dmz_dns

# DENY rouge redirections
no rdr

################ Traffic Normalizaton ##################################
# Set ttl to 254 to limit possible mapping of hosts behind firewall.
# Also set random-id to help with the same. 
match in all scrub (no-df random-id min-ttl 254 max-mss 1440 reassemble tcp)
match out all scrub (no-df random-id)

################ Filtering ##################################
# don't filter on the loopback interface
set skip on lo0
# Block spoofed packets: enable "set state-policy if-bound" above
antispoof log quick for { lo0 $dmz_if $prv_if $ext_if }
block log all
pass quick on lo0 all
block drop in quick on $ext_if from <bruteforce>
block in quick on $ext_if from <rfc1918> to any
block out quick on $ext_if from any to <rfc1918> 
block in quick on $ext_if inet from any to 255.255.255.255
block in log quick on $ext_if inet from urpf-failed to any
block in log quick on $ext_if inet from no-route to any

# Block anything coming from source we have no back routes for
block in log quick from no-route to any

# Anti-fake return scans
block return-rst out on $ext_if proto tcp all
block return-rst  in on $ext_if proto tcp all
block return-icmp out on $ext_if proto udp all
block return-icmp  in on $ext_if proto udp all

# Block and log outgoing packets that don't have our address as source,
# they are either spoofed or something is misconfigured. NAT disabled,
# (for instance), we want to be nice and don't send out garbage.
block out log quick on $ext_if from ! $ext_if to any

# Block nmap os detection scans  
block in quick proto tcp flags FUP/WEUAPRSF
block in quick proto tcp flags WEUAPRSF/WEUAPRSF
block in quick proto tcp flags SRAFU/WEUAPRSF
block in quick proto tcp flags /WEUAPRSF
block in quick proto tcp flags SR/SR
block in quick proto tcp flags SF/SF

# only allow ssh connections from the local network if it's from the
# trusted computer, 192.168.1.10. use "block return" so that a TCP RST is
# sent to close blocked connections right away. use "quick" so that this
# rule is not overridden by the "pass" rules below.
block return in quick on $prv_if proto tcp from ! 192.168.1.10 \
   to $prv_if port ssh
# allow ssh connections in on the external interface as long as they're
# NOT destined for the firewall (i.e., they're destined for a machine on
# the local network). log the initial packet so that we can later tell
# who is trying to connect. use the tcp syn proxy to proxy the connection.
# the default flags "S/SA" will automatically be applied to the rule by
# PF.
#pass in log on $ext_if proto tcp to ! <firewall> \
   port ssh synproxy state
#pass in on $ext_if proto tcp from any to $ext_if port ssh flags S/SA keep state (max-src-conn 1, max-src-conn-rate 3/5, overload <bruteforce> flush global)

# pass all connections originating from the firewall
pass out quick on $ext_if inet from $ext_if to any flags S/SA modulate state

# pass all connections originating from the screened LAN
pass in quick on $prv_if from $prv_hosts to any flags S/SA

# pass all connections originating from the DMZ
pass in quick on $dmz_if from $dmz_hosts to any flags S/SA
pass out quick on $dmz_if from $prv_hosts to any flags S/SA

# pass all connections to the WWW host in the DMZ
pass in quick on $ext_if inet proto tcp from any to $dmz_www port {www, https} flags S/SA synproxy state 
pass in quick on $ext_if inet proto tcp from any to $ext_if port {www, https} flags S/SA keep state 
pass out quick on $dmz_if inet proto tcp from any to $ext_if port {www, https} flags S/SA keep state
pass out quick on $dmz_if inet proto tcp from any to any port {www, https} flags S/SA keep state

#pass all connections to the SMTP host in the DMZ
pass in log on $ext_if proto tcp from any to $dmz_smtp port smtp flags S/SA synproxy state
pass in log on $prv_if proto tcp from $prv_hosts to $dmz_smtp port smtp flags S/SA synproxy state
pass out log on $dmz_if proto tcp from any to $dmz_smtp port smtp
pass in on $dmz_if proto tcp from $dmz_smtp to any port smtp
pass out log on $ext_if proto tcp from $dmz_if to any port smtp

# pass all connections to the DNS host in the DMZ
pass in on $ext_if inet proto {tcp, udp} from any to $dmz_dns port 53 flags S/SA keep state
```


----------



## kpa (Dec 9, 2011)

Sorry I don't have the time to look more closely at the rules now but one thing that jumped straight at me was this:

```
# Anti-fake return scans
block return-rst out on $ext_if proto tcp all
block return-rst  in on $ext_if proto tcp all
block return-icmp out on $ext_if proto udp all
block return-icmp  in on $ext_if proto udp all
```

Why on earth? Isn't the point of the firewall to keep the unused ports stealthed?


----------



## SirDice (Dec 9, 2011)

kpa said:
			
		

> Isn't the point of the firewall to keep the unused ports stealthed?


No, the point of a firewall is to block traffic. How you achieve that doesn't matter.

Returning a RST is actually the proper thing to do to indicate a TCP port is closed. Similarly an ICMP port unreachable should be returned for a closed UDP port.

Personally I only send a RST on TCP connections, UDP I just drop without a reply. I noticed a significant drop in the overall speed of my connection if I reply with ICMP.


----------



## bbzz (Dec 9, 2011)

kpa said:
			
		

> Sorry I don't have the time to look more closely at the rules now but one thing that jumped straight at me was this:
> 
> ```
> # Anti-fake return scans
> ...



Like SirDice said, sending back RST is what closed TCP port does. However I found out that setting up your firewall this way has some implications. 

Someone conducting a selective port scan on closed TCP/UDP ports on firewall that filters responses will not return back to scan you again.

On the other hand I've seen someone conducting scanning to come back and do a full scan of you ports once he sees closed rather than filtered ports, since its fast - RST response is immediate compared to filtered ports. In fact someone scanning a port most likely assumes that you don't have a firewall installed.

It doesn't lower you security, but I just don't see a point in doing it.


----------



## plamaiziere (Dec 9, 2011)

gpatrick said:
			
		

> In another thread, J65nko posted a snippet of code from someone else about passing traffic on the firewall, and if it was cracked they wouldn't need root, they could DOS as a non-root user.



I think you speak about this :

```
# --- OUT
# this rule allows any traffic initiated from the server.
# if the server gets cracked, the cracker doesn't have to become root to modify the pf.conf
# he just can go ahead and use the server as non-root user for attacking/DOSsing other sites 
pass out quick on $ext_if inet proto { tcp, udp, icmp } from $ext_if to any modulate state
```

That's plainly wrong. You need to be root to modify the loaded rules or to use pfctl(8). The link between PF (in the kernel) and the userland is the device /dev/pf, it is only usable by root of course.


```
# ls -la /dev/pf
crw-------  1 root  wheel    0, 174  9 dÃ©c 20:52 /dev/pf
```



> # he just can go ahead and use the server as non-root user for attacking/DOSsing other sites
> pass out quick on $ext_if inet proto { tcp, udp, icmp } from $ext_if to any modulate state



That's true, but the problem is not the rule. A good practice on a firewall is to deny all the connections from and to it and to allow only what is mandatory (for example outgoing snmp traps for monitoring and this sort of things, incoming ssh for administrative tasks, ...).

But then of course, you will have problems to update the firewall in place...

Also don't use "from $ext_if", instead use the special address "self".


> ```
> # Macros
> ext_if="axe0"
> prv_if="axe1"
> ...



You should use tables, they perform better. Do not use /32 for a host, it is useless and not very readable IMO.  



> ```
> # Options
> set optimization aggressive
> set ruleset-optimization none
> ...



Why changing these values? The defautls should be good. Change this only if you have problems. 



> ```
> # Set ttl to 254 to limit possible mapping of hosts behind firewall.
> ```


254 is to high, the default is 64. I don't believe this to be a security feature...



> ```
> ################ Filtering ##################################
> # don't filter on the loopback interface
> set skip on lo0
> ...



lo0 is skipped, so rules on it are useless.

No offense, but I think that your ruleset is a bit confused.



> ```
> pass out quick on $dmz_if inet proto tcp from any to $ext_if port {www, https} flags S/SA keep state
> ```



Looks to be a no sense. A packet leaving out via $dmz_if with a destination address on $ext_if?



> ```
> # pass all connections originating from the DMZ
> pass in quick on $dmz_if from $dmz_hosts to any flags S/SA
> ```



Why a DMZ if you don't block the traffic from the DMZ to the internal network? Looks like all is open (but may be I've missed something).

HTH, regards.


----------

