[OpenBSD]

[Anterior: Tabelas] [Conteúdo] [Próximo: Tradução do Endereço de Rede (NAT)]

PF: Filtragem de Pacotes


Conteúdo


Introdução

Filtragem de pacotes é o bloqueio ou liberação de maneira seletiva da passagem de pacotes de dados, conforme eles atravessam uma interface de rede. O critério que o pf(4) usa quando inspeciona pacotes é baseado na Camada 3 (IPv4 e IPv6) e na Camada 4 (TCP, UDP, ICMP e ICMPv6). Os critérios mais usados são os endereços de origem e destino, porta de origem e destino, e protocolo.

Regras de filtragem especificam o critério em que o pacote deve se enquadrar e a ação resultante que é tomada quando o pacote corresponde à regra, que pode ser o bloqueio ou a liberação. As regras de filtragem são avaliadas em sequência, da primeira até a última. A menos que o pacote corresponda a uma regra contendo a palavra-chave quick, ele é avaliado por todas as regras de filtragem antes da ação final ser tomada. A última regra a corresponder é a "vencedora" e dita qual ação tomar. Existe um pass all implícito no início de um conjunto de regras de filtragem, que significa que caso o pacote não corresponda a nenhuma regra a ação resultante será pass.

Sintaxe das Regras

A forma geral altamente simplificada da sintaxe para regras de filtragem é:
ação [direção] [log] [quick] [on interface] [fam_de_end] [proto protocolo] \
   [from end_de_or [port porta_de_or]] [to end_de_dest [port porta_de_dest]] \
   [flags sinalizadores_tcp] [estado]
ação
A ação executada em pacotes que correspondem à regra; pode ser pass ou block. A ação pass libera a passagem do pacote para posterior processamento pelo kernel, enquanto a ação block reage com base na definição da opção block-policy. A reação padrão pode ser sobrescrita especificando block drop ou block return na regra.
direção
A direção em que o pacote está se movendo em uma interface, pode ser in (entrando) ou out (saindo).
log
Especifica que o pacote deve ser registrado via pflogd(8). Se a regra cria um estado, somente o pacote que estabeleceu o estado é registrado. Para registrar todos pacotes, use log (all).
quick
Se um pacote corresponde a uma regra especificada com quick, então essa regra é considerada final e a ação especificada é executada.
interface
O nome ou grupo da interface de rede onde o pacote está passando. Interfaces podem ser adicionadas a um grupo usando o comando ifconfig(8). Vários grupos também são criados automaticamente pelo kernel: Isso faria com que a regra correspondesse a qualquer pacote atravessando qualquer interface ppp ou carp, respectivamente.
fam_de_end
A família de endereços do pacote, inet para IPv4 ou inet6 para IPv6. Geralmente o PF pode determinar essa informação com base no(s) endereço(s) de origem e/ou destino do pacote.
protocolo
O protocolo da Camada 4 do pacote:
end_de_or, end_de_dest
Os endereços de origem/destino no cabeçalho IP. Endereços podem ser especificados como:
porta_de_or, porta_de_dest
A porta de origem/destino no cabeçalho da Camada 4 do pacote. Portas podem ser especificadas da seguinte forma:
sinalizadores_tcp
Especifica os sinalizadores que precisam estar definidos no cabeçalho TCP quando proto tcp for utilizado. Sinalizadores são especificados como: flags a_verificar/máscara. Por exemplo: flags S/SA - instrui o PF a verificar somente os sinalizadores S e A (SYN e ACK) e corresponder somente se apenas o sinalizador SYN estiver "ligado" (e é aplicado, por padrão, em todas as regras que referenciem o protocolo TCP). No OpenBSD 4.1 e versões recentes, os sinalizadores padrão S/SA são aplicados em todas as regras de filtragem TCP. flags any diz ao PF para não checar as flags.
estado
Especifica se a informação de estado deve ser mantida em pacotes que correspondam à regra.

Negar por Padrão

A prática recomendada ao configurar um firewall é usar uma política "negar por padrão". Isto é, bloquear tudo e depois ir permitindo certos tipos de tráfego através do firewall. Essa é a abordagem recomendada por ser mais cautelosa, além de facilitar a configuração do conjunto de regras.

