sexta-feira, 5 de junho de 2009

FreeBSD com IPFW

Há uns 2 anos atraz eu postei esse artigo no meu antigo blog e hoje rodando pela net achei ele no site do PfSense... então ai vai o "re-blog".

O IPFIREWALL é o filtro de pacotes nativo do FreeBSD, sendo também chamado de IPFW, que é a interface para controle do IPFIREWALL. O IPFIREWALL faz o monitoramento de cada pacote em cada conexão feita à máquina, determinando por meio das regras definidas pelo IPFW qual é o tratamento dado a estes pacotes. As regras são lidas de cima para baixo, e podem determinar se o pacote será liberado, bloqueado, encaminhado etc.

Atualmente podemos ativar o suporte a IPFW2. O IPFW2 é uma nova versão do IPFW, com maior flexibilidade no formato das regras e algumas funcionalidades a mais, entre elas: suporte a regras não específicas para TCP ou UDP com número de porta, suporte a blocos OR, keepalives para sessões stateful e filtragem por cabeçalho MAC.
Ativando o IPFW2

Para habilitar o suporte ao ipfirewall e o ipfw2, devemos seguir alguns passos. Inicialmente, o próprio ipfw deverá ser recompilado, para suportar ipfw2. Execute os seguintes comandos:

# cd /usr/src/sbin/ipfw
# make clean
# make -DIPFW2
# make -DIPFW2 install

Para que quando formos atualizar o sistema e executar um "make buildworld" o make saiba deste detalhe no momento de compilar o ipfw, adicione a linha abaixo ao arquivo /etc/make.conf:

IPFW2=TRUE

Edite o arquivo de configuração do kernel e insira as seguintes linhas, descritas abaixo:

options IPFIREWALL
options IPFW2
options IPFIREWALL_VERBOSE
options IPFIREWALL_VERBOSE_LIMIT=100
options IPFIREWALL_FORWARD
options IPDIVERT

Primeira linha: ativa o ipfirewall, carregando-o estaticamente no kernel.
Segunda linha: ativa o ipfw2 propriamente dito.
Terceira linha: ativa o suporte a log no ipfirewall. O log é feito via syslog.
Quarta linha: define um limite para o log de cada regra. O padrão é 100, dessa forma cada regra terá até 100 ocorrências no log. Isto é feito para evitar o comprometimento do sistema em caso de ataques como negação de serviço.
Quarta linha: ativa o suporte a encaminhamento de pacotes.
Quinta linha: ativa o suporte a redirecionamento de porta através de socket "divert".


Após todas estas configurações, compile e reinstale o kernel, e reinicie a máquina. Isto deverá ser feito no console, pois após reiniciar o firewall será carregado, e como não foi definida nenhuma regra irá bloquear tudo. Uma forma de contornar isso, caso não seja possível estar junto a máquina, é inserir as seguintes linhas no arquivo /etc/rc.conf:

firewall_enable="YES"
firewall_type="OPEN"

Isto fará com que na inicialização seja carregada a configuração "OPEN" do arquivo /etc/rc.firewall. Esta configuração irá adicionar uma regra que libera todo o tráfego.

Após reiniciada a máquina, digite o seguinte comando:

# ipfw list

Será mostrada a lista de regras ativas, que de acordo com a configuração OPEN do arquivo /etc/rc.firewall deverá ser a seguinte:

00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any

Neste momento, todos os pacotes que entram e saem da máquina estão passando por estas regras, na ordem em que estão, definida pelo número da regra (que vai de 1 a 65535). A primeira regra que for atendida irá definir o que fazer com o pacote, e as demais são geralmente ignoradas (em alguns casos específicos o pacote é reinjetado).

Regra 100: permite que qualquer pacote IP trafegue na interface lo0 (localhost).
Regra 200: bloqueia o tráfego de qualquer origem para a rede 127.0.0.0/8 (localhost).
Regra 300: bloqueia o tráfego com origem na rede 127.0.0.0/8 para qualquer destino.
Regra 65000: permite qualquer tráfego.
Regra 65535: bloqueia qualquer tráfego.

