[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 da passagem de pacotes de dados de maneira seletiva, conforme eles atravessam a interface de rede. O critério usado pelo pf(4) ao inspecionar pacotes são baseados nos cabeçalhos da Camada 3 (IPv4 e IPv6) e Camada 4 (TCP, UDP, ICMP, e ICMPv6). Os critérios mais usados são endereço 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 pode ser bloqueio ou liberação, tomada quando o pacote casa com a regra. As regras de filtragem são avaliadas em sequência da primeira a última. A não ser que o pacote encontre um regra contendo a palavra-chave quick, o mesmo será avaliado contra todas as regras de filtragem antes da ação final ser tomada. A última regra a casar é a "vencedora" e dita qual ação tomar. Existe um pass all implícito no início das regras de filtragem, que significa que caso o pacote não case com nenhuma regra a ação resultante será pass.

Sintaxe das Regras

A forma geral simplificada da sintaxe para regras de filtragem é:
ação [direção] [log] [quick] [on interface] [af] [proto protocolo] \
   [from src_addr [port src_port]] [to dst_addr [port dst_port]] \
   [flags tcp_flags] [state]
ação
A ação executada em pacotes que combinem com a regra, pode ser pass ou block. A ação pass libera a passagem do pacote para processamento posterior pelo kernel, enquanto que a ação block reagirá com base na 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 na interface, pode ser in ou out.
log
Especifica que o pacote deve ser logado via pflogd(8). Caso a regra crie estados então somente o pacote que estabelece a conexão é logado. Para logar todos pacotes, use log (all).
quick
Se um pacote casar com uma regra que especifique a palavra quick, esta regra é considerada final e a ação especificada é executada.
interface
O nome ou grupo da interface de rede onde o pacote passa. Interfaces podem ser adicionadas aos grupos usando o comando ifconfig(8). Vários grupos também são criados automatiamente pelo kernel: Fará com que a regra case com qualquer pacote atravessando qualquer interface ppp ou carp respectivamente.
af
A família de endereços a que o pacote pertence, inet para IPv4 ou inet6 para IPv6. Geralmente o PF pode determinar essa informação com base no(s) endereço(os) de origem e/ou destino do pacote.
protocolo
O protocolo de Camada 4 do pacote:
src_addr, dst_addr
Os endereços de origem/destino no cabeçalho do IP. Endereços podem ser especificados como:
src_port, dst_port
A porta de origem/destino no cabeçalho da Camada 4 do pacote. Portas podem ser especificadas da seguinte forma:
tcp_flags
Especifica os flags que devem estar setados no cabeçalho TCP quando for usado proto tcp. Flags são especificados na forma: flags check/mask. Por exemplo: flags S/SA - instrui o PF a verificar somente os flags S e A (SYN e ACK) e casar, se apenas o flag SYN estiver "setado". No OpenBSD 4.1 e posteriores, a flag padrão S/SA é aplicada a todas as regras de filtragem TCP.
state
Especifica se a informação de estado deve ser mantida em pacotes que casem com a 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. Esta é a abordagem recomendada por ser mais cautelosa, além de facilitar a configuração das regras.

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

block in  all
block out all

Isto irá bloquear todo o tráfego em todas interfaces em qualquer direção, de qualquer lugar para qualquer lugar.

Passando 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 de passagem.

Alguns exemplos:

# Autoriza passagem de tráfego chegando em dc0 vindo da rede local 192.168.0.0/24,
# e indo para a máquina OpenBSD com endereço IP 192.168.0.1. Também permite
# tráfego de retorno saindo via 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


# Permite tráfego TCP chegando em fxp0 e indo para o servidor web
# rodando na máquina OpenBSD. O nome da interface, fxp0, é usado como
# endereço de destino, para que apenas pacotes destinados à
# máquina OpenBSD casem com a regra.
pass in on fxp0 proto tcp from any to fxp0 port www

A palavra-chave quick

Como dito anteriormente, cada pacote é avaliado contra as 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 casa "vence". Mas, há uma excessão à regra: A opção quick numa 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 from any to any port ssh
pass  in all

Neste 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 from any to any port ssh
pass  in all

Essas regras são avaliadas de maneira ligeiramente diferente. Caso a linha block case com a regra, devido o uso da opção quick, o pacote será bloqueado, e o restante das regras serão ignoradas.

Mantendo o Estado

Uma das características importantes do Packet Filter é "manter o estado das conexões", ou "stateful inspection". Stateful inspection é a habilidate do PF em registrar o estado, ou progresso de uma conexão de rede. Armazenando informações sobre o estado de cada conexão numa tabela, o PF pode rapidamente determinar se um pacote passando pelo firewall pertence a uma conexão já estabelecida. Caso afirmativo, ele passa direto pelo firewall sem ser avaliado pelas regras.

Manter informações de estado das conexões traz muitas vantagens incluindo simplicidade na configuração das regras e melhor performance na filtragem de pacotes. O PF pode criar entradas na tabela de estado para pacotes indo em qualquer direção, o que significa que regras de filtragem que autorizam o tráfego de retorno não precisam ser escritas. E, como pacotes que possuem entradas na tabela de estado não são avaliados pelas regras, o tempo que o PF gasta no processamento destes pode ser drasticamente reduzido.

Quando uma regra cria estados, o primeiro pacote que casa com a regra cria um "estado" entre transmissor e receptor. Agora, não somente os pacotes do transmissor para o receptor combinam com a entrada na tabela e passam direto pelas regras, como também as respostas do receptor.

A partir do OpenBSD 4.1, todas as regras de filtragem automaticamente criam um estado quando um pacote combinar com a regra. Nas versões anteriores do OpenBSD a regra de filtragem precisava explicitamente usar a opção keep state.

Exemplo usando OpenBSD 4.1 e posteriores:

pass out on fxp0 proto tcp from any to any

Exemplo ussando OpenBSD 4.0 e anteriores:

pass out on fxp0 proto tcp from any to any keep state

Estas regrais permitem qualquer tráfego TCP saindo pela interface fxp0, bem como o tráfego retornando em resposta ao firewall. Além do recurso de manutenção de estado ser uma excelente característica, seu uso ainda melhora a performance do firewall, pois pesquisas na tabela de estado são muito mais rápidas do que comparar o pacote contra todas as 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 (ISN) 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. Desde o OpenBSD 3.5, a opção modulate state pode ser usada também em regras que especifiquem protocolos diferentes do TCP.

Mantendo estado em pacotes TCP, UDP e ICMP e modulanto ISNs TCP:

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

Outra vantagem de manter o estado é que o tráfego ICMP correspondente também passa pelo firewall. Por exemplo, se uma conexão TCP passando pelo firewall for rastreada como stateful e chegar uma mensagem ICMP source-quench referenciando esta conexão, ela será identificada como pertencendo a uma entrada válida na tabela de estado e passará direto pelo firewall.

O escopo de uma entrada na tabela de estado é controlado globalmente pela opção state-policy em tempo de execução, e com base nas regras de estado pelas palavras-chave if-bound, group-bound e floating. Essas palavras-chave usadas nas regras tem 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 casar com a entrada na tabela de estado, ele deve estar transitando na interface fxp0.

Perceba que para regras nat, binat e rdr é implícita a criação de estados para conexões válidas desde que os pacotes passem pelas regras de filtragem.

Mantendo 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 nas 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 espera seja atingido, o registro de estado é eliminado. Valores para esse intervalo podem ser definidos não seção opções do arquivo pf.conf .

Opções de Rastreamento de Estado

Regras de filtragem que criam estados podem especificar varias opções para controlar o comportamento da tabela de estados resultante. As seguintes opções estão disponíveis:
max número
Limita o número máximo de estados que a regra pode criar para número. Se o máximo for alcançado, pacotes que normalmente criariam estados são descartados até que o número de estados existentes diminua.
no state
Evita que a regra crie estados automaticamente.
source-track
Esta opção habilita o rastreamento do número de estados criados por endereço IP de origem. Esta opção tem dois formatos: O número total de endereços IP de origem rastreados globalmente podem ser controlados pela opção src-nodes em tempo de execução.
max-src-nodes número
Quando opção source-track é usada, max-src-nodes limitará o número de endereços IP de origem que podem criar estados simultâneamente. Esta opção só pode ser usada com source-track rule.
max-src-states número
Quanto a opção source-track é usada, max-src-states limitará o número de estados simultâneos que podem ser criados por endereços IP de origem. O escopo deste limite (ex., somente estados criados por esta regra ou estados criados por todas a regras que usam source-track) é dependente da opção source-track especificada.

Opções são especificadas dentro de parênteses e imediatemente depois de uma das palavras-chave (keep state, modulate state ou synproxy state). Múltiplas opções são separadas por virgulas. No OpenBSD 4.1 e posterior, a opcão keep state se tornou padrão para todas as regras de filtragem. Apesar disso, quanto estiver especificando opções stateful, uma das palavras-chave ainda deve ser usada na frente 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 colodado em conexões TCP stateful que completaram o 3-way handshake.

max-src-conn número
Limita o número máximo de conexões TCP simultâneas que completaram o 3-way handshake que um único host 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 invocam automaticamente a opção source-track rule e são incompatíveis com source-track global.

Uma vez que estes limites são colocados em conexões TCP que completaram o 3-way handshake, ações mais agressivas podem ser tomadas em endereços IP ofensivos.

overload <tabela>
Coloca um endereço IP de um host ofensivo em uma tabela.
flush [global]
Mata qualquer outro estado que combine com esta regra e que foi criado por este IP de origem. Quando global é especificado, mata todos os estados combinando este IP de origem,

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)

