AccueilFAQStatistiquesDiversContact

Linux : utiliser iptables pour bloquer les chaines de caractères (2e partie)

-=[Mise à jour 04/04/09]=-

Toujours dans notre "croisade anti-w00tw00t", nous allons voir comment utiliser les modules Recent et TCP d'iptables pour finaliser notre règle pour la rendre plus performante et précise.

L'utilisation seule du module string peut se révéler, comme nous l'avons vu dans la première partie, terriblement efficace pour contrer les scanners peu malins comme DFind, mais elle n'est pas à mettre entre toutes les mains car elle a le désavantage d'être brutale et peu selective (et souvent peu recommandée) :

  • elle filtre tous les paquets entrants par le port 80, sans se soucier de quels types de paquets il s'agit.
  • elle peut entrainer des erreurs ("faux-positifs"). Bien que les chances soient très minimes, elles doivent être prises en compte.

Le concept :

Nous allons donc établir une règle qui ne va filtrer que le paquet qui nous intéresse, en l'occurence le premier contenant la requête HTTP (GET, POST...) et de ce fait pallier aux inconvénients du module string utilisé seul. Tous les autres paquets de la connexion seront ignorés.

Il faut comprendre comment fonctionne l'établissement d'une connexion TCP :

  • le client se connecte au serveur en envoyant un paquet SYN (Synchronize).
  • le serveur répond en envoyant un paquet SYN+ACK (Synchronize + Acknowledgement).
  • le client répond avec un paquet ACK (Acknowledgement).
  • la communication est dès lors établie, le client peut envoyer les données PSH+ACK (Push + Acknowledgement), le serveur va y répondre et ainsi de suite jusqu'à la fermeture de la connexion.

Le module 'TCP' et le paramètre --tcp-flags :

Pour trouver le paquet, nous allons identifier les différentes étapes de la connexion avec le module TCP et son paramètre --tcp-flags :


  # iptables -p tcp --help

  TCP v1.3.8 options:
 --tcp-flags [!] mask comp     match when TCP flags & mask == comp
                               (Flags: SYN ACK FIN RST URG PSH ALL NONE)

mask correspond aux flags qui ne doivent pas être activés, comp aux flags qui doivent l'être. Il peut y avoir un seul flag ou plusieurs et dans ce dernier cas, ils doivent être séparés par une virgule.

Exemple : pour déterminer si un paquet provenant de notre client correspond à une séquence ACK, nous devons rechercher un paquet avec ACK activé mais sans PSH,SYN,ACK et entrant sur le port 80 :


  # iptables -A INPUT -p tcp --tcp-flags PSH,SYN,ACK ACK --dport 80 -j ACCEPT

Nous sommes donc capables à présent de déterminer le type des paquets entrants ou sortants en vérifiant leur(s) flag(s).

Le module 'Recent' :

Le dernier problème a résoudre : à chaque transmission de paquets une réponse est envoyée pour confirmer la bonne réception et donc, durant toute la connexion, nous allons envoyer/recevoir de nombreux paquets PSH+ACK. Puisque seul le premier nous intéresse, nous allons utiliser le module 'Recent match' :


  # iptables -m recent --help

    --set          Add source address to list, always matches.
    --update       Match if source address in list, also update last-seen time.
    --remove       Match if source address in list, also removes that address from list.
    --name name    Name of the recent list to be used. DEFAULT used if none given.
    ...
    ...


Ce module est très complexe et dispose de nombreux autres paramètres mais seuls les 4 ci-dessus nous concernent. Il permet de créer une liste ou nous pouvons enregistrer l'IP et le timestamp de chaque étape de la connexion en cours, et permet de suivre tout événement lié aux paquets reçus et/ou envoyés. La liste peut être personnalisée (paramètre '--name') ou bien être celle par défaut (DEFAULT).
  • --set : nous l'utiliserons pour créer la liste dès la début de la connexion (SYN).
  • --update : nous l'utiliserons pour mettre à jour la liste durant le SYN+ACK et puis le ACK.
  • --remove : nous l'utiliserons pour supprimer notre liste dès la réception de notre paquet contenant la requête HTTP afin d'ignorer tous les paquets suivants.


 #!/bin/bash

 # juste pour le test ! on flush toutes les règles...
 iptables -F
 # et supprime toutes les chaînes :
 iptables -X

 # création de notre chaîne w00t :
 iptables -N w00t

 # redirige les paquets sur notre chaîne
 # (localhost juste pour le test) :
 iptables -A INPUT -d 127.0.0.1 -j w00t

 # on recherche le premier paquet qui ne doit contenir que le flag SYN activé
 # ( il est aussi possible d'utiliser '--syn' à la place de '--tcp-flags ALL SYN')
 # et on créé notre liste avec '--set' :
 iptables -A w00t -m recent -p tcp --tcp-flags ALL SYN --dport 80 --set

 # on recherche le paquet SYN,ACK suivant et on met à jour la liste
 # si c'est lui avec '--update' (c'est un paquet sortant donc '--sport 80'
 # et non plus '--dport 80') :
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK SYN,ACK --sport 80 --update

 # on attend le paquet ACK du client et on met à jour la liste
 # dès sa réception :
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK ACK --dport 80 --update

 # à partir de maintenant, la connexion est établie :

 # on cherche un paquet avec les flags PSH,ACK. Le premier sera celui contenant
 # la reqête HTTP (GET, POST, HEAD...). Dès qu'on le trouve, on supprime notre liste
 # avec '--remove' afin que tous les paquets suivants soient ignorés par nos règles :
 iptables -A w00t -m recent -p tcp --tcp-flags ACK,PSH PSH,ACK --dport 80 --remove

