Conjuntos de IP revisitados
Já existe uma resposta mencionando conjuntos de IPs. No entanto, é bastante unidimensional, pois se concentra nos ganhos de desempenho em relação às regras clássicas e no fato de que os conjuntos de IP atenuam o problema que se tem com muitos endereços IP individuais que não podem ser facilmente expressos como uma sub-rede na notação CIDR.
Notação usada abaixo
Pois ipset
usarei a notação lida ipset restore
e escrita por ipset save
.
Correspondentemente, para as regras iptables
(e ip6tables
), usarei a notação conforme lida iptables-restore
e escrita por iptables-save
. Isso contribui para uma notação mais curta e me permite destacar regras em potencial apenas IPv4 (prefixadas -4
) ou somente IPv6 (prefixadas -6
).
Em alguns exemplos, desviaremos o fluxo de pacotes para outra cadeia. Presume-se que a cadeia exista nesse ponto; portanto, as linhas para criar as cadeias não são produzidas (nem o nome da tabela é mencionado ou os comandos são COMMIT
exibidos no final).
Conjuntos de IP avançados
Os conjuntos de IPs podem fazer muito mais do que o mencionado na outra resposta e você deve definitivamente ler a documentação do conjunto de IPs ( ipset(8)
), iptables-extensions(8)
além desta breve entrada aqui.
Por exemplo, eu vou concentrar-se principalmente em três tipos de conjunto: hash:ip
, hash:net
e list:set
, mas há mais do que aqueles e todos eles têm casos de uso válidos.
Você pode, por exemplo, também combinar números de porta, não apenas endereços IP .
Salvar e restaurar conjuntos de IPs como iptables-save
eiptables-restore
Você pode criar declarações de conjunto de IP em massa e importá-las, canalizando-as para ipset restore
. Se você deseja tornar seu comando mais resistente a entradas já existentes, use ipset -exist restore
.
Se suas regras estiverem em um arquivo chamado, default.set
você usaria:
ipset -exist restore < default.set
Um arquivo como esse pode conter entradas para create
conjuntos e add
entradas para eles. Mas geralmente a maioria dos comandos da linha de comando parece ter uma versão correspondente nos arquivos. Exemplo (criando um conjunto de servidores DNS):
create dns4 hash:ip family inet
create dns6 hash:ip family inet6
# Google DNS servers
add dns4 8.8.8.8
add dns4 8.8.4.4
add dns6 2001:4860:4860::8888
add dns6 2001:4860:4860::8844
Aqui, um conjunto é criado para IPv4 ( dns4
) e outro para IPv6 ( dns6
).
Tempo limite em conjuntos de IP
Os tempos limites nos conjuntos de IPs podem ser definidos como padrão por conjunto e também por entrada. Isso é muito útil para cenários em que você deseja bloquear alguém temporariamente (por exemplo, para varredura de porta ou tentativa de força bruta no servidor SSH).
A maneira como isso funciona é a seguinte (padrão durante a criação de conjuntos de IP):
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
Voltaremos a esses conjuntos específicos abaixo e à lógica de por que eles são definidos da maneira que são.
Se você deseja definir o tempo limite para um endereço IP específico, basta dizer:
add ssh_dynblock4 1.2.3.4 timeout 7200
Para bloquear o IP 1.2.3.4 por duas horas, em vez da meia hora padrão (definida).
Se você analisar isso ipset save ssh_dynblock4
daqui a pouco, verá algo como:
create ssh_dynblock4 hash:ip family inet hashsize 1024 maxelem 65536 timeout 1800
add ssh_dynblock4 1.2.3.4 timeout 6954
Advertências de tempo limite
- tempos limite são um recurso em qualquer conjunto. Se o conjunto não foi criado com suporte ao tempo limite, você receberá um erro (por exemplo
Kernel error received: Unknown error -1
).
- tempos limite são dados em segundos. Use expressões aritméticas do Bash para passar de minutos a segundos, por exemplo. Por exemplo:
sudo ipset add ssh_dynblock4 1.2.3.4 timeout $((120*60))
Verificando se existe uma entrada em um determinado conjunto de IPs
Dentro de seus scripts, pode ser útil verificar se uma entrada já existe. Isso pode ser alcançado com o ipset test
qual retorna zero se a entrada existir e diferente de zero. Portanto, as verificações usuais podem ser aplicadas em um script:
if ipset test dns4 8.8.8.8; then
echo "Google DNS is in the set"
fi
No entanto, em muitos casos, você prefere usar a -exist
opção ipset
para direcioná-la a não reclamar das entradas existentes.
Preenchendo conjuntos de IPs de iptables
regras
Este, na minha opinião, é um dos principais recursos dos conjuntos de IPs. Além de poder corresponder às entradas de um conjunto de IPs, você também pode adicionar novas entradas a um conjunto de IPs existente.
Por exemplo, nesta resposta a esta pergunta, você tem:
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --update --seconds 15 -j DROP
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --set -j ACCEPT
... com a intenção de limitar a taxa de tentativas de conexão ao SSH (porta TCP 22). O módulo usado recent
controla as tentativas recentes de conexão. Em vez do state
módulo, eu prefiro o conntrack
módulo, no entanto.
# Say on your input chain of the filter table you have
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
# Then inside the SSH chain you can
# 1. create an entry in the recent list on new connections
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
# 2. check whether 3 connection attempts were made within 2 minutes
# and if so add or update an entry in the ssh_dynblock4 IP set
-4 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock4 src --exist
-6 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock6 src --exist
# 3. last but not least reject the packets if the source IP is in our
# IP set
-4 -A SSH -m set --match-set ssh_dynblock4 src -j REJECT
-6 -A SSH -m set --match-set ssh_dynblock6 src -j REJECT
Nesse caso, estou redirecionando o fluxo para a SSH
cadeia, de modo que não precise me repetir -p tcp --dport ssh
para todas as regras.
Reiterar:
-m set
torna iptables
conscientes de que estamos usando interruptores do set
módulo (que lida com conjuntos IP)
--match-set ssh_dynblock4 src
informa iptables
para combinar o endereço de origem ( src
) com o conjunto nomeado ( ssh_dynblock4
)
- corresponde a
sudo ipset test ssh_dynblock4 $IP
(onde $IP
contém o endereço IP de origem do pacote)
-j SET --add-set ssh_dynblock4 src --exist
adiciona ou atualiza o endereço de origem ( src
) do pacote no conjunto de IPs ssh_dynblock4
. Se existir uma entrada ( --exist
), ela será simplesmente atualizada.
- corresponde a
sudo ipset -exist add ssh_dynblock4 $IP
(onde $IP
contém o endereço IP de origem do pacote)
Se você deseja corresponder ao endereço de destino / destino, use em dst
vez de src
. Consulte o manual para mais opções.
Conjuntos de conjuntos
Os conjuntos de IPs podem conter outros conjuntos. Agora, se você seguiu o artigo até aqui, terá se perguntado se é possível combinar conjuntos. E é claro que é. Para os conjuntos de IPs acima, podemos criar dois conjuntos de juntas ssh_dynblock
e, ssh_loggedon
respectivamente, para conter os conjuntos somente de IPv4 e somente IPv6:
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
# Sets of sets
create ssh_loggedon list:set
create ssh_dynblock list:set
# Populate the sets of sets
add ssh_loggedon ssh_loggedon4
add ssh_loggedon ssh_loggedon6
add ssh_dynblock ssh_dynblock4
add ssh_dynblock ssh_dynblock6
E a próxima pergunta que deve aparecer em sua mente é se isso nos permite combinar e manipular conjuntos de IP de maneira independente de versão de IP.
E a resposta para isso é retumbante: SIM! (infelizmente, isso não foi documentado explicitamente da última vez que verifiquei)
Consequentemente, as regras da seção anterior podem ser reescritas para ler:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
o que é muito mais conciso. E sim, isso é experimentado e testado e funciona como um encanto.
Juntando tudo: defesa da força bruta do SSH
Nos meus servidores, eu tenho um script executado como um cron
trabalho que pega vários nomes de host e os resolve para endereços IP, depois alimenta-o no conjunto de IPs para "hosts confiáveis". A idéia é que os hosts confiáveis obtenham mais tentativas de logon no servidor e não sejam necessariamente bloqueados pelo tempo que mais alguém.
Por outro lado, países inteiros impediram de se conectar ao meu servidor SSH, com a exceção (potencial) de hosts confiáveis (ou seja, a ordem das regras é importante).
No entanto, isso é deixado como um exercício para o leitor. Aqui, eu gostaria de adicionar uma solução elegante que utilizará os conjuntos contidos no ssh_loggedon
conjunto para permitir que tentativas subseqüentes de conexão sejam realizadas e não sejam sujeitas ao mesmo escrutínio que os outros pacotes.
É importante lembrar os tempos limite padrão de 90 minutos ssh_loggedon
e 30 minutos ssh_dynblock
ao observar as seguintes iptables
regras:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m set --match-set ssh_loggedon src -j ACCEPT
-A SSH -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
Agora, você deve se perguntar como o endereço IP de conexão termina nos ssh_loggedon
subconjuntos. Então continue a ler ...
Bônus: adicionando o IP com o qual você efetua login durante o logon SSH
Se você já fez experiências com sshrc
amigos, já aprendeu suas deficiências. Mas o PAM vem em socorro. Um módulo chamado pam_exec.so
permite invocar um script durante o logon SSH em um ponto em que sabemos que o usuário é admitido.
No /etc/pam.d/sshd
abaixo do pam_env
e pam_selinux
entradas adicione a seguinte linha:
session optional pam_exec.so stdout /path/to/your/script
e verifique se a sua versão do script ( /path/to/your/script
acima) existe e é executável.
O PAM usa variáveis de ambiente para comunicar o que está acontecendo, para que você possa usar um script simples como este:
#!/bin/bash
# When called via pam_exec.so ...
SETNAME=ssh_loggedon
if [[ "$PAM_TYPE" == "open_session" ]] && [[ -n "$PAM_RHOST" ]]; then
[[ "x$PAM_RHOST" != "x${PAM_RHOST//:/}" ]] && SETNAME="${SETNAME}6" || SETNAME="${SETNAME}4"
ipset -exist add $SETNAME "$PAM_RHOST"
fi
Infelizmente, o ipset
utilitário não parece ter as esperanças internas do netfilter. Portanto, precisamos distinguir entre o conjunto de IPs IPv4 e IPv6 ao adicionar nossa entrada. Caso contrário ipset
, assumiremos que queremos adicionar outro conjunto ao conjunto de conjuntos, em vez do IP. E é claro que é improvável que exista um conjunto com o nome de um IP :)
Portanto, verificamos :
o endereço IP e anexamos 6
ao nome do conjunto nesse caso ou 4
não.
O fim.