Isto faz o seguinte:

Flags TCP

Comparar pacotes TCP com base em seus flags é geralmente usado na filtragem de pacotes tentando abrir novas conexões. Os flags TCP e seu significado são listados aqui:

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

flags check/mask
flags any

A parte mask diz ao PF para verificar apenas os flags indicados e a parte check informa qual(is) flag(s) devem estar "setados" no cabeçalho para que o pacote case com a regra. Usando a palavra-chave any permite que qualquer combinação de flags seja configurada no cabeçlho.

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

A regra acima autoriza tráfego TCP com a flag SYN setada, mas verifica somente os flags SYN e ACK. Um pacote com os flags SYN e ECE combinam com a regra acima, enquanto um pacote com SYN e ACK ou somente ACK não combina.

No OpenBSD 4.1 e posterior, a flag padrão aplicada em regras TCP é flags S/SA. Cobinado com o padrão de regras de filtragem keep state do OpenBSD 4.1, estas duas regras são equivalentes:

pass out on fxp0 proto tcp all flags S/SA keep state
pass out on fxp0 proto tcp all

Cada regra irá combinar com pacotes TCP com a flag SYN configurada e a flag ACK limpa e criará uma entrada na tabela de estados. A flag padrão pode ser substituída usado a opção flags como descrito acima.

