Mi filtro de paquetes de FreeBSD definitivo (pf.conf)

13 min

language: ja bn en es hi pt ru zh-cn zh-tw

Hola, soy Munou.

En esta ocasión, me gustaría presentarles el filtro de paquetes con el que he estado trasteando recientemente.

Configuración actual

Es así.
En el caso de Packet Filter,
las reglas de conversión como NAT, rdr, etc., se aplican a la primera coincidencia.
Las reglas de filtrado como pass, block, etc., se aplican a la última coincidencia.
Esa es la especificación.

En este caso, por legibilidad, he configurado todas las reglas de filtrado con quick para que se aplique la regla que coincida en ese momento.

Dejaré comentarios en lo que parezca útil conservar.

set skip on lo
set block-policy drop
set optimization conservative
set state-policy if-bound
set ruleset-optimization basic
wanint="vtnet0"
# WireGuard Configuration
wg_ifs  = "{ wg0, wg1 }"
wg_net = "10.1.0.0/24"
wg_ports="{51820}"
table <wg_clients> const { 10.1.0.2, 10.1.0.4, 10.1.0.22 }
#### -- Scrub Rules -- ####
scrub in on $wanint all random-id max-mss 1360
scrub out on $wanint all random-id max-mss 1360
scrub in all
scrub out all
#### -- NAT Rules -- ####
# WireGuard Clients NAT
nat on $wanint inet from <wg_clients> to any -> ($wanint)
block all
#### -- Fail2Ban Rules -- ####
anchor "f2b/*"
pass out quick keep state
#### -- UDP Rules -- ####
pass in quick on $wg_ifs from $wg_net to any keep state
pass in quick on $wanint proto udp from any to ($wanint) port $wg_ports keep state
# HTTP/3 Protocol
pass in quick on $wanint proto udp to ($wanint) port 443 keep state
#### -- TCP Rules -- ####
# Mail Protocols
pass in quick proto tcp from any to ($wanint) port {25, 465, 993, 995} synproxy state
# HTTP
pass in quick on $wanint proto tcp to ($wanint) port {80, 443} modulate state

set block-policy drop

Todos los paquetes que no coincidan con ninguna regla serán descartados (drop).
En el caso de return, para TCP se devuelve un RST para notificar al otro extremo y cerrar la conexión.
Para UDP, dado que no existe una conexión TCP, simplemente devuelve ICMP port unreachable.

set optimization conservative

Es una regla de optimización que en la documentación se describe como una configuración conservadora.
En el caso de aggressive, se prioriza la expiración de las conexiones, pero en entornos como WireGuard donde se desea mantener un estado de conexión constante, la conexión se cortaría simplemente por no recibir paquetes nuevos.

Además, también existe satellite, lo cual tiene su encanto.

       high-latency
             A high-latency environment (such  as  a  satellite  connec-
             tion).
       satellite
             Alias for high-latency.

set state-policy if-bound

Mantiene el state (estado), lo recuerda y permite el paso de paquetes para evitar inconsistencias.
El resultado de pfctl -ss es el state, y por ejemplo, en una configuración como la siguiente:

Cliente ←→ wg ←→ Servidor

Dado que la información sobre "en qué interfaz se creó" se asocia al state, se evitan inconsistencias al validar solo el tráfico que pasa por la misma interfaz.

En el caso de un floating normal:
Entrada: wg0
Salida: vtnet0
Retorno: tanto vtnet0 como wg0 están bien

Sin embargo, en el caso de state-policy if-bound:
Entrada: wg0
Salida: vtnet0
Retorno: debe ser obligatoriamente wg0, de lo contrario es NG

set ruleset-optimization basic

Realiza la optimización de las reglas de manera adecuada.
De la documentación oficial:

  1. remove duplicate rules

  2. remove rules that are a subset of another rule

  3. combine multiple rules into a table when advanta-geous

  4. re-order the rules to improve evaluation perfor-mance

En español:

  1. Eliminar reglas duplicadas

  2. Eliminar reglas que están contenidas en otras reglas

  3. Combinar múltiples reglas en una tabla cuando sea ventajoso

  4. Reordenar las reglas para mejorar el rendimiento de la evaluación

En resumen, incluso si escribes reglas de pf de forma descuidada, las organizará limpiamente. Es una función increíble que organiza las reglas adecuadamente, permitiéndote escribir reglas de pf que sean fáciles de leer para ti.

Definición de variables

Especificación del nombre de la interfaz WAN

wanint="vtnet0"

Definido como la dirección IPv4 asignada y como IP de salida explícita

exsrv1 = "163.44.113.145"

Definición de variables y tablas en WireGuard

wg_ifs  = "{ wg0, wg1 }"
wg_net = "10.1.0.0/24"
wg_ports="{51820}"
table <wg_clients> const { 10.1.0.2, 10.1.0.4, 10.1.0.22 }

Scrub Rules