Estas regras estão no formato do ipfw1, que também é suportado, para compatibilidade, pelo ipfw2. Lembre-se que todas estas regras foram definidas pelo rc.firewall, exceto a regra de número 65535 (máximo), que é o padrão do ipfirewall, bloquear tudo. Caso seja conveniente que o padrão do firewall seja liberar tudo, ou seja, a regra 65535 seria "allow all from any to any", então deve ser adicionada a seguinte linha na configuração do kernel:

options IPFIREWALL_DEFAULT_TO_ACCEPT



Comando IPFW

O comando ipfw, de uma forma sucinta, possui os seguintes parâmetros:

ipfw [-q] add regra -> Adiciona a regra (ver o formato abaixo). A opção "-q" indica que deverá ser uma operação "silenciosa", não gerando saídas nem relatando as ações.

ipfw delete número_regra -> Remove a regra com o número especificado.

ipfw list -> Lista as regras ativas.

ipfw [-t -d] show -> Lista as regras ativas, incluindo os contadores número de pacotes e número de bytes. O parâmetro -t inclui ainda a data/hora da última ocorrência. O parâmetro -d lista também as regras dinâmicas.

ipfw [-q] flush -> Deleta todas as regras.

ipfw [-q] zero -> Zera todos os contadores (número de pacotes, número de bytes, número de logs e timestamp).

ipfw [-q] resetlog -> Zera o contador número de logs.



Formato das Regras

As regras que vimos anteriormente, como foi mencionado, estão no formato do ipfw1, que atualmente também é aceito pelo ipfw2. Pode-se usar este formato para escrever as regras, no entanto é bom se habituar com o novo formato. Quando forem acrescentadas regras no formato novo, o ipfw2 irá automaticamente inserir as palavras "ip from any to any", que fazem parte do formato antigo, e não irão mudar em nada a regra, já que não impões nenhuma restrição, e quem vai ditar a especificação dos pacotes são as opções, explicadas adiante. Este esquema é feito para manter uma certa compatibilidade com o ipfw1.

A partir de agora, já podem ser definidas regras para controlar o Firewall. É muito importante se familiarizar com a sintaxe e forma de uso do ipfw, que será o único comando utilizado para controlar o ipfirewall. A formato das regras é o seguinte:

[número_regra] [prob probalilidade] ação [log [logamount número] ] corpo_regra

[número_regra]
Varia de 1 a 65535 e indica a seqüência em que as regras serão processadas. A número 65535 é reservado para a ação padrão do firewall, que será bloquear ou permitir tudo, dependendo da configuração do kernel. Se não for inserido um número de regra ela será automaticamente a última antes da 65535. Se forem inseridas duas ou mais regras com o mesmo número, será obedecida a ordem em que foram inseridas.

[prob probabilidade]
Define uma probabilidade para aplicar a regra. Varia de 0 a 1.


ação

allow
Sinônimo de accept, pass e permit. Libera o tráfego do pacote e termina a leitura das regras.

check-state
Checa o pacote contra um conjunto de regras dinâmico.

count
Apenas atualiza o contador desta regra. As demais regras continuam a ser lidas.

deny
Sinônimo de drop, descarta o pacote e termina a leitura das regras.

divert porta
Redireciona o pacote para a porta especificada, utilizando um socket "divert". Pode ser especificado número ou nome, veja /etc/services.

fwd ip[,porta]
Sinônimo de forward, encaminha o pacote para o ip especificado. Se o ip for local será encaminhado para a porta especificada, se o ip não for local a porta será ignorada. O pacote não é alterado, e isto inclui o ip de destino, então se o pacote for encaminhado para outro host provavelmente será rejeitado. Caso seja encaminhado para um ip local, desta máquina, o socket que irá receber o pacote terá o seu endereço alterado para coincidir com o endereço de destino do pacote, aceitando desta forma o mesmo.

pipe número
Passa o pacote através de um "pipe" dummynet, para controle de tráfego.

queue número
Passa o pacote para uma "queue" dummynet, para controle de tráfego utilizando WF2Q+.

reset
Descarta o pacote, e se o mesmo for TCP tenta enviar um TCP RST.

skipto número
Pula para a regra de número especificado.

tee porta
Aceita o pacote e envia uma cópia do mesmo para a porta especificada, via socket "divert".