Para criar uma política de filtragem de negar por padrão, as primeiras duas regras de filtragem devem ser:

block in  all
block out all

Isso bloqueia todo o tráfego em todas interfaces em qualquer direção, de qualquer lugar para qualquer lugar.

Passando o Tráfego

O tráfego agora deve ser explicitamente permitido ou será barrado pela política padrão do firewall. É aqui que os critérios de filtragem como porta de origem/destino, endereço de origem/destino e protocolo entram em cena. Sempre que o tráfego tiver permissão de cruzar o firewall, as regras devem ser escritas da maneira mais restritiva possível. Isso é para nos certificarmos de que o tráfego válido, e somente tráfego válido, terá permissão para passar.

Alguns exemplos:

# Libera o tráfego entrando na interface dc0, vindo da rede local
# 192.168.0.0/24 e indo para a máquina OpenBSD com o endereço IP
# 192.168.0.1. Também permite o tráfego de retorno saindo da dc0.
pass in  on dc0 from 192.168.0.0/24 to 192.168.0.1
pass out on dc0 from 192.168.0.1 to 192.168.0.0/24


# Libera o tráfego TCP chegando em fxp0 e indo para o servidor Web
# em execução na máquina OpenBSD. O nome da interface, fxp0, é usado
# como endereço de destino, de modo que aqueles pacotes somente
# correspondam à regra se eles têm como destino a máquina OpenBSD.
pass in on fxp0 proto tcp from any to fxp0 port www

A palavra-chave quick

Como dito anteriormente, cada pacote é avaliado pelo conjunto de regras de filtragem de cima para baixo. Por padrão, o pacote é marcado para passagem, o que pode ser alterado por qualquer regra e depois alterado novamente várias vezes antes do fim das regras de filtragem. A última regra que corresponde "vence". Mas, há uma exceção à regra: a opção quick em uma regra de filtragem tem o efeito de cancelar o processamento de qualquer outra regra que venha em seguida e executa imediatamente a ação especificada. Vejamos alguns exemplos:

Errado:

block in on fxp0 proto tcp to port ssh
pass  in all

Nesse caso, a linha block será avaliada, mas jamais terá efeito algum, pois é seguida de uma linha que permite a passagem de tudo.

Melhor:

block in quick on fxp0 proto tcp to port ssh
pass  in all

Essas regras são avaliadas de maneira ligeiramente diferente. Caso a linha block corresponda à regra, devido ao uso da opção quick, o pacote será bloqueado e o restante do conjunto de regras será ignorado.

Mantendo o Estado

Uma das características importantes do Packet Filter é manter o estado das conexões. O PF é capaz de registrar o estado, ou progresso, de uma conexão de rede. Ao armazenar informações sobre o estado de cada conexão em uma tabela de estados, o PF pode rapidamente determinar se um pacote passando pelo firewall pertence a uma conexão já estabelecida. Caso afirmativo, o pacote passa direto pelo firewall sem ser avaliado pelo conjunto de regras.

Manter informações do estado das conexões traz muitas vantagens, incluindo simplicidade na configuração do conjunto de regras e melhor desempenho na filtragem de pacotes. O PF é capaz de comparar os pacotes, indo em qualquer direção, com as entradas na tabela de estados, o que significa que regras de filtragem que autorizam o tráfego de retorno não precisam ser escritas. E, como pacotes que correspondem às conexões com o estado mantido não são avaliados pelo conjunto de regras, o tempo que o PF gasta processando aqueles pacotes pode ser reduzido drasticamente.

Quando uma regra cria estado, o primeiro pacote que corresponde à regra cria um "estado" entre o transmissor e o receptor. Agora, não somente os pacotes do transmissor para o receptor correspondem à entrada na tabela e passam direto pela avaliação do conjunto de regras, mas também os pacotes de resposta do receptor para o transmissor.

Todas as regras pass automaticamente criam um registro de estado quando um pacote corresponde aquela regra. Esse comportamento pode ser explicitamente desabilitado utilizando no state.

pass out on fxp0 proto tcp from any to any

Isso permite que qualquer tráfego TCP saia pela interface fxp0, bem como o tráfego retornando em resposta ao firewall. Manter estados de conexões dão um aumento significativo na performance do seu firewall, uma vez que consultas aos registros de estados já existentes é muito mais rápido que fazer o pacote percorrer outras regras de filtragem.

