O meu filtro de pacotes FreeBSD definitivo (pf.conf)
Olá, eu sou o Munou.
Desta vez, gostaria de apresentar as configurações de filtro de pacotes com as quais andei mexendo recentemente.
Configuração Atual
Está assim.
No caso do Packet Filter (PF):
Regras de conversão como NAT, rdr, etc., são aplicadas na primeira correspondência (first-match).
Regras de filtragem como pass, block, etc., são aplicadas na última correspondência (last-match).
Essa é a especificação.
Neste caso, por uma questão de legibilidade, configurei todas as regras de filtragem como quick para que a regra correspondente naquele momento seja aplicada imediatamente.
No entanto, quanto às regras de block do fail2ban que desejo registrar em log de forma abrangente, decidi colocá-las no final para garantir o registro sem omissões.
Vou deixar comentários onde for possível.
set skip on lo
set block-policy drop
set optimization conservative
set state-policy if-bound
set ruleset-optimization basic
wanint="vtnet0"
# Configuração do 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 }
#### -- Regras de Scrub -- ####
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
#### -- Regras de NAT -- ####
# NAT para Clientes WireGuard
nat on $wanint inet from <wg_clients> to any -> ($wanint)
block all
#### -- Regras do Fail2Ban -- ####
anchor "f2b/*"
pass out quick keep state
#### -- Regras UDP -- ####
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
# Protocolo HTTP/3
pass in quick on $wanint proto udp to ($wanint) port 443 keep state
#### -- Regras TCP -- ####
# Protocolos de E-mail
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 os pacotes que não corresponderem a nenhuma regra serão descartados (drop).
No caso de return, se for TCP, ele retorna um RST para notificar a outra parte e fechar a conexão.
No caso do UDP, como obviamente não existe uma conexão TCP, ele apenas retorna um ICMP port unreachable.
set optimization conservative
Esta é uma regra de otimização que, na documentação, é considerada uma configuração conservadora.
No caso de aggressive, as conexões expiram prioritariamente, mas em ambientes como o WireGuard, onde se deseja manter um estado de conexão constante, a conexão seria cortada apenas por não haver novos pacotes chegando.
Além disso, existe até algo como satellite, o que é bem interessante.
high-latency A high-latency environment (such as a satellite connec- tion). satellite Alias for high-latency.
set state-policy if-bound
Mantém o state (estado) e o memoriza para permitir a passagem de pacotes, evitando assim inconsistências.
O resultado de pfctl -ss é o state. Por exemplo, na seguinte configuração:
Cliente ←→ wg ←→ Servidor
Como a informação de "em qual interface foi criado" está vinculada ao state, a inconsistência é evitada ao validar apenas a comunicação que passa pela mesma interface.
No caso do floating normal:
Entrada: wg0
Saída: vtnet0
Retorno: OK tanto por vtnet0 quanto por wg0
No entanto, no caso de state-policy if-bound:
Entrada: wg0
Saída: vtnet0
Retorno: Deve ser obrigatoriamente wg0, caso contrário, falha (NG)
set ruleset-optimization basic
Realiza a otimização das regras de forma adequada.
Da documentação oficial:
remove duplicate rules
remove rules that are a subset of another rule
combine multiple rules into a table when advanta-geous
re-order the rules to improve evaluation perfor-mance
Em português:
Remover regras duplicadas
Remover regras que são um subconjunto de outras regras
Combinar múltiplas regras em uma tabela quando vantajoso
Reordenar as regras para melhorar o desempenho da avaliação
Em outras palavras, ele organiza de forma limpa até mesmo regras de pf escritas de qualquer jeito. É uma funcionalidade fantástica que organiza as regras adequadamente, permitindo que você as escreva de uma forma que seja fácil de ler para você.
Definição de Variáveis
Especificação do nome da interface WAN
wanint="vtnet0"
Definido como o endereço IPv4 atribuído e um IP de saída explícito
exsrv1 = "163.44.113.145"
Definição de variáveis e tabelas no 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 }
Regras de Scrub
Neste caso, o valor MSS é especificado tanto para pacotes vindos da WAN quanto para os que saem.
Quanto ao all ramdom-id, parece ser para evitar que o próprio SO seja identificado a partir dos pacotes, mas, pelo que verifiquei com sudo tcpdump -n -v -i $interface, no caso do GNU/Linux, parece estar ativado por padrão.
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)
Além disso, configurei in all e out all para que o scrub também seja feito em outras interfaces além da 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
Regras de NAT
Certos clientes do WireGuard saem através do NAT da interface WAN do lado do servidor WireGuard.
No entanto, neste caso, a saída é apenas por IPv4. Como o lado do servidor WireGuard não está distribuindo endereços IPv6, utilizei apenas inet from.
nat on $wanint inet from <wg_clients> to any -> ($wanint)
Regras do Fail2Ban
Regras para fazer o Fail2Ban adicionar o IP de origem do ataque a uma tabela do pf e bloqueá-lo. Coloquei o anchor "f2b/*" no final para direcionar os pacotes de IPs adicionados à tabela do pf que não corresponderam a nenhuma regra para as regras do Fail2Ban.
Coloquei-as acima porque precisam corresponder antes das regras de filtragem que usam quick.
Como a regra padrão block all está logo antes disso, todos os pacotes que não corresponderem aqui serão bloqueados (block).
/usr/local/etc/fail2ban/action.d/pf.conf:
# Opção: block
#
# A ação que você deseja que o pf execute.
# Provavelmente, você vai querer "block quick", mas ajuste conforme necessário.
# Se desejar registrar todos os bloqueios, use "blog log quick"
block = block quick
Como as regras do Fail2ban com block quick são adicionadas a partir desta configuração, eu as coloquei em uma posição superior.
Ou seja, primeiro direciono os pacotes de IPs adicionados à tabela do pf para as regras do Fail2Ban via anchor "f2b/*" e, em seguida:
anchor "f2b/*"
Como regra de filtragem, defini primeiro a regra que permite a saída de pacotes.
pass out quick keep state
Regras UDP
Regras de permissão para o servidor WireGuard.
Permite a porta 51820/udp e todas as interfaces virtuais (NICs) do WireGuard em $wg_ifs.
Talvez devesse ser mais rigoroso aqui para o caso de algum imprevisto?
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
Neste caso, como o estado (state) já está sendo mantido, todos os pacotes que vêm pelo túnel WireGuard serão permitidos. Se esta ordem fosse invertida, a regra pass in quick on $wg_ifs from $wg_net to any keep state não funcionaria, pois ela pressupõe a existência do túnel WireGuard.
Permite 443/udp para habilitar o HTTP/3.
# Protocolo HTTP/3
pass in quick on $wanint proto udp to ($wanint) port 443 keep state
Regras TCP
Como quem já usou ferramentas como o nmap deve saber, os escaneamentos de porta em TCP são realizados de forma extremamente rápida, então apenas mudar a porta não impedirá que um atacante a encontre rapidamente. No caso de portas UDP, o escaneamento do nmap leva muito tempo, então, para tornar o sistema mais seguro, configurei o acesso ao SSH apenas através do túnel WireGuard. Basicamente, acredito que a configuração mais segura seja, na prática, encapsular o TCP em UDP.
Como implementei o Fail2ban, se houver um ataque ao SSH, o IP de origem será adicionado à tabela do pf e bloqueado. Isso funciona praticamente como um honeypot.
Para os protocolos de e-mail, ativei o synproxy para prevenir ataques de SYN Flood.
No caso do HTTP, especificar o IP de destino com ($wanint) serve para corresponder dinamicamente ao endereço IP atribuído à interface WAN. Não especifiquei explicitamente inet para permitir tanto IPv4 quanto IPv6.
Para pacotes que chegam via IPv6, como o IP de destino é especificado por ($wanint), eles corresponderão dinamicamente ao endereço IPv6 atribuído à interface WAN.
# Protocolos de E-mail
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
Enfim, ficou longo...