unreach código
Descarta o pacote, e tenta enviar uma resposta "ICMP unreachable" com o código especificado. O código deve ser entre 0 e 255, ou alguma destas palavras chave: net, host, protocol, port, needfrag, srcfail, net-unknown, host-unknown, isolated, net-prohib, host-prohib, tosnet, toshost, filter-prohib, host-precedence ou precedence-cutoff.


[log [logamount número] ]
Caso mencionada a palavra log, cada vez que um pacote coincidir com esta regra será feito um log, através do syslog. Caso seja inserido logamount número, este será o limite de vezes que será feito o log para esta regra. O valor 0 (zero) significa sem limites. Caso não seja inserido logamount, o padrão é o limite que foi configurado no kernel.


corpo_regra
Contém uma ou mais exigências que o pacote precisa coincidir para a regra ser atendida. Essa especificação pode incluir endereço ip de origem, endereço ip de destino, porta de origem, porta de destino, protocolo, interface de rede de entrada, interface de rede de saída etc. O corpo da regra pode possuir uma ou mais opções. Essas opções podem ser precedidas de "not", como negação, ou serem agrupadas em blocos OR, entre chaves, por exemplo: { dst-port 50 or dst-port 51 or not src-port 52 }.

A seguir as opções mais importantes:

// comentário
Insere o texto como sendo um comentário na regra.

dst-ip endereço
Endereço IP de destino do pacote.

dst-port porta
Porta(s) de destino do pacote. Se for especificada mais de uma porta, separar por vírgula (50, 51, 52), ou em faixa de portas (50-60).

established
Se o pacote tiver os bits RST ou ACK.

frag
fragmentos de pacotes, não sendo o primeiro fragmento.

gid grupo
Pacotes TCP ou UDP enviados ou recebidos pelo grupo. O grupo pode ser especificado pelo nome ou pelo GID.

icmptypes tipo
Tipo(s) de pacotes ICMP. Se for mais de um, separar por vírgula. Os tipos podem ser: echo reply (0), destination unreachable (3), source quench (4), redirect (5), echo request (8), router advertisement (9), router solicitation (10), time-to-live exceeded (11), IP header bad (12), timestamp request (13), timestamp reply (14), information request (15), information reply (16), address mask request (17) e address mask reply (18).

in | out
Pacotes de entrada ou de saída. Note que isto significa que os pacotes estão entrando ou saindo da máquina, então mesmo que um pacote venha da rede interna, estará entrando na máquina antes de sair.

keep-state
Quando um pacote coincidir com uma regra que tiver esta opção, será criada uma regra dinâmica, cujo comportamento será coincidir o tráfego bidirecional entre este ip/porta de origem e ip/porta de destino, no mesmo protocolo. A regra dinâmica expira após um certo tempo. Dessa forma, pode-se definir uma regra "check-state" anterior a esta, liberando este fluxo de pacotes, e teremos um firewall "stateful".

limit {ip-origem | porta-origem | ip-destino | porta-destino} número
Serão permitidas apenas o número especificado de conexões com os parâmetros especificados.

mac mac-destino mac-origem
Pacotes com o endereço MAC de destino e/ou de origem especificados. Se não for especificado algum deverá ser usada a palavra "any", para coincidir com todos os endereços.

proto protocolo
Pacotes com o protocolo (IP) especificado. Veja /etc/protocols.

recv interface | xmit interface | via interface
Pacotes recebidos pela interface de rede especificada (recv xl0), pacotes transmitidos pela interface especificada (xmit fxp0), ou pacotes passando pela interface, independentemente de entrar ou sair (via xl0). Quando xmit for utilizado é requerida a opção "out", já que o pacote estará saindo.

setup
Pacotes com o bit SYN mas sem o bit ACK.

src-ip endereço
Endereço IP de origem do pacote.

src-port porta
Porta(s) de origem do pacote.

tcpflags flags
Flags dos pacotes TCP, separadas por vírgula. As possíveis são: fin, syn, rst, psh, ack e urg. A negação pode ser feita por um "!".

uid usuário
Pacotes TCP ou UDP enviados ou recebidos pelo usuário. O usuário pode ser especificado pelo username ou pelo UID.

