Les ensembles IP revisités
Il existe déjà une réponse mentionnant les ensembles IP. Cependant, il est plutôt unidimensionnel dans la mesure où il se concentre sur les gains de performances par rapport aux règles classiques et sur le fait que les ensembles IP atténuent le problème rencontré avec de nombreuses adresses IP individuelles qui ne peuvent pas être facilement exprimées sous forme de sous-réseau dans la notation CIDR.
Notation utilisée ci-dessous
Pour que ipset
je vais utiliser la notation lue par ipset restore
écrit et par ipset save
.
De manière correspondante pour iptables
(et ip6tables
) les règles, je vais utiliser la notation lue iptables-restore
et écrite par iptables-save
. Cela donne une notation plus courte et me permet de mettre en évidence les règles potentielles uniquement IPv4 (préfixées -4
) ou IPv6 uniquement (préfixées -6
).
Dans certains exemples, nous allons transférer le flux de paquets dans une autre chaîne. La chaîne est supposée exister à ce point, ainsi les lignes pour créer les chaînes ne sont pas produites (ni le nom de la table ni les commandes COMMIT
-ted à la fin).
Ensembles IP avancés
Les ensembles IP peuvent faire beaucoup plus que ce qui a été mentionné dans la réponse précédente et vous devez absolument lire la documentation de cet ensemble IP ( ipset(8)
) ainsi que iptables-extensions(8)
cette brève entrée ici.
Par exemple , je vais me concentrer principalement sur trois types de set: hash:ip
, hash:net
et list:set
, mais il y a plus de ceux -ci et ils ont tous les cas d'utilisation valides.
Vous pouvez par exemple également faire correspondre les numéros de port, pas seulement les adresses IP .
Sauvegarde et restauration des ensembles IP comme avec iptables-save
etiptables-restore
Vous pouvez créer des déclarations d'ensemble IP en bloc et les importer en les canalisant dans ipset restore
. Si vous voulez rendre votre commande plus résistante aux entrées existantes, utilisez ipset -exist restore
.
Si vos règles sont dans un fichier appelé, default.set
vous utiliseriez:
ipset -exist restore < default.set
Un fichier comme celui-ci peut contenir des entrées dans des create
ensembles et des add
entrées. Mais généralement, la plupart des commandes de la ligne de commande semblent avoir une version correspondante dans les fichiers. Exemple (création d'un ensemble de serveurs 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
Ici, un ensemble est créé pour IPv4 ( dns4
) et un autre pour IPv6 ( dns6
).
Délais d'attente sur les postes IP
Les délais dans les ensembles IP peuvent être définis par défaut par ensemble et également par entrée. Ceci est très utile pour les scénarios dans lesquels vous souhaitez bloquer une personne temporairement (par exemple, pour analyser un port ou tenter de forcer brutalement votre serveur SSH).
Voici comment cela fonctionne (par défaut lors de la création d’ensembles 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
Nous reviendrons sur ces ensembles particuliers ci-dessous et sur les raisons de leur définition.
Si vous souhaitez définir votre délai d’expiration pour une adresse IP particulière, vous pouvez simplement dire:
add ssh_dynblock4 1.2.3.4 timeout 7200
Pour bloquer IP 1.2.3.4 pendant deux heures au lieu de la demi-heure par défaut (définie).
Si vous envisagiez cela ipset save ssh_dynblock4
après un moment, vous verriez quelque chose comme:
create ssh_dynblock4 hash:ip family inet hashsize 1024 maxelem 65536 timeout 1800
add ssh_dynblock4 1.2.3.4 timeout 6954
Mises en garde de délai
- les délais d'attente sont une caractéristique de n'importe quel ensemble. Si le jeu n'a pas été créé avec une prise en charge du délai d'attente, vous recevrez une erreur (par exemple
Kernel error received: Unknown error -1
).
- les délais d'attente sont donnés en secondes. Utilisez les expressions arithmétiques Bash pour passer de minutes en secondes, par exemple. Par exemple:
sudo ipset add ssh_dynblock4 1.2.3.4 timeout $((120*60))
Vérifier si une entrée existe dans un ensemble IP donné
Dans vos scripts, il peut être utile de voir si une entrée existe déjà. Ceci peut être réalisé avec ipset test
qui renvoie zéro si l'entrée existe et différent de zéro sinon. Donc, les vérifications habituelles peuvent être appliquées dans un script:
if ipset test dns4 8.8.8.8; then
echo "Google DNS is in the set"
fi
Cependant, dans de nombreux cas, vous souhaiterez plutôt utiliser le -exist
commutateur to ipset
afin de ne pas se plaindre des entrées existantes.
Remplir des ensembles IP à partir de iptables
règles
Ceci, à mon avis, est l’une des caractéristiques les plus meurtrières des ensembles IP. Vous pouvez non seulement faire correspondre les entrées d'un ensemble IP, vous pouvez également ajouter de nouvelles entrées à un ensemble IP existant.
Par exemple, dans cette réponse à cette question, vous avez:
-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
... avec l'intention de limiter le nombre de tentatives de connexion à SSH (port TCP 22). Le module utilisé recent
garde une trace des tentatives de connexion récentes. Au lieu du state
module, je préfère cependant le conntrack
module.
# 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
Dans ce cas, je redirige le flux vers la SSH
chaîne de sorte que je n'ai pas à me répéter -p tcp --dport ssh
pour chaque règle.
Recommencer:
-m set
fait iptables
savoir que nous utilisons des commutateurs du set
module (qui gère les ensembles IP)
--match-set ssh_dynblock4 src
indique de iptables
faire correspondre l' adresse source ( src
) à l'ensemble nommé ( ssh_dynblock4
)
- cela correspond à
sudo ipset test ssh_dynblock4 $IP
(où $IP
contient l'adresse IP source du paquet)
-j SET --add-set ssh_dynblock4 src --exist
ajoute ou met à jour l' adresse source ( src
) du paquet dans l'ensemble IP ssh_dynblock4
. Si une entrée existe ( --exist
), elle sera simplement mise à jour.
- cela correspond à
sudo ipset -exist add ssh_dynblock4 $IP
(où $IP
contient l'adresse IP source du paquet)
Si vous souhaitez faire correspondre l'adresse de destination / destination à la place, vous utiliserez à la dst
place de src
. Consultez le manuel pour plus d'options.
Ensembles d'ensembles
Les ensembles IP peuvent contenir d'autres ensembles. Maintenant, si vous avez suivi l'article jusqu'à présent, vous vous êtes déjà demandé s'il était possible de combiner des ensembles. Et bien sûr que ça l'est. Pour les ensembles IP ci-dessus, nous pouvons créer deux ensembles conjoints ssh_dynblock
et ssh_loggedon
contenir respectivement les ensembles IPv4 uniquement et IPv6 uniquement:
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
Et la question suivante qui devrait vous être posée est de savoir si cela nous permet de faire correspondre et de manipuler les ensembles IP de manière indépendante de la version IP.
Et la réponse à cela est un retentissant: OUI! (hélas, cela n'a pas été documenté explicitement la dernière fois que j'ai vérifié)
Par conséquent, les règles de la section précédente peuvent être réécrites comme suit:
-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
ce qui est beaucoup plus concis. Et oui, cela a fait ses preuves et fonctionne à merveille.
Rassembler tout cela: la défense de force brute SSH
Sur mes serveurs, j'ai un script exécuté comme un cron
travail qui prend un tas de noms d'hôtes et les résout en adresses IP, puis les alimente dans l'IP défini pour les "hôtes approuvés". L'idée est que les hôtes de confiance ont plus de tentatives de connexion au serveur et ne sont pas nécessairement bloqués aussi longtemps que quiconque.
À l'inverse, des pays entiers sont empêchés de se connecter à mon serveur SSH, à l'exception (potentielle) des hôtes de confiance (c'est-à-dire que l'ordre des règles est important).
Cependant, cela reste comme un exercice pour le lecteur. Ici, je voudrais ajouter une solution soignée qui utilisera les ensembles contenus dans l' ssh_loggedon
ensemble pour permettre aux tentatives de connexion ultérieures d'être transmises et non soumises au même contrôle que les autres paquets.
Il est important de garder à l'esprit les délais d'expiration par défaut de 90 minutes ssh_loggedon
et de 30 minutes ssh_dynblock
lorsque vous examinez les iptables
règles suivantes :
-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
A présent, vous devriez vous demander comment l'adresse IP de connexion se retrouve dans les ssh_loggedon
sous-ensembles. Alors lisez la suite ...
Bonus: ajout de l'IP avec laquelle vous vous connectez lors de la connexion SSH
Si vous avez expérimenté avec des sshrc
amis, vous aurez appris ses inconvénients. Mais PAM vient à la rescousse. Un module nommé pam_exec.so
nous permet d’appeler un script lors de la connexion SSH à un moment où nous savons que l’utilisateur est admis.
En /etc/pam.d/sshd
dessous des entrées pam_env
et, pam_selinux
ajoutez la ligne suivante:
session optional pam_exec.so stdout /path/to/your/script
et assurez-vous que votre version du script ( /path/to/your/script
ci-dessus) existe et est exécutable.
PAM utilise des variables d'environnement pour communiquer ce qui se passe, vous pouvez donc utiliser un script simple comme celui-ci:
#!/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
Malheureusement, l' ipset
utilitaire ne semble pas avoir l'intelligence intégrée de netfilter. Nous devons donc faire la distinction entre IPv4 et IPv6 lorsque vous ajoutez notre entrée. Sinon ipset
, supposons que nous voulions ajouter un autre ensemble à l'ensemble des ensembles, au lieu de l'adresse IP. Et bien sûr, il est peu probable qu'il y ait un ensemble nommé d'après une adresse IP :)
Nous vérifions donc :
l'adresse IP et ajoutons 6
le nom de l'ensemble dans ce cas et dans les autres cas 4
.
La fin.