Pour s'assurer que tout fonctionne parfaitement, on va tester en local avec un appel à la page HTML que vous êtes en train de lire soit un total de 7 requêtes (1 page HTML + 1 fichier CSS + 1 fichier JS + 4 images) puis nous vérifierons que les paquets ont été correctement interceptés/ignorés suivant nos règles.

Les détail de la requête HTTP avec Wireshark :

Total : 27 paquets
SYN : 2 paquets (No 1 et 13)
SYN, ACK : 2 paquets (No 2 et 14)
ACK (entrants) : 6 paquets (No 3, 7, 15, 19, 26 et 27)
Les requêtes HTTP qui nous interessent et que nous souhaitons filtrer : 7 paquets (No 4, 8, 11, 16, 20, 22 et 24) qui correspondent aux GET de la page HTML, des fichiers CSS et JS ainsi que des 4 images.
Les autres paquets ne nous intéressent pas.

Verifions avec iptables comment le filtrage des paquets s'est déroulé :


 # iptables -L -nvx

 Chain INPUT
  pkts  bytes target  ...
   27   18596 w00t    ...

 Chain w00t (1 references)
  pkts  bytes target  ...
    2     120         ...recent:SET name:DEFAULT side:source tcp dpt:80 flags:0x3F/0x02
    2     120         ...recent:UPDATE name:DEFAULT side:source tcp spt:80 flags:0x1A/0x12
    6     312         ...recent:UPDATE name:DEFAULT side:source tcp dpt:80 flags:0x1A/0x10
    7    5412         ...recent:REMOVE name:DEFAULT side:source tcp dpt:80 flags:0x18/0x18

On voit qu'il y a eu un total de 27 paquets transmis et nos règles ont intercepté séparément 2 SYN (0x02), 2 SYN+ACK (0x12), 6 ACK (0x10) et enfin notre 4e règle a uniquement intercepté le premier paquet PSH+ACK (0x18) qui nous intéresse de chacune des 7 requêtes.
Les 10 autres paquets ont bel et bien été ignorés !

Finalisation :

Maintenant que nous sommes capables d'isoler le paquet de la requête HTTP, il ne reste plus qu'à le filter avec l'aide du module string :


 #!/bin/bash

 # création de notre chaîne w00t :
 iptables -N w00t

 # redirige les paquets TCP sur notre chaîne :
 iptables -A INPUT -p tcp -j w00t

 # recherche du premier SYN et création de la liste :
 iptables -A w00t -m recent -p tcp --syn --dport 80 --set

 # recherche du paquet SYN,ACK et mise à jour la liste :
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK SYN,ACK --sport 80 --update

 # recherche du paquet ACK et mise à jour la liste
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK ACK --dport 80 --update

 # recherche de la signature de DFind dans le prenier PSH+ACK.
 # Si elle est présente, on DROP.
 # On supprime la liste pour ne pas filtrer les paquets suivants
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,ACK PSH,ACK --dport 80 --remove \
 -m string --to 70 --algo bm --string "GET /w00tw00t.at.ISC.SANS." -j DROP

Il est possible de rajouter une ou plusieurs autres règles de filtrage sur ce même paquet, mais dans ce cas il faut s'assurer que seule la dernière supprime la liste (--remove), les précédentes ne faisant que la mettre à jour (--update).
Exemple en ajoutant une règle filtrant sur l'adresse IP du serveur dans le champ 'Host:' comme nous l'avions vu dans le premier article :


 # 1ere règle sur le paquet + MAJ de la liste :
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,ACK PSH,ACK --dport 80 --update \
 -m string --to 70 --algo bm --string "GET /w00tw00t.at.ISC.SANS." -j DROP

 # 2e règle sur même paquet + suppression de la liste :
 iptables -A w00t -m recent -p tcp --tcp-flags PSH,ACK PSH,ACK --dport 80 --remove \
 -m string --to 700 --algo bm --string 'Host: xxx.xxx.xxx.xxx' -j DROP



Mise à jour 04/04/09 :

Suite aux commentaires (en bas de cet article) et aux nombreux logs qui m'ont été envoyés par certains lecteurs, voici une règle qui me semble être plus efficace pour contrer la plupart des éventualités :

  • recherche élargie :
  • certain script-kiddies modifient la chaîne "GET /w00tw00t.at.ISC.SANS." avec un éditeur hexadécimal (ex : "GET /test.w00t:)"). Pour contrer ce problème, le filtrage pourrait simplement porter sur le fait que la requête n'est pas conforme au protocole HTTP v1.1 et se baser sur la séquence "HTTP/1.1" + 2 retours à la ligne (CR/LF/CR/LF) qui sera convertie en hexadécimale avec le paramètre --hex-string. Pour plus de détails sur ce point, consultez les commentaires en bas de cet article et ceux de la première partie de l'article.
  • blacklist de l'IP :
  • à chaque fois que DFind sera détecté, il sera blacklisté pendant 6 heures grâce au module ipt_recent d'iptables. Il devra se faire complètement oublier car s'il revient avant l'expiration du délais, les 6 heures seront à nouveau ajoutées (paramètres --update).
  • reset de la connexion :
  • au lieu de dropper le paquet, il sera rejeté et la connexion immédiatement terminée (-p tcp -j REJECT --reject-with tcp-reset). L'utilisation de DROP n'est finalement pas très intéressante ici car le blocage intervient après la séquence d'établissement de la connexion et de ce fait, le scanner sait qu'il y a un serveur HTTP connecté. Il est donc inutile de vouloir lui faire croire le contraire, ni même de perdre du temps.

    Règle "générique" anti-w00tw00t :

    
     #!/bin/bash
    
     # cette partie est à mettre au tout début de vos règles :
    
     # accepte loopback
     iptables -A INPUT -i lo -j ACCEPT
    
     # vérifie si l'IP est déjà présente dans la liste w00tlist.
     # Si c'est la cas, on la rejette immédiatement, met à jour la liste et
     # l'attaquant est de nouveau blacklisté pour 6h :
     iptables -A INPUT -p tcp -m recent --name w00tlist --update --seconds 21600 -j DROP
    
     # création de la chaine w00tchain qui rajoute l'IP
     # à la liste w00tlist et reset la connexion (ne pas oublier le paramètre
     # '-p tcp' indispensable pour l'utilisation de '--reject-with tcp-reset') :
     iptables -N w00tchain
     iptables -A w00tchain -m recent --set --name w00tlist -p tcp \
     -j REJECT --reject-with tcp-reset
    
     # création de notre chaîne w00t :
     iptables -N w00t
    
     # redirige les paquets TCP sur notre chaîne :
     iptables -A INPUT -p tcp -j w00t
    
     #####################################################
     # mettez ici vos propres règles iptables :
     # accepte les connexions établies, etc :
     iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
     ...
     ...
     ...
    
     #####################################################
    
     # chaîne w00t :
     # recherche du premier SYN et création de la liste :
     iptables -A w00t -m recent -p tcp --syn --dport 80 --set
    
     # recherche du paquet SYN,ACK et mise à jour la liste :
     iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK SYN,ACK --sport 80 --update
    
     # recherche du paquet ACK et mise à jour la liste :
     iptables -A w00t -m recent -p tcp --tcp-flags PSH,SYN,ACK ACK --dport 80 --update
    
     # recherche de la signature hexadécimale dans le prenier PSH+ACK.
     # Si elle est présente, on renvoie sur w00tchain pour blacklister et
     # terminer la connexion.
     # On supprime la liste pour ne pas filtrer les paquets suivants :
     iptables -A w00t -m recent -p tcp --tcp-flags PSH,ACK PSH,ACK --dport 80 --remove \
     -m string --to 80 --algo bm --hex-string '|485454502f312e310d0a0d0a|' -j w00tchain
    
    
    




    Une fois les règles en place il est possible d'avoir un apperçu des paquets et nombres d'octets rejetés avec iptables :
     # iptables -L w00t -nvx 

    Et, dans le cas de la dernière règle, pour voir les IP blacklistées :

     # cat /proc/net/ipt_recent/w00tlist

    Conclusion

    Il est possible de filter d'autres ports (SSH etc) par exemple pour bloquer des tentatives de buffer overflow (--hex-string '|90 90 90 90 90 90|'). Cependant, n'essayez pas de faire un filtrage systématique sur n'importe quelle chaîne de caractère ( ex : filtrage anti-spam en filtrant les emails entrant par votre port 25 !) car non seulement ce n'est pas le but d'iptables, mais en plus il serait toujours possible pour un attaquant de découper ces chaînes dans plusieurs paquets TCP et donc d'échapper à la détection.

    Comme d'habitude, n'oubliez pas le plus important : RTFM

    
     # man iptables
    
    
    Ou, pour un paramètre spécifique :
    
    # iptables [PARAMETRE] --help