vrrevpath
Pra pacotes de entrada, é feita uma consulta ao endereço de origem na tabela de roteamento. Se a interface na qual o pacote entrou é a mesma de saída especificada pela rota, então a regra coincide. Isto pode ser utilizado para criar regras anti-spoofing. Os pacotes de saída não são submetidos à verificação.



Firewall Stateful

O funcionamento stateful permite que o firewall crie regras dinâmicas para fluxos específicos de pacotes. Pode-se fazer algumas coisas interessantes, como por exemplo manter o firewall fechado, bloqueando todo tráfego de fora para dentro e permitindo apenas que pacotes da rede interna saiam para a rede externa, passando por uma regra "keep-state". Assim, cada conexão feita de dentro para fora irá criar uma regra dinâmica, que irá liberar aquele tráfego nas duas direções, permitindo que os dados de resposta, por exemplo, uma página web que um usuário acessou, cheguem até a máquina do usuário, na rede interna.
As regras dinâmicas possuem as seguintes informações: protocolo, endereço IP e porta de origem e endereço IP e porta de destino. Elas irão permitir o tráfego bidirecional, ou seja, mesmo que os endereços de origem e destino se invertam. Isto é uma das coisas que possibilita criar o que foi descrito acima. As regras dinâmicas possuem um tempo de vida limitado, que é determinado pelas variáveis net.inet.ip.fw.dyn*, do sysctl (maiores informações vide a man page do sysctl). Estas variáveis também determinam o número máximo de regras dinâmicas, entre outros.
Uma regra dinâmica é criada cada vez que um pacote coincide com uma regra que possua as opções keep-state ou limit, não sem antes checar se a regra já existe. As regras dinâmicas são checadas na ocorrência da ação check-state.

Exemplo:

# ipfw add 1000 check-state
# ipfw add 1100 allow tcp from 10.10.0.0/16 to any setup keep-state
# ipfw add 1200 deny tcp from any to any

Este conjunto de regras irá, para cada pacote:
1. Checar se existe alguma regra dinâmica que permita o tráfego do mesmo;
2. Caso o pacote seja da rede 10.10.0.0/16 e tiver o bit SYN, mas não o bit ACK (indicando desta forma um início de conexão), irá permitir o tráfego e criar uma regra dinâmica;
3. Bloquear qualquer outro tráfego.



Log

Para direcionar os logs do ipfw para o arquivo /var/log/ipfw/ipfw.log, primeiramente crie este diretório e este arquivo:

# mkdir /var/log/ipfw
# touch /var/log/ipfw/ipfw.log
# chmod -R 600 /var/log/ipfw

Então adicione as seguintes linhas ao arquivo /var/log/syslog.conf:

!ipfw
*.* /var/log/ipfw/ipfw.log

Após isso, reinicie o syslog, através do comando "killall -HUP syslog". Talvez seja interessante também criar um script que faça a rotação deste log e agendar no Cron, ou adicionar uma entrada no newsyslog.conf, para que o arquivo não fique demasiado grande.



Arquivo de Regras

Não é recomendado editar o arquivo /etc/rc.firewall, que vem com o sistema. O seu conjunto de regras deverá ficar em um arquivo separado, exclusivo para isso. Este arquivo poderá ser um script shell ou poderá ser apenas uma listagem de regras, que o ipfw irá interpretar. O tipo de arquivo é uma escolha pessoal, e não fará diferença no funcionamento do firewall.


Arquivo com listagem de regras

Deverá ser criado um arquivo, por exemplo /etc/firewall, com dono root e permissão 600. Neste arquivo serão colocadas as regras, que são iguais às passadas via linha de comando ao ipfw, mas sem o comando "ipfw" no começo. Exemplo:

add 1000 allow src-ip 10.10.0.0/16 dst-ip 192.168.0.0/16

Para efetuar a inicialização destas regras no momento da inicialização, adicione ou modifique as seguintes linhas no /etc/rc.conf:


firewall_enable="YES"
firewall_type="/etc/firewall"
firewall_quiet="YES"

A opção firewall_quiet faz com que seja executado o comando "ipfw -q" ao invés de simplesmente "ipfw", ao ler cada regra do arquivo. Dessa forma ocorrerá uma operação "silenciosa", não gerando saídas na tela. Para interpretar um arquivo deste tipo via linha de comando, simplesmente execute "ipfw /etc/firewall".



