Hacer que Fail2ban nateado le diga a un pfSense que tráfico no tiene que dejar pasar

  • 13 Ago 2021
  • Fail2ban, pfSense

Escenario

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).

Objetivo

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".

Planificación

  1. Crear una acción en Fail2ban para guardar en un archivo las direcciones a bloquear y que pueda también desbloquearlas.
  2. Que ese archivo pueda ser leído por pfSense.
  3. Que pfSense tenga una regla que se ocupe de bloquear el acceso a todas las direcciones IP registradas por Fail2ban.

Ejecución

Crear la regla en pfSense

Para lograr que pfSense bloquee el acceso a las direcciones IP registradas en drop.txt podemos hacer lo siguiente:

  1. Crear un alias de nombre fail2ban de tipo URL Table (IPs) indicando midominio.com/drop.txt
  2. Crear una regla de firewall que se encargue de bloquear (action: block) el tráfico en la WAN utilizando como origen el alias creado en el punto 1

Crear un filtro de Fail2ban para Drupal

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.

Crear la acción de Fail2ban

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:

  1. Escribir en un archivo de texto accesible a pfSense la dirección IP a bloquear.
  2. Pedirle a pfSense que actualice el alias fail2ban.
  3. Pedirle a pfSense que elimine posibles conexiones ya establecidas con la dirección IP que queremos bloquear.

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:

  1. Eliminar la drección IP que ya no necesitamos bloquear del archivo de texto
  2. Pedirle a pfSense que actualice el alias fail2ban
<?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");