A opção modulate state funciona como a keep state, exceto que ela se aplica apenas a pacotes TCP. Com modulate state, o Número de Sequência Inicial de conexões saindo do firewall é aleatório. Isso é útil para proteger conexões iniciadas por certos sistemas operacionais que não fazem um bom trabalho ao escolher ISNs. Com intuito de criar conjuntos de regras mais simples, utilizar modulate state como uma opção na regra pode ser feito para regras que não especifiquem o protocolo TCP; nesses casos o PF tratará a regra como keep state.

Para manter o estado em pacotes TCP, UDP e ICMP, e modular ISNs TCP:

pass out on fxp0 proto { tcp, udp, icmp } from any \
    to any modulate state

Outra vantagem de se manter o estado é que o tráfego ICMP correspondente também passa pelo firewall. Por exemplo, se existe uma conexão TCP passando através do firewall, com o estado sendo mantido, e chega uma mensagem de congestionamento ("source-quench") ICMP referenciando esta conexão TCP, ela será correspondida à entrada apropriada na tabela de estados e passará direto pelo firewall.

O escopo de uma entrada na tabela de estados é controlado globalmente pela opção em tempo de execução state-policy e ao nível de cada regra pelas palavras-chave de opções de estado if-bound e floating. Essas palavras-chave usadas nas regras têm o mesmo efeito de quando são usadas na opção state-policy. Exemplo:

pass out on fxp0 proto { tcp, udp, icmp } from any \
    to any modulate state (if-bound)

Essa regra define que para um pacote corresponder à entrada na tabela de estados, ele deve estar transitando na interface fxp0.

Mantendo o Estado Para UDP

Algumas vezes você pode ouvir que: "Não se pode criar estados para conexões UDP, pelo fato do UDP ser um protocolo que não mantém o estado das conexões!". Enquanto é verdade que uma sessão de comunicação UDP não possui nenhum conceito de estado (um início e fim explícito na comunicação), isso não causa nenhum impacto na habilidade do PF em criar estados para sessões UDP. No caso de protocolos sem pacotes de "início" e "fim", o PF simplesmente mantém um registro do tempo desde a última ocorrência de um pacote para dada conexão. Caso o tempo de expiração seja atingido, o registro de estado é eliminado. Valores para esse intervalo podem ser definidos na seção de opções do arquivo pf.conf.

Opções de Rastreamento de Estado

Regras de filtragem que criam entradas na tabela de estados podem especificar várias opções para controlar o comportamento resultante da criação de estado. As seguintes opções estão disponíveis:
max número
Limita o número máximo de entradas de estado que a regra pode criar para número. Se o máximo for alcançado, pacotes que normalmente criariam estados falham na correspondência à regra até que o número de estados existentes diminua abaixo do limite.
no state
Previne a regra de criar automaticamente uma entrada na tabela de estados.
source-track
Essa opção habilita o rastreamento do número de estados criados por endereço IP de origem. Essa opção tem dois formatos: O número total de endereços IP de origem rastreados globalmente pode ser controlado pela opção em tempo de execução src-nodes.
max-src-nodes número
Quando a opção source-track é usada, max-src-nodes limita o número de endereços IP de origem que podem criar estados simultaneamente. Essa opção só pode ser usada com source-track rule.
max-src-states número
Quando a opção source-track é usada, max-src-states limita o número de estados simultâneos que podem ser criados por endereço IP de origem. O escopo desse limite (ou seja, estados criados por somente essa regra ou estados criados por todas as regras que usam source-track) é dependente da opção source-track especificada.

Opções são especificadas dentro de parêntesis e imediatamente depois de uma das palavras-chave de estado (keep state, modulate state ou synproxy state). Múltiplas opções são separadas por vírgulas. No OpenBSD 4.1 e versões recentes, a opção keep state se tornou o padrão implícito para todas as regras de filtragem. Apesar disso, quando se especifica opções de manter estado, uma das palavras-chave deve ainda ser usada antes das opções.

Uma regra de exemplo:

pass in on $ext_if proto tcp to $web_server \
    port www keep state \
    (max 200, source-track rule, max-src-nodes 100, max-src-states 3)

A regra acima define o seguinte comportamento:

Um conjunto separado de restrições pode ser colocado em conexões TCP, com o estado mantido, que completaram o aperto de mão triplo ("3-way handshake").

max-src-conn número
Limita o número máximo de conexões TCP simultâneas, que completaram o aperto de mão triplo, que uma única máquina pode fazer.
max-src-conn-rate número / intervalo
Limita a taxa de novas conexões a uma certa quantidade por intervalo de tempo.

Ambas as opções chamam automaticamente a opção source-track rule e são incompatíveis com source-track global.

Uma vez que esses limites são colocados em conexões TCP que completaram o aperto de mão triplo, ações mais agressivas podem ser tomadas em endereços IP ofensivos.

overload <tabela>
Coloca um endereço IP de uma máquina ofensiva na tabela nomeada.
flush [global]
Mata qualquer outro estado que corresponda à regra e que foi criado por esse IP de origem. Quando global é especificado, mata todos os estados que correspondam a esse IP de origem, sem levar em consideração qual regra criou o estado.

Um exemplo:

table <abusive_hosts> persist
block in quick from <abusive_hosts>

pass in on $ext_if proto tcp to $web_server \
    port www flags S/SA keep state \
    (max-src-conn 100, max-src-conn-rate 15/5, overload <abusive_hosts> flush)

Isso faz o seguinte:

Sinalizadores TCP

Comparar pacotes TCP com base em seus sinalizadores é geralmente usado na filtragem de pacotes que tentam abrir novas conexões. Os sinalizadores TCP e seus significados são listados aqui:

Para que o PF inspecione os sinalizadores TCP durante a avaliação de uma regra, a palavra-chave flags é usada com a seguinte sintaxe:

flags a_verificar/máscara
flags any

A parte máscara diz ao PF para verificar apenas os sinalizadores indicados, e a parte a_verificar informa o(s) sinalizador(s) que deve(m) estar "ligado(s)" no cabeçalho para que o pacote corresponda à regra. O uso da palavra-chave any permite que qualquer combinação de sinalizadores esteja definida no cabeçalho.

pass in on fxp0 proto tcp from any to any port ssh flags S/SA
pass in on fxp0 proto tcp from any to any port ssh

Como flags S/SA é utilizado por padrão, ambas as regras são equivalentes. As duas regras acima deixam passar o tráfego TCP com a flag SYN habilitada, preocupando-se em checar apenas as flags SYN e ACK. Um pacote que tenha as flags SYN e ECE habilitadas também cairá nas regras acima, enquanto um pacote com ambas as flags habilitadas (SYN+ACK) ou apenas com a flag ACK habilitada não corresponderá a nenhuma das regras acima.

O sinalizador de flags padrão pode ser sobrescrito usando a opção flags conforme descrito anteriormente.

Deve-se tomar cuidado ao usar sinalizadores - entenda o que você está fazendo e o porquê de estar fazendo isso - tenha cuidado com conselhos dados pelos outros, pois grande parte deles são incorretos. Algumas pessoas sugerem a criação de estado "somente se o sinalizador SYN estiver marcado, e nenhum outro". Fique atento! Se seguir esse conselho, a regra ficaria assim:

. . . flags S/FSRPAUEW UMA PÉSSIMA IDÉIA!!!

A teoria é criar estado apenas no início da sessão TCP, e a sessão deve ser iniciada somente com um sinalizador SYN, e nenhum outro. O problema é que alguns lugares estão começando a usar o sinalizador ECN, e qualquer um que use ECN e tente se conectar ao seu servidor seria rejeitado por essa regra. Uma abordagem muito melhor seria não especificar sinalizador nenhum e deixar o PF aplicar os sinalizadores padrão nas suas regras. Se você realmente necessita de especificar os sinalizadores você mesmo, então esta combinação seria segura:

. . . flags S/SAFR

Embora seja prático e seguro, também é totalmente desnecessário verificar os sinalizadores FIN e RST caso o tráfego passe antes por regras de scrub. O processo de normalização adotado pelo uso de scrub fará com que o PF discarte quaisquer combinações ilegais nas flags do protocolo TCP (tais como SYN+RST) e normalizará potenciais combinações ambiguas (tais como SYN+FIN).

Proxy de Pacotes TCP SYN