Script de regras

No caso de criamos um script shell com as regras, também deverá ser criado um arquivo exclusivo para isso, como por exemplo /etc/firewall.sh, com dono root e permissão 700. O conteúdo deste arquivo pode ser como você quiser, pois trata-se de um script comum. Quando for passar as regras, o comando ipfw deve ser exatamente como se fosse via linha de comando. É recomendado usar a opção "-q", do comando ipfw, em scripts.
Para que o script seja executado na inicialização do sistema, edite o arquivo /etc/rc.conf, remova as linhas (caso existirem) firewall_type e firewall_quiet, mantenha a linha

firewall_enable="YES"

e adicione a seguinte linha:

firewall_script="/etc/firewall.sh"

Deste ponto em diante, cabe a você decidir como deverá ser o comportamento do seu firewall, tendo em vista a que ele se destina. Sugiro a leitura do livro "Building Internet Firewalls", de D. Brent Chapman e Elizabeth D. Zwicky, da editora O'Reilly. Seguem abaixo algumas regras e um script simples, a título de exemplo.



Exemplos de Regras

(não esquecer do comando "ipfw" antes delas):

add 100 allow via lo0
add 200 deny { dst-ip 127.0.0.0/8 or src-ip 127.0.0.0/8 }

Observe os espaços após a "{" e antes da "}". Se não houver este espaço será retornado o seguinte erro:

ipfw in free(): warning: modified (chunk-) pointer

Estas duas regras acima terão o mesmo efeito que as regras abaixo, no formato antigo, descritas anteriormente:

add 100 pass all from any to any via lo0
add 200 deny all from any to 127.0.0.0/8
add 300 deny ip from 127.0.0.0/8 to any

add 1000 allow src-ip 10.10.0.0/16 dst-port 80
add 1100 allow dst-ip 10.10.0.0/16 dst-port 1024-65535

Irá permitir que máquinas da rede 10.10.0.0/16 enviem pacotes com destino a porta 80 e irá permitir que pacotes cheguem até a rede 10.10.0.0/16 com destino a portas entre 1024 e 65535, permitindo por exemplo a resposta de um pedido HTTP.

add 1000 allow proto tcp dst-port ssh recv xl0
add 1100 deny proto tcp dst-port ssh out

Irá permitir que a máquina receba conexões TCP pela interface de rede xl0, à porta do ssh, que é a porta 22, conforme definido no arquivo /etc/services. Também irá negar a saída de qualquer pacote com protocolo TCP e com destino a porta do ssh.

add 1000 check-state
add 1100 allow src-ip 10.10.0.0/16 keep-state
add 1200 deny log ip from any to any

Irá permitir que a rede 10.10.0.0/16 estabeleça qualquer conexão, cujo tráfego de resposta será liberado pelas regras dinâmicas que serão criadas pela regra 1100 e que serão checadas pela regra 1000. Qualquer outro tráfego será bloqueado e logado no arquivo de log.

add 50 deny not vrrevpath in

Irá bloquear ip-spoofing, conforme explicado anteriormente.

add 500 deny log { src-ip 10.0.0.0/8 or dst-ip 10.0.0.0/8 } via xl0
add 510 deny log { src-ip 172.16.0.0/12 or dst-ip 172.16.0.0/12 } via xl0
add 520 deny log { src-ip 192.168.0.0/16 or dst-ip 192.168.0.0/16 } via xl0

Irá proibir o tráfego de pacotes de redes privadas, conforme definido na RFC1918, na interface de rede xl0. Também irá fazer log quando a regra coincidir com algum pacote.

add 100 prob 0.05 deny in

Irá bloquear 5% dos pacotes de entrada, como se houvesse perda de pacotes.


Exemplo de Script

#!/bin/sh

ipfw="/sbin/ipfw -q"

# IP local
ip="10.10.0.5"

# Portas de entrada permitidas
portas="22,53,80"

$ipfw flush
$ipfw add 100 deny log not verrevpath in
$ipfw add 1000 check-state
$ipfw add 1100 allow src-ip $ip keep-state
$ipfw add 1200 allow dst-port $portas in
$ipfw add 65000 deny ip from any to any


By CleBeeR...

Nenhum comentário: