AccueilFAQStatistiquesDiversContact

Sécurité : Linux : faire communiquer mod_security et iptables pour bloquer les IP en temps réel

iptables et mod_security sont deux applications très utiles pour protéger un serveur mais malheureusement ne peuvent pas communiquer ensembles, mod_security étant un module d'apache il hérite bien entendu des privilèges de celui-ci.
Il est cependant très facile de pallier à ce problème avec un simple petit client/serveur en Perl.

Fonctionnement :

Lors du filtrage des données, mod_security peut exécuter plusieurs actions, notamment la redirection ou l'exécution d'un programme. C'est cette dernière que nous allons utiliser.
mod_security va lancer un petit script Perl (le client) et lui passer l'adresse IP à bloquer par l'intermédiaire de la variable d'environnement $ENV{'REMOTE_ADDR'}. Puisque ce script devra lui-aussi avoir les même privilèges (apache) et sera donc bien incapable d'accéder à iptables, il va simplement se connecter à un autre script (le serveur) et c'est celui-ci qui se chargera de faire la liaison avec iptables pour bloquer l'IP. Le serveur n'a pas besoin d'être un daemon et pourra simplement être lancé avec xinetd.

Pour les exemples ci-dessous, nous supposerons que le répertoire de votre site www est /var/www/httpd.

Le client :

Il sera placé à la racine de votre répertoire HTTP et ne pas pouvoir être accessible directement depuis l'extérieur (avec un navigateur par exemple).
Il sera chmodé en 0755 et aura les droits d'apache comme ci-dessous pour pouvoir être lancé par mod_security :


 /var
   |---/www
     |--- modsec2ipt.pl    -rwxr-xr-x    www-data www-data
     |--- /cgi-bin
     |--- /httpd


 Important : ne tentez jamais d'autoriser Apache à accéder au firewall directement (sudoers, modification des privilèges etc)  : en cas de vulnérabilité d'un script de votre site, un attaquant pourrait agir directement sur iptables comme bon lui semble ! Dans le cas de ce script, si l'attaquant avait la possibilité d'y accéder il serait immédiatement blacklisté (le script n'accepte aucun argument et envoie l'IP du client à iptables).

Le script du client (modsec2ipt.pl) :


#!/usr/bin/perl
###########################################################
# modsec2ipt.pl
#
# client lancé par mod-security pour se connecter
# au serveur chargé de blacklister les IP
#
###########################################################

# port sur lequel écoute le serveur modsec2ipt_srv.pl :
$port = '54545';

###########################################################

use Socket;

# l'IP est envoyée par mod_security :
$USER_IP=$ENV{'REMOTE_ADDR'};

goto QUIT if (!$USER_IP);

eval {
   local $SIG{ALRM} = sub { die "alarm\n" };
   alarm 10;
   $proto = getprotobyname('tcp');
   socket(SOCK, PF_INET, SOCK_STREAM, $proto);
   $iaddr = gethostbyname('127.0.0.1');
   $sin = pack('Sna4x8', AF_INET, $port, $iaddr);
   connect(SOCK, $sin);
   recv SOCK, $res, 512, 0;
   alarm 0;
};
goto QUIT if ($@ =~ /alarm/);
goto CLOSE if ($res !~ /^OK/);

# envoie l'IP :
send SOCK, "$USER_IP\r\n", 0;
recv SOCK, $res, 512, 0;
CLOSE:
send SOCK, "QUIT\r\n", 0;
recv SOCK, $res, 100, 0;
close SOCK;

QUIT:
# STDOUT nécessaire pour mod_security :
print ".";
exit;

Règles de mod_security :

Il est nécessaire de rajouter l'exécution du client dans les règles de mod_security.
Nous allons prendre comme exemple une règle qui bloque une requête que l'on retrouve souvent dans les logs de nos serveurs :

  GET /sumthin HTTP/1.0
Nous rajoutons l'exécution de notre client avec le paramètre exec.
- Pour mod_security v1.x :

  SecFilter "^GET \/sumthin" "log,exec:/var/www/modsec2ipt.pl"

- Pour mod_security v2.x :

  SecRule REQUEST_URI "\/sumthin" "log,exec:/var/www/modsec2ipt.pl"

Relancez apache pour qu'il prenne en compte ces nouvelles règles.