No OpenBSD 4.0 e anteriores, não existia uma flag padrão aplicada em todas as regras de filtragem. Cada regra tinha que especificar qual(ais) flag(s) e também tinham que usar explicitamente a opção keep state.

pass out on fxp0 proto tcp all flags S/SA keep state

Deve-se tomar cuidado ao usar flags -- entenda o que você está fazendo e o porquê, e tenha cuidado com conselhos dados pelos outros, pois grande parte deles é incorreto. Algumas pessoas sugerem a criação de estado "somente se o flag SYN estiver setado e nenhum outro". Esta regra deve ficar asssim: . . . flags S/FSRPAUEW má idéia!!

A teoria é, crie estado apenas no início da sessão TCP, e a sessão deve ser iniciada somente com um flag SYN, nada mais. O problema é que alguns sites estão começando a usar o flag ECN e qualquer um que use ECN e tente se conectar ao seu servidor terá os pacotes rejeitados por esta regra. Uma abordagem muito melhor seria não especificar uma flag e deixar que o PF aplique a flag padrão para suas regras. Se você realmente precisa especificar flags então esta combinação deve ser segura:

. . . flags S/SAFR

Além de ser prático e seguro, também não é necessário verificar os flags FIN e RST caso o tráfego passe antes por regras scrub. No processo de normalização o PF descartará quaisquer pacotes que cheguem com combinações de flags TCP inválidas (como SYN e RST) e normalizará combinaçãoes ambiguas em potencial (como SYN e FIN).

TCP SYN Proxy

Normalmente quando um cliente inicia uma conexão TCP com o servidor, o PF transfere os pacotes do handshake da forma como eles vieram. O PF, porém possui a habilidade de fazer proxy no handshake. Com o uso de proxy no handshake, o próprio PF completará o handshake com o cliente, iniciará um handshake com o servidor, e então transferirá pacotes entre os dois. O benefício deste procedimento é que nenhum pacote é enviado ao servidor antes do cliente completar o handshake. Isso evita que ataques TCP SYN flood spoofados atinjam o servidor porque uma conexão spoofada não conseguirá completar o handshake.

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

