Imaginemos por un momento que tenemos un servidor web con un sitio abierto al mundo entero. No va a tener que pasar mucho tiempo para que notemos que más de uno intentará descubrir vulnerabilidades y si tiene suerte, aprovecharse de ellas para hacerse con las suyas. En este artículo pondré como ejemplo un simple sitio desarrollado con Drupal cuyo log se registra en un archivo y no solo en la base de datos como en una configuración predeterminada. Pero eso no quiere decir que este artículo no sirva como referencia para controlar cualquier otro servicio, basta que tenga un log que registre quien (dirección IP) ha hecho una cosa que podriamos definir como "indevida". Este servidor se encuentra detrás de un pfSense (LAN) configurado para natear los puertos 80 (http) y 443 (https). De este modo, nuestro servidor web es accesible desde el exterior (WAN).
La acción predeterminada de Fail2ban es agregar una regla de firewall en el mismo servidor web bloqueando así el acceso a la misma. Pero, por qué hacer que se ocupe el mismo servidor web de bloquear a los mal intencionados y no directamente pfSense? Ese es el objetivo de este artículo. Que Fail2ban le diga a pfSense: "Che, no traigas más a esta gente".
Para lograr que pfSense bloquee el acceso a las direcciones IP registradas en drop.txt podemos hacer lo siguiente:
Como ya dije antes, en este caso queremos bloquear una dirección después de que haya generado n errores de tipo 404 en cierta cantidad de tiempo. Para eso, creamos un archivo en /etc/fail2ban/filter.d que llamaremos drupal-404.conf con el siguiente contenido:
[Definition]
failregex = ^%(__prefix_line)s(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/[\w\.-]+)*\|\d{10}\|page not found\|<host>\|.+\|.*\|\d\|.*\|.*$
ignoreregex =
Luego, agregamos al archivo /etc/fail2ban/jail.conf las siguientes lineas:
[drupal-404]
enabled = true
port = http,https
logpath = /var/log/drupal.log
backend = %(syslog_backend)s
action = drop
En el mismo archivo podremos manejar los criterios de bloqueo con las variables bantime, findtime, maxretry y maxmatches. Una vez hecho esto, vamos a crear la acción drop.
Para crear una acción de Fail2ban que se ocupe de registrar las direcciones a bloquear tendremos que crear un archivo en /etc/fail2ban/action.d. Lo llamaremos drop.conf
[Definition]
actionban = /usr/bin/php /etc/fail2ban/action.d/drop.php <ip> <name>
actionunban = /usr/bin/php /etc/fail2ban/action.d/undrop.php <ip> <name>
Como vemos, esta acción ejecuta un script para prohibir (escribiendo la dirección IP en un archivo) y otro para permitir (eliminando una dirección IP del archivo). Estos dos scripts los hice en PHP.
El primer script se encargará de:
El primer punto no merece una explicación. El segundo ejecutará a través de SSH (desde el servidor web deberíamos poder acceder a pfSense sin contraseña y con el usuario root) el siguiente comando:
/usr/bin/nice -n20 /etc/rc.update_urltables now forceupdate fail2ban
Y por último, con el comando pfctl eliminaremos las conexiones establecidas con la dirección que queremos bloquear utilizando la opción -k.
<?php
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
date_default_timezone_set("Europe/Rome");
$date = date('l jS \of F Y h:i:s A');
$file = "/var/www/stock/www/drop.txt";
$ip = $argv[1];
if( ip2long($ip) == false) {
exit();
}
$fp = fopen($file, 'a');
fwrite($fp, $ip . " ; date: " . $date . "\n");
// reemplazar pfsense con la dirección IP de pfsense
shell_exec("ssh root@pfsense /usr/bin/nice -n20 /etc/rc.update_urltables now forceupdate fail2ban");
shell_exec("ssh root@pfsense pfctl -k " . $ip);
El segundo script es mucho más simple. Se ocupará de:
<?php
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
date_default_timezone_set("Europe/Rome");
$date = date('l jS \of F Y h:i:s A');
$file = "/var/www/stock/www/drop.txt";
$ip = $argv[1];
$drop = "";
$handle = @fopen($file, "r");
if ($handle) {
while (($buffer = fgets($handle, 4096)) !== false) {
if(strpos($buffer, $ip) !== 0) {
$drop .= $buffer;
}
}
if (!feof($handle)) {
echo "Error: unexpected fgets() fail\n";
}
fclose($handle);
}
// now write to file
file_put_contents($file, $drop);
// refresh fail2ban alias
// reemplazar pfsense con la dirección IP de pfsense
shell_exec("ssh root@pfsense /usr/bin/nice -n20 /etc/rc.update_urltables now forceupdate fail2ban");