Le serveur :

Le script du serveur, modsec2ipt_srv.pl téléchargeable en bas de cet article, doit être copié dans le répertoire /usr/bin et chmodé en 0755 avec les droits de root (root:root).
Il sera lancé par xinetd. Si ce dernier n'est pas installé sur votre machine, c'est une bonne occasion de le faire :

  apt-get install xinetd

Il faut maintenant créér un fichier /etc/xinetd.d/modsec2ipt dans lequel on rajoute les lignes suivantes :

  service modsec2ipt
  {
    flags         = NAMEINARGS
    socket_type   = stream
    protocol      = tcp
    wait          = no
    user          = root
    server        = /usr/sbin/tcpd
    server_args   = /usr/bin/modsec2ipt_srv.pl
    only_from     = 127.0.0.1
  }


Ouvrez le fichier /etc/services et rajoutez la ligne du port du serveur :

  modsec2ipt   54545/tcp


Relancez xinetd :

  # /etc/init.d/xinetd restart


Vérifiez que vous pouvez vous connecter au serveur :

  # telnet localhost 54545

  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  OK modsec2ipt serveur

Tapez 'QUIT' pour fermer la connexion.

Le serveur utilise par defaut une chaîne iptables nommée 'MODSEC2IPT'. Il se charge lui-même de la créer si elle n'existe pas. Le blocage des IP se fait uniquement sur le port HTTP (80). Libre à vous de modifier ces paramètres.

Le script du serveur (/usr/bin/modsec2ipt_srv.pl) :


#!/usr/bin/perl
###########################################################
# modsec2ipt_srv.pl
#
# serveur bloquant les IP avec iptables sur le port
# HTTP (80). utilise/créé la chaîne "MODSEC2IPT"
#
###########################################################
$chain = "MODSEC2IPT";
# log des IP bloquées :
$log = '/var/log/modsec2ipt.log';
# log des erreurs :
$error_log='/var/log/modsec2ipt_error.log';
###########################################################

# vérification de la présence de la chaîne :
`iptables -L $chain  >/dev/null 2>&1`;
$res = ( $? >> 8);
if ($res) {
   `iptables -N $chain 2>/dev/null`;
   `iptables -I INPUT -p tcp --dport 80 -j $chain 2>/dev/null`;
}
$date=`date '+%b %e %X'`;
chomp $date;
$|=1;
select(STDOUT);
print "OK, modsec2ipt serveur\x0D\x0A";
eval {
   local $SIG{ALRM} = sub { die "TIMEOUT\n" };
   alarm(10);
   while (<STDIN>){
      alarm(0);
      chomp;chop if /\r$/;
      if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
         `iptables -I $chain -s $_ -j DROP 2>/dev/null`;
         $res = ( $? >> 8);
         if ($res) {
            print "ERR #1\x0D\x0A";
            open LOG,">>$error_log";
            print LOG "$date impossible de bloquer [$_]\n";
            close LOG;
         }else{
            print "OK\r\n";
            open LOG,">>$log";
            print LOG "$date $_\n";
            close LOG;
         }
      }elsif (/^QUIT/i){
         print "OK\r\n";
         last;
      }else{ print "ERR : #2\x0D\x0A"; }
   }
};
print "ERR : timed-out\x0D\x0A" if( $@=~/TIMEOUT/ );

exit(0);

Test :

Lancez votre navigateur et entrez : http://votresite.com/sumthin
Puis, tentez de recharger la page. Normalement, vous devriez être bloqués :


 # iptables -L MODSEC2IPT -nvx

    Chain MODSEC2IPT (1 references)
    pkts      bytes target   prot opt in     out     source               destination
      18     5972 DROP       all  --  *      *       VOTRE_ADRESS_ IP        0.0.0.0/0

Flush des règles :

Il n'est pas vraiment concevable de laisser les IP bloquées indéfiniment. De ce fait, créez une tâche cron qui va régulièrement les effacer, par exemple toutes les 15 minutes :


   echo "14,29,44,59 * * * * root /sbin/iptables -F MODSEC2IPT >/dev/null" \
   >/etc/cron.d/modsec2ipt.cron

Téléchargement des scripts :

 modsec2ipt.tgz - 1.5 Ko