pass in on $ext_if proto tcp from any to $web_server port www \
   flags S/SA synproxy state

Aqui, as conexões para o servidor web passarão pelo proxy do PF.

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

O SYN proxy não funcionará caso o PF esteja atuando numa bridge(4).

Bloqueando Pacotes Spoofados

"Spoofing" é 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 spoofado 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 alguma proteção contra spoofing através do uso da palavra-chave antispoof:

antispoof [log] [quick] for interface [af]
log
Especifica que, pacotes combinando com a regra devem ser logados via pflogd(8).
quick
Caso um pacote case com a regra ela será considerada a regra "vencedora" e a avaliação das regras terminará.
interface
A interface de rede onde ativar a proteção antispoof. Pode ser também uma lista de interfaces.
af
A família de endereçamento onde se deve ativar a proteção antispoof, inet para IPv4 ou inet6 para IPv6.

Exemplo:

antispoof for fxp0 inet

Quando as regras são carregadas, quaisquer ocorrências da palavra antispoof são expandidas em duas regras de filtragem. Assumindo que a interface fxp0 possui endereço IP 10.0.0.1 e máscara de rede 255.255.255.0 (ex. /24), a regra antispoof acima será expandida para:

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

Estas regras fazem duas coisas:

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

set skip on lo0

antispoof for fxp0 inet

O uso de antispoof deve ser restrito a interfaces que possuam endereço IP. Usar antispoof numa interface sem um endereço IP resultará em regras como:

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

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

Encaminhamento Unicast Reverso

A partir do OpenBSD 4.0, PF oferece Encaminhamento Unicast Reverso (uRPF). Quanto um pacote é verificado pelo uRPF, o endereço IP de origem do pacote é procurado na tabela de roteamento. Se a interface de saída encontrada na tabela de roteamento é a mesma da interface que o pacote entrou, então a verificação uRPF passa. Se a interface não combinar, então é possível que o pacote tenha seu endereço de origem falsificado (spoofed).

A verificação uRPF pode ser aplicada aos 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 só faz sentido em um ambiente onde o roteamento seja simétrico.

uRPF oferece a mesma funcionalidade que as regras antispoof.

Detecção Passiva de Sistema Operacional

Passive OS Fingerprinting (OSFP) é um método para identificar de maneira passiva o sistema operacional de um host remoto com base em certas características dos pacotes TCP SYN gerados pelo host. Esta 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 é /etc/pf.os. Quando o PF está habilitado, a lista atual de impressões digitais pode ser vista com o comando:

# pfctl -s osfp

Numa 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 como saída do comando pfctl mostrado acima. Para especificar uma impressão digital numa regra de filtragem, a palavra-chave os deve ser usada.

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

A classe de sistema operacional unknown permite o enquadramento de pacotes quanto 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 setadas. Isso pode dificultar o trabalho de ferramentas de "detecção de sistema operacional" como nmap. Caso possua alguma aplicação que faça uso destes pacotes, como multicast ou IGMP, você pode usar a diretiva allow-opts:
pass in quick on fxp0 all allow-opts

Exemplo de Arquivo de Regras

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

ext_if = "fxp0" int_if = "dc0" lan_net = "192.168.0.0/24" # tabela contendo todos 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 scrub in all # define o política padrão block all # ativa proteção contra spoof para todas as interfaces block in quick from urpf-failed # permite conexões ssh vindas apenas da rede interna, e se for # de um computador confiável, 192.168.0.15. usa "block return" de # forma que um TCP RST é enviado para derrubar conexões bloqueadas. # usa "quick" para 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 # aceita tráfego indo e vindo para a rede interna. # estas regras criarão estados devido a opção # padrão "keep state" que será automaticamente aplicada. pass in on $int_if from $lan_net to any pass out on $int_if from any to $lan_net # aceita tcp, udp, e icmp saindo pela interface externa (Internet). # conexões tcp configuradas com modulate, udp/icmp serão # rastredos de forma stateful. 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 (ex., conexões destinadas a máquinas na rede # local). loga os pacotes iniciais para que mais tarde possamos saber # quem tentou se conectar. usa tcp syn proxy nas conexões. # A flag padrão "S/SA" será automaticamente aplicada na regra # pelo PF. pass in log on $ext_if proto tcp from any 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.9 2007/12/01 10:39:11 tobias Exp $