En este caso, se especifica el valor MSS tanto para los paquetes que entran desde la WAN como para los que salen.
En cuanto a all random-id, parece ser para evitar que el propio SO sea identificado a través de los paquetes, pero según he comprobado con sudo tcpdump -n -v -i $interface, en GNU/Linux parece estar habilitado por defecto.

04:15:18.538048 IP (tos 0x0, ttl 52, id 61686, offset 0, flags [DF], proto TCP (6), length 52)
04:15:18.538049 IP (tos 0x0, ttl 52, id 61687, offset 0, flags [DF], proto TCP (6), length 131)
04:15:18.538179 IP (tos 0x0, ttl 64, id 65430, offset 0, flags [DF], proto TCP (6), length 52)
04:15:18.538223 IP (tos 0x0, ttl 52, id 61688, offset 0, flags [DF], proto TCP (6), length 131)

También se configuran in all y out all para realizar scrub en interfaces distintas a la WAN.

scrub in on $wanint all random-id max-mss 1360
scrub out on $wanint all random-id max-mss 1360
scrub in all
scrub out all

Reglas NAT

Ciertos clientes de WireGuard salen a través del NAT de la interfaz WAN del lado del servidor WireGuard.
Sin embargo, en este caso solo salen por IPv4. Esto se debe a que el servidor WireGuard no distribuye direcciones IPv6, por lo que solo se utiliza inet from.

nat on $wanint inet from <wg_clients> to any -> ($wanint)

Reglas de Fail2Ban

Regla para que Fail2Ban añada la IP de origen del ataque a una tabla de pf y la bloquee. Al colocarla al final, los paquetes que no coinciden con ninguna regla se dirigen a través de anchor "f2b/*" a las reglas de Fail2Ban para las IPs añadidas a la tabla de pf.
Se coloca arriba porque debe coincidir antes que las reglas de filtrado marcadas como quick.
Dado que justo antes está la regla por defecto block all, todos los paquetes que no coincidan aquí serán bloqueados (block).

/usr/local/etc/fail2ban/action.d/pf.conf:

# Option: block
#
# The action you want pf to take.
# Probably, you want "block quick", but adjust as needed.
# If you want to log all blocked use "blog log quick"
block = block quick

Debido a esta configuración, se añaden las reglas de Fail2ban con block quick, por lo que se colocan en una posición superior.

Es decir, primero se dirigen los paquetes de las IPs añadidas a la tabla de pf a las reglas de Fail2Ban mediante anchor "f2b/*", y luego:

anchor "f2b/*"

Como regla de filtrado, se coloca primero la regla que permite los paquetes de salida.

pass out quick keep state

Reglas UDP

Reglas de permiso para el servidor WireGuard.
Se permite 51820/udp y se permiten todas las NIC virtuales de WireGuard en $wg_ifs.
¿Quizás debería ser más estricto aquí por si acaso?

pass in quick on $wg_ifs from $wg_net to any keep state
pass in quick on $wanint proto udp from any to ($wanint) port $wg_ports keep state

En este caso, como ya se mantiene el estado (state), se permitirán todos los paquetes que pasen por el túnel de WireGuard. Si el orden fuera inverso, la regla pass in quick on $wg_ifs from $wg_net to any keep state no funcionaría, ya que asume que se trata del túnel de WireGuard.

Se permite 443/udp para habilitar HTTP/3.

# HTTP/3 Protocol
pass in quick on $wanint proto udp to ($wanint) port 443 keep state

Reglas TCP

Como sabrá cualquiera que haya usado nmap o herramientas similares, el escaneo de puertos en TCP se realiza de forma muy rápida, por lo que cambiar el puerto no impedirá que un atacante lo encuentre pronto. En el caso de los puertos UDP, el escaneo con nmap toma mucho tiempo, por lo que para mayor seguridad, el acceso a SSH solo se permite a través del túnel de WireGuard. En esencia, creo que la configuración más segura es una en la que TCP esté envuelto en UDP.
Dado que se ha implementado Fail2ban, si hay un ataque a SSH, la IP de origen se añade a la tabla de pf para ser bloqueada. Esto actúa prácticamente como un honeypot.

Para los protocolos de correo, se habilita synproxy para prevenir ataques SYN Flood.
En cuanto a HTTP, se especifica la IP de destino con ($wanint) para que coincida dinámicamente con la dirección IP asignada a la interfaz WAN. No se especifica explícitamente inet para permitir tanto IPv4 como IPv6.

Los paquetes que llegan por IPv6 coincidirán dinámicamente con la dirección IPv6 asignada a la interfaz WAN, ya que se especifica la IP de destino con ($wanint).

# Mail Protocols
pass in quick proto tcp from any to ($wanint) port {25, 465, 993, 995} synproxy state
# HTTP
pass in quick on $wanint proto tcp to ($wanint) port {80, 443} modulate state

En fin, esto se alargó...

Related Posts