Normalmente quando um cliente inicia uma conexão TCP com o servidor, o PF transfere os pacotes de aperto de mão ("handshake") da forma como eles vieram. O PF, porém, possui a habilidade de fazer proxy no aperto de mão. Com o uso de proxy no aperto de mão, o próprio PF completa o aperto de mão com o cliente, inicia um aperto de mão com o servidor, e então transfere pacotes entre os dois. No caso de um ataque de flood SYN em TCP (jamais ocorrerá o "handshake" completo) os pacotes de flood nunca chegarão ao servidor protegido, mas clientes verdadeiros que tentem se conectar ao servidor terão uma conexão estabelecida normalmente. Isso minimiza o impacto de floods de SYN em TCP forjados (spoofados) destinados ao serviço protegido, tratando dos pacotes diretamente no PF. O uso rotineiro desta opção não é recomendado, pois ele quebra o comportamento esperado do protocolo TCP quando o servidor não pode processar a solicitação e quando existe algum tipo de balanceamento de carga relacionado so serviço provido por um determinado servidor.

O proxy de pacotes TCP SYN é habilitado usando as palavras-chave synproxy state em regras de filtragem. Exemplo:

pass in on $ext_if proto tcp to $web_server port www synproxy state

Aqui as conexões para o servidor Web passarão pelo proxy TCP do PF.

Pela forma como o synproxy state funciona, ele também inclui as mesmas funcionalidades de keep state e modulate state.

O proxy de pacotes SYN não funciona caso o PF esteja atuando em uma ponte (bridge(4)).

Bloqueio de Pacotes Falsificados

"Spoofing" (dissimulação, falsificação) é quando um usuário malicioso falsifica o endereço IP de origem nos pacotes transmitidos por ele para esconder seu endereço real ou personificar outro nó na rede. Uma vez que o usuário tenha falsificado seu endereço, ele pode lançar um ataque na rede sem que sua verdadeira origem seja descoberta, ou ainda tentar ganhar acesso a serviços restritos a determinados endereços IP.

O PF oferece uma proteção contra falsificações através do uso da palavra-chave antispoof:

antispoof [log] [quick] for interface [fam_de_end]
log
Especifica que os pacotes que correspondam à regra devem ser registrados via pflogd(8).
quick
Caso um pacote corresponda à regra, ela é considerada a regra "vencedora" e a avaliação do conjunto de regras termina.
interface
A interface de rede onde ativar a proteção contra falsificações. Pode ser também uma lista de interfaces.
fam_de_end
A família de endereços onde se deve ativar a proteção contra falsificações, inet para IPv4 ou inet6 para IPv6.

Exemplo:

antispoof for fxp0 inet

Quando um conjunto de regras é carregado, quaisquer ocorrências da palavra-chave antispoof são expandidas em duas regras de filtragem. Assumindo que a interface fxp0 possui o endereço IP 10.0.0.1 e a máscara de sub-rede 255.255.255.0 (ou seja, /24), a regra antispoof acima expandiria para:

block in on ! fxp0 inet from 10.0.0.0/24 to any
block in inet from 10.0.0.1 to any

Essas regras fazem duas coisas:

NOTA: As regras de filtragem antispoof expandidas também bloquearão pacotes enviados pela interface de loopback para o endereço local. Recomenda-se evitar qualquer tipo de filtragem na interface de loopback, mas isso se torna uma necessidade quando usamos regras contra falsificações:

set skip on lo0

antispoof for fxp0 inet

O uso de antispoof deve ser restrito à interfaces que possuam um endereço IP. Usar antispoof em uma interface sem um endereço IP resulta em regras como:

block drop in on ! fxp0 inet all
block drop in inet all

Nessas regras existe o risco de se bloquear todo o tráfego de entrada em todas as interfaces.

Unicast Reverse Path Forwarding

O PF oferece o recurso de "Unicast Reverse Path Forwarding" (uRPF). Quando um pacote passa por uma verificação uRPF, o endereço de origem do pacote é procurado na tabela de roteamento. Se a interface de saída encontrada na entrada da tabela de roteamento é a mesma que a interface que o pacote está chegando, então a verificação uRPF libera a passagem do pacote. Se as interfaces não correspondem, então é possível que o pacote teve seu endereço de origem falsificado.

