Packet Filer o PF es un sistema de filtrado de tráfico TCP/IP y NAT, PF también es capaz de normalizar y condicionar tráfico TCP/IP además de proveer mecanismos de control de ancho de banda y priorización de paquetes. PF forma parte de FreeBSD desde la versión 5.3, a lo largo de los años las versiones de OpenBSD y FreeBSD han divergido de forma considerable por lo que algunas funcionalidades funcionarán de forma distinta según el sistema subyacente.
- Instalación
- Comandos útiles
- Aspectos importantes
- Reglas básicas
- Listas
- Macros
- Quick
- Respuesta tráfico descartado
- Scrub
- AntiSpoof
- Tablas
- Anchors
- Log
- PFTop
- Mi script servidor Bastille
Instalación
Para poder arrancar PF debemos crear un fichero de reglas, inicialmente puede estar vacío ya que solo lo generamos para comprobar que PF arranque:
chmod 600 /etc/pf.conf
Habilitamos PF en el arranque:
sysrc pf_rules="/etc/pf.conf"
PF nos permite logear tráfico para ser visualizado en tiempo real mediante un analizador de tráfico en la interfaz pflog0 o tener un registro en un fichero de texto.
The pflog interface is a device which makes visible all packets logged by the packet filter, pf(4).
Logged packets can easily be monitored in realtime by invoking tcpdump(1) on the pflog interface, or stored to disk using pflogd(8).
Habilitamos PF-LOG en el arranque y configuramos el fichero de logs:
sysrc pflog_logfile="/var/log/pflog"
Si el equipo va a actuar como router o NATear tráfico, debemos habilitar el forwarding a nivel de sysctl:
sysctl net.inet6.ip6.forwarding=1
Para que la configuración persista debemos modificar los siguientes parámetros RC:
sysrc ipv6_gateway_enable=YES
Arrancamos los dos servicios:
service pflog start
Al arrancar el servicio pflog veremos como se ha generado una interfaz de red adicional:
pflog0: flags=141<UP,RUNNING,PROMISC> metric 0 mtu 33160
groups: pflog
PF viene con bastantes ejemplos de configuración:
total 62
drwxr-xr-x 2 root wheel 12 May 12 2022 .
drwxr-xr-x 41 root wheel 41 May 12 2022 ..
-r--r--r-- 1 root wheel 1245 May 12 2022 ackpri
-r--r--r-- 1 root wheel 937 May 12 2022 faq-example1
-r--r--r-- 1 root wheel 3141 May 12 2022 faq-example2
-r--r--r-- 1 root wheel 4723 May 12 2022 faq-example3
-r--r--r-- 1 root wheel 1074 May 12 2022 pf.conf
-r--r--r-- 1 root wheel 860 May 12 2022 queue1
-r--r--r-- 1 root wheel 1122 May 12 2022 queue2
-r--r--r-- 1 root wheel 492 May 12 2022 queue3
-r--r--r-- 1 root wheel 912 May 12 2022 queue4
-r--r--r-- 1 root wheel 268 May 12 2022 spamd
Comandos útiles
Una vez arrancado PF este puede ser administrado mediante el comando pfctl, los comandos mas utilizados son:
Comando | Descripción |
---|---|
pfctl -e | Habilita PF |
pfctl -d | Deshabilita PF |
pfctl -f /etc/pf.conf | Recarga configuración |
pfctl -nf /etc/pf.conf | Comprueba configuración sin cargarla |
pfctl -nvf /etc/pf.conf | Comprueba configuración sin cargarla, además muestra el parseo de las reglas |
pfctl -F all -f /etc/pf.conf | Flushea todas las reglas de NAT, filter, state, table y recarga la configuración |
pfctl -s rules/nat/states | Muestra las reglas de las tablas filtrado/NAT/estado |
pfctl -sa | Muestra toda la información posible |
Aspectos importantes
Algunos aspectos importantes a tener en cuenta cuando utilizamos PF son:
- Cuando una regla machea NO se para el procesamiento de reglas a no ser que se utilice quick .
- La regla que se aplicará siempre será la última en machear.
- Esto equivale a leer las reglas de abajo hacia arriba.
- Todas las reglas son statefull.
Reglas básicas
La sintaxis de las reglas es la siguiente:
action [direction] [log] [quick] [on interface] [af] [proto protocol]
[from src_addr [port src_port]] [to dst_addr [port dst_port]]
[flags tcp_flags] [state]
Como ejemplo básico vamos a configurar unas reglas que denieguen el tráfico entrante a excepción del puerto 22 y que permitan conexiones salientes.
block in all
pass in proto tcp to any port 22
pass out all
Recargamos la configuración:
Listas
PF soporta el uso de listas lo que nos facilitará mucho la escritura de reglas:
block all
pass out proto tcp to any port { 80, 443 }
pass in proto tcp to any port 22
Macros
PF también soporta el uso de macros, realmente útiles:
ext_if = "em0"
tcp_services = "{ ssh, smtp, domain, http, pop3, auth, pop3s }"
udp_services = "{ domain }"
block all
pass out proto tcp to any port $tcp_services
pass proto udp to any port $udp_services
pass in proto tcp to any port 22
Podemos ver como se permite el tráfico HTTP:
HTTP/1.1 302 Found
content-length: 0
location: https://alfaexploit.com/
cache-control: no-cache
Pero no el HTTPS:
curl: (7) Couldn't connect to server
Quick
Como ya hemos comentado, por defecto la regla que se aplica siempre es la última en machear, para evitar este comportamiento y hacer que se interrumpa el procesamiento de las reglas al hacer match, debemos utilizar la función quick.
Pongamos este ejemplo de reglas con una conexión ssh saliente desde el equipo en cuestión:
ext_if = "em0"
tcp_services = "{ ssh, smtp, domain, http, pop3, auth, pop3s }"
udp_services = "{ domain }"
block all
pass out proto tcp to any port $tcp_services
pass out proto tcp to any
pass proto udp to any port $udp_services
pass in proto tcp to any port 22
Las reglas que machean el tráfico son:
block all
pass out proto tcp to any port $tcp_services
pass out proto tcp to any
Pero solo se aplicará la tercera ya que es la última en machear:
pass out proto tcp to any
Este comportamiento puede ser evitado si añadimos la función quick, de este modo cuando una regla machee se parará el procesamiento de reglas y el tráfico será tratado acorde a dicha regla.
Pongamos el mismo ejemplo anterior pero esta vez utilizando quick:
ext_if = "em0"
tcp_services = "{ ssh, smtp, domain, http, pop3, auth, pop3s }"
udp_services = "{ domain }"
block all
pass out quick proto tcp to any port $tcp_services
pass out proto tcp to any
pass proto udp to any port $udp_services
pass in proto tcp to any port 22
Siguen macheando tres reglas:
block all
pass out quick proto tcp to any port $tcp_services
pass out proto tcp to any
Pero al llegar a la segunda se parará el procesamiento de reglas y se aplicará dicha regla:
pass out quick proto tcp to any port $tcp_services
Respuesta tráfico descartado
Se puede definir como responder ante el tráfico descartado, hay dos opciones:
- Drop(default): Se descarta el tráfico sin mas.
- Return: Se descarta y además se le envía al origen un mensaje de status code.
set block-policy return
Scrub
Existen ciertos ataques que aprovechan la fragmentación de paquetes para evadir reglas de filtrado o sistemas de detección de ataques, mediante PF podremos bloquear dichos ataques utilizando la función scrub.
Scrub reensamblará los fragmentos asegurándose así de que toda la información del paquete TCP sea correcta.
La manera mas sencilla de utilizar scrub es esta:
scrub in all
Pero scrub permite hacer mas ajustes como por ejemplo cambiar flags TCP, en este caso elimina el bit “do not fragment” e impone un tamaño máximo de segmente de 1440 bytes:
scrub in all fragment reassemble no-df max-mss 1440
Antispoof
Una buena práctica es denegar el tráfico cuyo origen no coincida con la interfaz de red conectada a dicha red:
martians = "{ 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 }"
block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians
Una manera de hacer esto automáticamente es mediante la función antispoof, pero antes debemos skipear el tráfico loopback ya que se ve afectado por antispoof:
set skip on lo
antispoof for $ext_if inet
Tablas
Las tablas son listas que pueden ser modificadas sin necesidad de recargar el set de reglas, además estas pueden ser consultadas de forma muy rápida.
Tablas dinámicas:
Su contenido puede ser modificado.
table <spammers> { 192.168.2.0/24, !192.168.2.5 }
block all
pass in proto tcp to any port 25
block in proto tcp from <spammers> to any port 25
pass in proto tcp to any port 22
Podemos consultar la tabla:
192.168.2.0/24
!192.168.2.5
Una peculiaridad de las tablas es que si no son referenciadas por alguna regla, no se crean:
table <spammers> { 192.168.2.0/24, !192.168.2.5 }
block all
pass in proto tcp to any port 25
#block in proto tcp from <spammers> to any port 25
pass in proto tcp to any port 22
Borramos las tablas y recargamos la configuración:
Intentamos consultar la tabla:
pfctl: Table does not exist.
Si queremos que la tabla se cree incluso sin existir referencias a ella debemos indicarlo:
table <spammers> persist { 192.168.2.0/24, !192.168.2.5 }
block all
pass in proto tcp to any port 25
#block in proto tcp from <spammers> to any port 25
pass in proto tcp to any port 22
Borramos las tablas y recargamos la configuración:
Podemos consultar la tabla:
192.168.2.0/24
!192.168.2.5
Una tabla dinámica puede ser manipulada en real-time mediante pfctl:
1/1 addresses added.
192.168.2.0/24
!192.168.2.5
192.168.3.4
Las modificaciones realizadas sobre una tabla no persisten al reinicio del equipo, una manera de hacer que persistan es cargándolas desde un fichero de texto y crontabeando un volcado regular de estas al fichero en cuestión.
192.168.2.0/24
!192.168.2.5
Modificamos el script de reglas del siguiente modo:
table <spammers> file "/etc/spammers"
block all
pass in proto tcp to any port 25
block in proto tcp from <spammers> to any port 25
pass in proto tcp to any port 22
Borramos las tablas y recargamos la configuración:
Añadimos una entrada a la tabla:
Volcamos manualmente el contenido de la tabla al fichero, este comando es el que deberíamos de crontabear:
Finalmente recargamos la configuración:
Y efectivamente el contenido de la tabla es correcto tras el reinicio:
192.168.2.0/24
!192.168.2.5
192.168.3.4
Tablas estáticas:
Su contenido no puede ser modificado.
table <spammers> const { 192.168.2.0/24, !192.168.2.5 }
block all
pass in proto tcp to any port 25
block in proto tcp from <spammers> to any port 25
pass in proto tcp to any port 22
Si intentamos añadir una entrada mostrará el siguiente error:
pfctl: Operation not permitted.
Manipulación de tablas
Añadir entradas:
Mostrar contenido de la tabla:
Eliminar contenido de la tabla:
Eliminar todas las entradas de una tabla:
Eliminar la tabla en si misma:
Recargar el contenido al vuelo:
Se puede hacer limpieza de las tablas eliminando las entradas que no han sido referenciadas en los últimos X segundos.
Anchors
Los anchors son un conjunto de reglas, tablas u otros anchors a los que se les ha asignado un nombre, se podría decir que son el equivalente a un include de código, la ventaja de los anchors es que pueden ser manipuladas en tiempo real mediante pfctl, además pueden ser anidados lo que proporciona una gran flexibilidad.
Los anchors pueden incluirse de manera que solo incluyan cierto tipo de reglas:
- anchor name: Incluye las reglas del anchor.
- binat-anchor name: Incluye solo las reglas bitnat del anchor.
- nat-anchor name: Incluye solo las reglas nat del anchor.
- rdr-anchor name: Incluye solo las reglas rdr del anchor.
Los anchors pueden ser poblados de distintas maneras.
Desde un fichero de texto
pass in proto tcp from 192.168.69.0/24 to any port 25
En este script se bloquea todo el tráfico por defecto pero se permite el tráfico al puerto 25 a través de un anchor:
block all
anchor goodguys
load anchor goodguys from "/etc/anchor-goodguys"
pass in proto tcp to any port 22
Podemos ver el contenido de los anchors:
pass in inet proto tcp from 192.168.69.0/24 to any port = smtp flags S/SA keep state
Es posible editar el fichero y recargar el contenido de nuevo:
pass in proto tcp from 192.168.69.0/24 to any port 25
pass in proto tcp from 192.168.2.0/24 to any port 25
Recargamos el contenido:
Podemos ver el contenido de los anchors:
pass in inet proto tcp from 192.168.69.0/24 to any port = smtp flags S/SA keep state
pass in inet proto tcp from 192.168.2.0/24 to any port = smtp flags S/SA keep state
Mediante pfctl
Simplemente definimos el anchor en el script de filtrado:
block all
anchor goodguys
pass in proto tcp to any port 22
Borramos las reglas anteriores:
Lo poblamos con el siguiente comando:
Podemos ver el contenido de los anchors:
pass in inet proto tcp from 192.168.69.0/24 to any port = smtp flags S/SA keep state
Inline dentro del script de reglas
Tan solo debemos escribir las reglas dentro de {}, el nombre del anchor es opcional:
block all
anchor "goodguys" {
pass in proto tcp from 192.168.69.0/24 to port 25
}
pass in proto tcp to any port 22
Podemos ver el contenido de los anchors:
pass in inet proto tcp from 192.168.69.0/24 to any port = smtp flags S/SA keep state
Los anchors pueden ser anidados:
anchor "goodguys" {
anchor "client1" {
pass in proto tcp from 192.168.69.4 to port 25
}
anchor "client2" {
pass in proto tcp from 192.168.69.5 to port 25
}
}
Cargamos el contenido del anchor:
Podemos ver el contenido de los anchors:
anchor "goodguys" all {
anchor "client1" all {
pass in inet proto tcp from 192.168.69.4 to any port = smtp flags S/SA keep state
}
anchor "client2" all {
pass in inet proto tcp from 192.168.69.5 to any port = smtp flags S/SA keep state
}
}
Ahora incluimos todos los anchors de segundo nivel de goodguys, estos serán evaluados en orden alfabético:
block all
anchor "goodguys/*"
pass in proto tcp to any port 22
NOTA: Hay que tener en cuenta que no se descenderá recursivamente, solo se evaluarán los anchors de segundo de nivel, si hubiese anchors de tercer nivel estos serían ignorados.
Mostrar las reglas de un anchor:
Borrar las reglas de un anchor:
LOG
Mediante la opción log podremos generar entradas en el fichero de log o verlas en tiempo real en la interfaz log0.
El script de reglas será este, generamos logs del tráfico rechazado y del destinado al puerto 25:
block log all
pass in log proto tcp to any port 25
pass in proto tcp to any port 22
Podemos consultar los logs accediendo al fichero, pero debemos de tener en cuenta que el volcado no es real time, si queremos verlo en tiempo real tendremos que consultar directamente la interfaz log0:
00:00:00.000000 rule 1/0(match): pass in on em0: 192.168.69.4.64484 > 192.168.69.55.25: Flags [S], seq 4073334594, win 65535, options [mss 1460,[|tcp]>
O directamente en la interfaz de red:
00:00:00.000000 rule 1/0(match): pass in on em0: 192.168.69.4.19198 > 192.168.69.55.25: Flags [S], seq 494470215, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 4053058779 ecr 0], length 0
Log soporta varios opciones interesantes:
- all: Por defecto solo se logea el primer paquete, con all logearemos los paquetes subsecuentes de dicha conexión.
- to pflogN: Cambia la interfaz en la que logear las entradas.
- user: También se logea el UID/GID del socket que generó o recibió la conexión.
En este caso vamos a guardar el tráfico bloqueado y los paquetes relacionados con la conexión al puerto 25 y el UID/GID del socket:
block log all
pass in log (all, user) proto tcp to any port 25
pass in proto tcp to any port 22
00:00:00.000000 rule 1/0(match): pass in on em0: 192.168.69.4.35085 > 192.168.69.55.25: Flags [S], seq 702812990, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 1667456513 ecr 0], length 0
00:00:02.057478 rule 1/0(match): pass in on em0: 192.168.69.4.35096 > 192.168.69.55.25: Flags [P.], seq 1:5, ack 1, win 1027, options [nop,nop,TS val 1978886000 ecr 1634216891], length 4: SMTP: asd
También podemos filtrar las entradas del fichero de log:
Tcpdump soporta la salida de pflogd por lo que podremos realizar filtrados mas avanzados:
ip - address family is IPv4.
ip6 - address family is IPv6.
on int - packet passed through the interface int.
ifname int - same as on int.
ruleset name - the ruleset/anchor that the packet was matched in.
rulenum num - the filter rule that the packet matched was rule number num.
action act - the action taken on the packet. Possible actions are pass and block.
reason res - the reason that action was taken. Possible reasons are match, bad-offset, fragment, short, normalize, memory, bad-timestamp, congestion, ip-option, proto-cksum, state-mismatch, state-insert, state-limit, src-limit and synproxy.
inbound - packet was inbound.
outbound - packet was outbound.
En este ejemplo veremos el tráfico denegado de salida:
00:00:00.000000 rule 0/0(match) [uid 0]: block out on em0: 192.168.69.55.33512 > 192.168.69.4.443: Flags [S], seq 26421961, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 1046077323 ecr 0], length 0
PFTop
Una herramienta muy útil para ver las conexiones y el ancho de banda consumido por cada una de ellas es pftop.
pfTop: Up State 1-33/33, View: default, Order: none, Cache: 10000 10:40:51
PR DIR SRC DEST STATE AGE EXP PKTS BYTES
udp Out 192.168.69.4:29155 142.250.200.138:443 MULTIPLE:MULTIPLE 00:52:04 00:00:59 1078 108109
tcp Out 192.168.69.4:37191 192.168.69.2:22 ESTABLISHED:ESTABLISHED 00:52:07 23:59:56 67 11968
tcp Out 192.168.69.4:41701 192.168.69.203:8009 ESTABLISHED:ESTABLISHED 00:52:05 23:59:55 1888 242722
tcp Out 192.168.69.4:60886 192.168.69.198:8009 ESTABLISHED:ESTABLISHED 00:52:07 23:59:59 1892 244049
tcp Out 192.168.69.4:55729 149.154.167.92:443 ESTABLISHED:ESTABLISHED 00:51:34 23:59:25 307 52375
tcp Out 192.168.69.4:19092 157.90.179.245:443 ESTABLISHED:ESTABLISHED 00:44:00 23:59:31 183 29954
tcp Out 192.168.69.4:32448 198.252.206.25:443 ESTABLISHED:ESTABLISHED 00:41:37 23:59:15 146 15433
tcp Out 192.168.69.4:31612 140.82.114.26:443 ESTABLISHED:ESTABLISHED 00:11:28 23:59:32 71 10235
tcp Out 192.168.69.4:30968 140.82.114.26:443 ESTABLISHED:ESTABLISHED 00:10:32 23:59:29 68 13000
tcp Out 192.168.69.4:48598 192.168.69.19:443 FIN_WAIT_2:FIN_WAIT_2 00:01:16 00:00:50 15 4171
udp Out 192.168.69.4:65471 142.250.184.4:443 MULTIPLE:MULTIPLE 00:00:43 00:00:19 115 56281
tcp Out 192.168.69.4:48582 216.58.209.74:443 ESTABLISHED:ESTABLISHED 00:02:42 23:59:33 26 5924
tcp Out 192.168.69.4:65216 199.71.0.46:43 FIN_WAIT_2:FIN_WAIT_2 00:00:54 00:00:36 12 2893
tcp Out 192.168.69.4:65226 88.26.176.27:443 FIN_WAIT_2:FIN_WAIT_2 00:00:11 00:01:24 22 9094
tcp Out 192.168.69.4:65225 192.168.69.19:443 ESTABLISHED:ESTABLISHED 00:00:16 23:59:44 16 9265
tcp Out 192.168.69.4:65220 88.26.176.27:443 FIN_WAIT_2:FIN_WAIT_2 00:00:41 00:00:54 22 9094
tcp Out 192.168.69.4:48586 192.168.69.19:443 FIN_WAIT_2:FIN_WAIT_2 00:02:16 00:00:14 15 3788
tcp Out 192.168.69.4:48587 192.168.69.19:443 FIN_WAIT_2:FIN_WAIT_2 00:02:16 00:00:14 17 5757
udp Out 192.168.69.4:54755 142.250.201.78:443 MULTIPLE:MULTIPLE 00:00:54 00:00:06 18 6855
tcp Out 192.168.69.4:65221 142.250.200.138:443 TIME_WAIT:TIME_WAIT 00:00:40 00:00:51 11 1038
tcp Out 192.168.69.4:41841 162.159.138.60:443 ESTABLISHED:ESTABLISHED 00:01:02 23:59:43 17 6580
tcp Out 192.168.69.4:65222 142.250.200.138:443 TIME_WAIT:TIME_WAIT 00:00:40 00:00:50 9 920
tcp Out 192.168.69.4:48599 88.26.176.27:443 FIN_WAIT_2:FIN_WAIT_2 00:01:11 00:00:24 22 9094
tcp Out 192.168.69.4:65224 142.250.200.138:443 TIME_WAIT:TIME_WAIT 00:00:39 00:00:51 9 946
tcp Out 192.168.69.4:65218 88.26.176.27:443 FIN_WAIT_2:FIN_WAIT_2 00:00:41 00:00:54 24 8964
tcp Out 192.168.69.4:65219 88.26.176.27:443 FIN_WAIT_2:FIN_WAIT_2 00:00:41 00:00:54 21 8156
tcp Out 192.168.69.4:65511 192.0.47.59:43 FIN_WAIT_2:FIN_WAIT_2 00:00:55 00:00:36 10 864
tcp Out 192.168.69.4:65223 142.250.200.138:443 TIME_WAIT:TIME_WAIT 00:00:39 00:00:51 9 946
udp Out 192.168.69.4:36386 8.8.8.8:53 MULTIPLE:SINGLE 00:00:01 00:00:29 2 170
tcp Out 192.168.69.4:65217 172.217.168.170:443 FIN_WAIT_2:FIN_WAIT_2 00:00:52 00:00:39 28 15504
udp Out 192.168.69.4:61727 142.250.200.74:443 MULTIPLE:MULTIPLE 00:00:01 00:00:59 18 5518
tcp Out 192.168.69.4:48589 162.159.130.234:443 ESTABLISHED:ESTABLISHED 00:01:56 23:59:56 46 31599
udp Out 192.168.69.4:64629 142.250.200.74:443 MULTIPLE:MULTIPLE 00:00:01 00:01:00 17 6942
Mi script servidor Bastille
Como nota final dejo mi script de filtrado en un servidor Bastille:
ext_if = "nfe0"
set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
table <badguys> persist
table <jails> persist
nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"
antispoof for $ext_if inet
block log all
pass out quick
block in quick from <badguys>
pass in proto tcp to 192.168.69.2 port 7777
# SMTP -> HellStorm
pass in proto tcp to 192.168.69.17 port 25
# HTTP/HTTPS -> Atlas
pass in proto tcp to 192.168.69.19 port 80
pass in proto tcp to 192.168.69.19 port 443
# Xbox -> Paradox
pass in proto tcp from 192.168.69.196 to 192.168.69.18 port 80
# TARS -> Paradox
pass in proto tcp from 192.168.69.198 to 192.168.69.18 port 80
pass in proto tcp to any port 22
Crontabeado tengo el comando de limpieza que elimina las entradas que no han sido referenciadas en las últimas 24h:
# 24h or reboot: Banned ips
00 11 * * * pfctl -t badguys -T expire 86400