A verificação uRPF pode ser feita nos pacotes usando a palavra-chave urpf-failed nas regras de filtragem:

block in quick from urpf-failed label uRPF

Note que a verificação uRPF somente faz sentido em um ambiente onde as rotas são simétricas.

uRPF fornece a mesma funcionalidade das regras antispoof.

Detecção Passiva de Sistema Operacional

"Passive OS Fingerprinting" (OSFP) é um método para identificar de maneira passiva o sistema operacional de uma máquina remota com base em certas características dos pacotes TCP SYN gerados pela máquina. Essa informação pode então ser usada como critério em regras de filtragem.

O PF determina o sistema operacional remoto comparando as características do pacote TCP SYN contra um arquivo de impressões digitais, que por padrão é o /etc/pf.os. Quando o PF está habilitado, a lista atual de impressões digitais pode ser vista com o comando:

# pfctl -s osfp

Em uma regra de filtragem, a impressão digital pode ser especificada pela classe do SO, versão ou subtipo/revisão. Cada um dos itens é listado na saída do comando pfctl mostrado acima. Para especificar uma impressão digital em uma regra de filtragem, a palavra-chave os é usada:

pass  in on $ext_if proto tcp from any os OpenBSD keep state
block in on $ext_if proto tcp from any os "Windows 2000"
block in on $ext_if proto tcp from any os "Linux 2.4 ts"
block in on $ext_if proto tcp from any os unknown

A classe de sistema operacional unknown permite o enquadramento de pacotes quando a impressão digital do SO não é conhecida.

TOME NOTA:

Opções IP

Por padrão, o PF bloqueia pacotes com opções IP definidas. Isso pode dificultar o trabalho de ferramentas de "detecção de sistema operacional", como o nmap. Caso você possua algum aplicativo que faça uso desses pacotes, como multicast ou IGMP, você pode usar a diretiva allow-opts:
pass in quick on fxp0 all allow-opts

Exemplo de Conjunto de Regras de Filtragem

Abaixo está um exemplo de conjunto de regras de filtragem. A máquina executando o PF funciona como firewall entre uma pequena rede interna e a Internet. São mostradas apenas as regras de filtragem; regras de enfileiramento, nat, rdr, etc., foram deixadas de fora deste exemplo.

ext_if  = "fxp0"
int_if  = "dc0"
lan_net = "192.168.0.0/24"

# Tabela contendo todos os endereços IP atribuídos ao firewall
table <firewall> const { self }

# Não filtra na interface loopback
set skip on lo0

# Faz scrub em pacotes que chegam
match in all scrub (no-df)

# Define a política "negar por padrão"
block all

# Ativa a proteção contra falsificações para todas as interfaces
block in quick from urpf-failed

# Permite conexões ssh vindas apenas da rede interna e se forem
# de um computador confiável, 192.168.0.15. "block return" faz com que
# um pacote TCP RST seja enviado para derrubar conexões bloqueadas.
# "quick" assegura que esta regra não seja invalidada por alguma
# regra "pass" abaixo.
block return in quick on $int_if proto tcp from ! 192.168.0.15 \
   to $int_if port ssh

# Permite tráfego indo para e vindo da rede interna.
# Estas regras criam entradas na tabela de estados devido
# à opção padrão "keep state" que é aplicada automaticamente.
pass in  on $int_if from $lan_net
pass out on $int_if to $lan_net

# Permite tráfego tcp, udp e icmp saindo pela interface externa (Internet).
# Conexões tcp serão moduladas, udp/icmp manterão o estado.
pass out on $ext_if proto { tcp udp icmp } all modulate state

# Permite conexões ssh na interface externa contanto que NÃO sejam
# destinadas ao firewall (ou seja, conexões destinadas à máquinas na
# rede local). Registra o pacote inicial para que mais tarde possamos
# saber quem tentou se conectar. 
# Descomente a última parte (synproxy state) para utilizar o synproxy 
# para gerenciar a conexão.
pass in log on $ext_if proto tcp to ! <firewall> \
   port ssh # synproxy state

[Anterior: Tabelas] [Conteúdo] [Próximo: Tradução do Endereço de Rede (NAT)]


[voltar] www@openbsd.org
$OpenBSD: filter.html,v 1.18 2013/05/03 05:53:48 ajacoutot Exp $