Esta pagina se ve mejor con JavaScript habilitado

libmodsecurity mediante Apache/Nginx bajo Gentoo

 ·  🎃 kr0m

LibmodSecurity es un WAF(Web Application Firewall), este nos permite detectar ciertos tipos de ataques en base a unas reglas predefinidas, mediante estas firmas podremos detectar inyecciones SQL, XSS, LFI, RFI, también podemos disponer de reglas específicas para cierto software como Wordpress, cPanel, osComerce, Joomla, además podemos cargar reglas del proyecto OWASP o incluso las nuestras propias.

Hasta la version 2.X el proyecto se llamaba modsecurity, este dependia mucho de Apache y las adaptaciones a otros servidores eran meros wrappers entorno al modo de funcionamiento nativo de Apache, esto suponía una penalización en el rendimiento en los servidores que no son Apache. Podemos encontrar un análisis mas detallado en este enlace.

A partir de la version 3 ya no se trata de un módulo si no de una librería, cada servidor web accede a esta mediante un conector en forma de módulo. Nosotros vamos a utilizar los conectores para Apache y Nginx:
https://github.com/SpiderLabs/ModSecurity
https://github.com/SpiderLabs/ModSecurity-nginx
https://github.com/SpiderLabs/ModSecurity-apache

La versión 2.X todavía es mantenida, pero no es recomendable utilizarla:
https://github.com/SpiderLabs/ModSecurity/tree/v2/master

NOTA: Si utilizamos Nginx como balanceador se puede instalar en este punto común y protegería al resto de servidores web.

Este manual se compone de varias partes:


Compilación e instalación de PHP

Empezamos compilando e instalando PHP, primero definiremos la versión de PHP:

vi /etc/make.conf

PHP_TARGETS="php7-3"
PHP_INI_VERSION="production"

Definimos las use flags que deseamos en PHP:

vi /etc/portage/package.use/php

dev-lang/php apache2 berkdb bzip2 cli crypt ctype curl curlwrappers exif fileinfo filter ftp gd gdbm hash iconv imap intl json mysql mysqli nls odbc pdo phar posix readline session simplexml soap sockets sqlite3 ssl sysvipc threads tokenizer unicode xml xmlreader xmlrpc xmlwriter zip zlib threads fpm cgi truetype bcmath

Añadimos la use flag fpm al eselect-php:

echo “app-eselect/eselect-php fpm” > /etc/portage/package.use/eselect-php

Compilamos PHP:

emerge -av dev-lang/php

Configuramos el pool del php-fpm:

[www]
user = nobody
group = nobody
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

Arrancamos el PHP-FPM y lo añadimos al arranque:

/etc/init.d/php-fpm start
rc-update add php-fpm default

Seleccionamos la versión de PHP que deseemos utilizar:

eselect php list fpm

 [1] php7.2 *
 [2] php7.3
eselect php set fpm 2

Reiniciamos el FPM:

/etc/init.d/php-fpm restart


Compilación e instalación de Apache + PHP

Ahora compilamos Apache con soporte para PHP-FPM:

vi /etc/make.conf

APACHE2_MPMS="worker"
APACHE2_MODULES="actions alias auth_basic authn_alias authn_anon authn_dbm authn_default authn_file authz_core authz_dbm authz_default authz_groupfile authz_host authz_owner authz_user autoindex cache cgi cgid dav dav_fs dav_lock deflate dir disk_cache env expires ext_filter file_cache filter headers include info log_config logio mem_cache mime mime_magic negotiation rewrite setenvif speling status unique_id userdir usertrack vhost_alias proxy socache_shmcb proxy_fcgi authn_core unixd proxy_http proxy_http2"
vi /etc/portage/package.use/apache
www-servers/apache threads
emerge -av www-servers/apache

Configuramos Apache para que cargue los módulos necesarios para ejecutar PHP a través de FPM:

vi /etc/conf.d/apache2

APACHE2_OPTS="-D LANGUAGE -D PROXY -D CGI"

Creamos el vhost de apache, este quedará a la escucha en el puerto 8000:

vi /etc/apache2/vhosts.d/00_modsecurityTest.conf

Listen 8000
<VirtualHost *:8000>
        ServerAdmin sys@alfaexploit.com
        DocumentRoot /var/www/modsecurityTest/
        ServerName modsecurityTest.alfaexploit.com
        ErrorLog /var/log/apache2/modsecurityTest.error_log
        CustomLog /var/log/apache2/modsecurityTest.access_log combined
        DirectoryIndex index.php
        AddHandler application/x-httpd-php .php .php5 .phtml
        AddHandler application/x-httpd-php-source .phps

        <FilesMatch ".php$">
            SetHandler "proxy:fcgi://127.0.0.1:9000/"
        </FilesMatch>

        <Directory "/var/www/modsecurityTest/">
            Options -Indexes +FollowSymLinks +ExecCGI
            AllowOverride None
            Require all granted
            AllowOverride All
        </Directory>

</VirtualHost>

Creamos los ficheros de log de nuestro proyecto web:

touch /var/log/apache2/modsecurityTest.error_log
touch /var/log/apache2/modsecurityTest.access_log

Reiniciamos Apache:

/etc/init.d/apache2 restart


Compilación e instalación de Nginx + PHP

Ahora compilamos Nginx:

emerge -av www-servers/nginx

Creamos el vhost de Nginx, este quedará a la escucha en el puerto 8001:

vi /etc/nginx/nginx.conf

user nginx nginx;
worker_processes 1;

error_log /var/log/nginx/error_log info;

events {
    worker_connections 1024;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main
        '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $bytes_sent '
        '"$http_referer" "$http_user_agent" '
        '"$gzip_ratio"';

    client_header_timeout 10m;
    client_body_timeout 10m;
    send_timeout 10m;

    connection_pool_size 256;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 2k;
    request_pool_size 4k;

    gzip off;

    output_buffers 1 32k;
    postpone_output 1460;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 75 20;

    ignore_invalid_headers on;

    index index.php;

    server {
        listen 0.0.0.0:8001;
        server_name modsecurityTest.alfaexploit.com;

        access_log /var/log/nginx/modsecurityTest.access_log main;
        error_log /var/log/nginx/modsecurityTest.error_log info;

        root /var/www/modsecurityTest/;
        location ~* \.php$ {
            fastcgi_index   index.php;
            fastcgi_pass    127.0.0.1:9000;
            include         fastcgi_params;
            fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
            fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
        }

    }
}

Reiniciamos Nginx:

/etc/init.d/nginx restart


Código de prueba

Para realizar las pruebas vamos a programar una pequeña aplicación web vulnerable a inyecciones SQL:

mkdir /var/www/modsecurityTest/
vi /var/www/modsecurityTest/index.php

<html>
<body>
<?php
    if(isset($_POST['login']))
    {
        $username = $_POST['username'];
        $password = $_POST['password'];
        $con = mysqli_connect('localhost','root','password','sample');
        $result = mysqli_query($con, "SELECT * FROM `users` WHERE username='$username' AND password='$password'");
        if(mysqli_num_rows($result) == 0)
            echo 'Invalid username or password';
        else
            echo '<h1>Logged in</h1><p>A Secret for you....</p>';
    }
    else
    {
?>
        <form action="" method="post">
            Username: <input type="text" name="username"/><br />
            Password: <input type="password" name="password"/><br />
            <input type="submit" name="login" value="Login"/>
        </form>
<?php
    }
?>
</body>
</html>

Compilación e instalación de MySQL

Compilamos MySQL:

emerge -av virtual/mysql
emerge --config =dev-db/mysql-5.7.27-r1
/etc/init.d/mysql start
rc-update add mysql default

Creamos la base de datos a la que accede la aplicación web:

mysql -u root -p

create database sample;
connect sample;
create table users(username VARCHAR(100),password VARCHAR(100));
insert into users values('jesin','pwd');
insert into users values('alice','secret');
quit;

Tests PHP e inyección

Comprobamos que la ejecución de PHP funciona correctamente:

echo “” > /var/www/modsecurityTest/info.php

Accedemos a las URLs correspondientes de Apache/Nginx:
http://modsecuritytest.alfaexploit.com:8000/info.php
http://modsecuritytest.alfaexploit.com:8001/info.php

Una vez comprobado, borramos el fichero de phpinfo:

rm /var/www/modsecurityTest/info.php

Comprobamos que la app funciona correctamente en los dos servidores:
http://modsecurityTest.alfaexploit.com:PUERTO

Username: jesin
Password: pwd
Logged in
A Secret for you....

Comprobamos que la aplicación es vulnerable a inyecciones SQL, si introducimos como usuario:

Username: ' or true --
Logged in
A Secret for you....

Compilación e instalación de libmodsecurity

Compilamos libmodsecurity, esta no esta disponible en los repos de ninguna distro, hay que compilarla obligatoriamente desde las fuentes a no ser que se utilice Nginx-Plus , primero compilamos una dependencia de json:

emerge -av dev-libs/yajl dev-libs/geoip

Ahora la librería en sí misma:

cd /usr/src
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity/
cd ModSecurity
git submodule init
git submodule update
./build.sh
./configure --prefix=/ --with-yajl --enable-standalone-module
make -j4
make install
ln -s /include/modsecurity /usr/include/modsecurity


Conector Apache

Compilamos el conector para Apache:

cd /usr/src
git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-apache.git
cd ModSecurity-apache
./autogen.sh
./configure
make -j4
make install

Comprobamos que el módulo existe:

ls -la /usr/lib64/apache2/modules/mod_security3.so

-rwxr-xr-x 1 root root 29488 nov 4 13:01 /usr/lib64/apache2/modules/mod_security3.so

Cargamos el módulo:

vi /etc/apache2/httpd.conf

LoadModule security3_module modules/mod_security3.so

Reiniciamos Apache y comprobamos que el módulo ha sido cargado:

/etc/init.d/apache2 restart
/etc/init.d/apache2 modules

 security3_module (shared)

Conector Nginx

Compilamos el conector para Nginx:

Recompilamos Nginx indicándole donde está el módulo externo :

vi /etc/portage/make.conf

NGINX_ADD_MODULES="/usr/src/ModSecurity-nginx"
emerge -av www-servers/nginx

Comprobamos que el módulo ha sido cargado:

nginx -V 2>&1 | tr -- - ‘\n’ | grep module

http_v2_module
http_realip_module
http_ssl_module
stream_access_module
stream_geo_module
stream_limit_conn_module
stream_map_module
stream_return_module
stream_split_clients_module
stream_upstream_hash_module
stream_upstream_least_conn_module
stream_upstream_zone_module
mail_imap_module
mail_pop3_module
mail_smtp_module
module=/usr/src/ModSecurity

Configuración libmodsecurity

Configuramos modsec partiendo de una configuración base:

mkdir /etc/modsec
cd /etc/modsec
wget https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
mv modsecurity.conf-recommended modsecurity.conf
vi /etc/modsec/modsecurity.conf

SecRuleEngine On
SecAuditLogFormat json
SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsec_audit.log

Hay un fichero necesario para el funcionamiento de libmodsecurity pero por alguna razón no se instala al hacer el make install, nos lo bajamos manualmente:

Nos bajamos las reglas OWASP:

cd /usr/local
git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git
cd owasp-modsecurity-crs
cp crs-setup.conf.example crs-setup.conf

Le indicamos a modsec que cargue dichas reglas:

vi /etc/modsec/main.conf

# Include the recommended configuration
Include /etc/modsec/modsecurity.conf

# OWASP CRS v3 rules
Include /usr/local/owasp-modsecurity-crs/crs-setup.conf
Include /usr/local/owasp-modsecurity-crs/rules/*.conf

Configuración Apache + libmodsecurity

Configuramos Apache para que utilice libmodsecurity:

vi /etc/apache2/vhosts.d/00_modsecurityTest.conf

Listen 8000
<VirtualHost *:8000>
        ServerAdmin sys@alfaexploit.com
        DocumentRoot /var/www/modsecurityTest/
        ServerName modsecurityTest.alfaexploit.com
        ErrorLog /var/log/apache2/modsecurityTest.error_log
        CustomLog /var/log/apache2/modsecurityTest.access_log combined
        DirectoryIndex index.php

        modsecurity on
        modsecurity_rules_file /etc/modsec/main.conf

        AddHandler application/x-httpd-php .php .php5 .phtml
        AddHandler application/x-httpd-php-source .phps

        <FilesMatch ".php$">
            SetHandler "proxy:fcgi://127.0.0.1:9000/"
        </FilesMatch>

        <Directory "/var/www/modsecurityTest/">
            Options -Indexes +FollowSymLinks +ExecCGI
            AllowOverride None
            Require all granted
            AllowOverride All
        </Directory>

</VirtualHost>

NOTA: Si queremos hacerlo de forma global.

vi /etc/apache2/modules.d/90_libmodsecurity.conf
modsecurity on
modsecurity_rules_file /etc/modsec/main.conf

NOTA: Si se trata de un servidor en producción en preferible utilizar solo la detección antes, para poder ir adaptando las reglas a nuestro escenario particular.

vi /etc/modsec/modsecurity.conf
SecRuleEngine DetectionOnly

Reiniciamos Apache:

/etc/init.d/apache2 restart


Configuración Nginx + libmodsecurity

Configuramos Nginx para que utilice libmodsecurity:

vi /etc/nginx/nginx.conf

user nginx nginx;
worker_processes 1;

error_log /var/log/nginx/error_log info;

events {
    worker_connections 1024;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main
        '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $bytes_sent '
        '"$http_referer" "$http_user_agent" '
        '"$gzip_ratio"';

    client_header_timeout 10m;
    client_body_timeout 10m;
    send_timeout 10m;

    connection_pool_size 256;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 2k;
    request_pool_size 4k;

    gzip off;

    output_buffers 1 32k;
    postpone_output 1460;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 75 20;

    ignore_invalid_headers on;

    index index.php;

    server {
        listen 0.0.0.0:8001;
        server_name modsecurityTest.alfaexploit.com;

        modsecurity on;
        modsecurity_rules_file /etc/modsec/main.conf;

        access_log /var/log/nginx/modsecurityTest.access_log main;
        error_log /var/log/nginx/modsecurityTest.error_log info;

        root /var/www/modsecurityTest/;
        location ~* \.php$ {
            fastcgi_index   index.php;
            fastcgi_pass    127.0.0.1:9000;
            include         fastcgi_params;
            fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
            fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
        }

    }
}

Reiniciamos Nginx:

/etc/init.d/nginx restart


Test inyección2

Si intentamos hacer login mediante la inyección SQL obtenemos:

Forbidden
You don't have permission to access this resource.

En los logs podemos ver las alertas:

tail -f /var/log/modsec_audit.log


Concurrencia

Si vamos a generar muchas entradas en el log es preferible escribir las entradas en formato concurrent, de este modo no se logearán en un fichero de texto de forma serializada, si no que se hará en distintos ficheros de forma paralela:

vi /etc/modsec/modsecurity.conf

#SecAuditLogType Serial
SecAuditLogType Concurrent
SecAuditLog /var/log/modsec_audit.log

SecAuditLogStorageDir /opt/modsecurity/var/audit

Custom config por tener dos servidores web simultáneos

Como tenemos dos servidores web hay que darle acceso a los dos usuarios para que sean capaces de generar los ficheros de log:

groupadd httpservers
gpasswd -a apache httpservers
gpasswd -a nginx httpservers

Creamos el directorio donde se almacenarán los logs:

mkdir -p /opt/modsecurity/var/audit/
chown -R root:httpservers /opt/modsecurity/var/audit/
chmod 775 /opt/modsecurity/var/audit/

Reiniciamos ambos servidores para aplicar la nueva configuración:

/etc/init.d/apache2/nginx restart

En cuanto hagamos saltar alguna regla podemos ver como se generar directorios nuevos:

ls -la /opt/modsecurity/var/audit/

total 12
drwxrwxr-x 3 root httpservers 4096 nov 4 13:37 .
drwxr-xr-x 4 root root 4096 nov 4 13:28 ..
drwxr-x--- 3 apache apache 4096 nov 4 13:37 20191104

NOTA: En este ejemplo Modsecurity es compartido por Apache y Nginx, cada servidor correr con su usuario correspondiente, por lo tanto cuando modsecurity genere logs lo hará con dicho usuario, en nuestro ejemplo esto genera problemas ya que el primero en generar el directorio del día podrá escribir en este denegando el acceso al segundo.

ls -la /opt/modsecurity/var/audit/

drwxr-x--- 3 apache apache 4096 nov 4 18:56 20191104
drwxr-x--- 3 nginx nginx 4096 nov 4 18:58 20191104

Para testear los dos servidores podemos eliminar los ficheros de log, hacer las pruebas con un servidor, volver a eliminar los logs y hacer las pruebas con el segundo servidor. Una alternativa sería tener dos configuraciones de /etc/modsec/modsecurity.conf cada una de ellas con un SecAuditLogStorageDir diferente.


Integración con Telegram

Una manera interesante de recibir las notificaciones es vía Telegram, para ello debemos crear un bot y añadirlo a un grupo donde enviaremos las alertas generadas por nuestro parser de logs.

Para almacenar el número de ataques por ip utilizaremos Redis.

emerge -av dev-db/redis

Configuramos el servicio para que precise autenticación:

vi /etc/redis.conf

requirepass XXXXXXXXXXXXX

Arrancamos el servicio y lo metemos en el arranque:

/etc/init.d/redis start
rc-update add redis default

Si miramos los ficheros generados por libmodsecurity podemos ver como el campo message es nulo a excepción de la regla que ha macheado, en base a esto podemos desarrollar nuestro parser:

cat /opt/modsecurity/var/audit/20191104/20191104-1408/20191104-140857-157287293756.333688 |jq ‘.transaction.messages[].message’

""
""
"SQL Injection Attack Detected via libinjection"
""
""

Preguntando por Internet comentan que a pesar de que las reglas tengan la opción nolog habilitada generan entradas de log con message nulo:

If you google the problem, you will see that it is a common bug

Instalamos las librerias de python necesarias:

pip install python-iptables --user
pip install redis --user
pip install pyinotify --user

Programamos el parser para bloquear atacantes si en 60s se han realizado 5 o mas ataques:

vi /usr/local/modsecurityNotifier.py

#!/usr/bin/python

import pyinotify
import os
import json
import requests
import redis
import iptc

apikey = "XXXXX:YYYYYYYYYYYYYYYYYYYYYY"
telegramurl = "https://api.telegram.org/bot{}/sendMessage".format(apikey)
userid = "ZZZZZZZZZ"

try:
    redisconnection = redis.Redis(host="127.0.0.1", port=6379, db=0, password='XXXXXXXXXXXXXX')
    redisconnection.ping()
except:
    print '++ ERROR: Cant connect to redis server'
    quit()
    
class CommitFunction(pyinotify.ProcessEvent):
    def process_default(self, event):
        fullpath = event.path + '/' + event.name
        if os.path.isfile(fullpath):
            print '------------------------------------------------'
            print "Processing: " + str(fullpath)
            with open(fullpath) as fp:
                for line in fp:
                    try:
                        rawdata = json.loads(line)
                    except:
                        continue

                    for messageline in rawdata['transaction']['messages']:
                        message = messageline['message']
                        data = messageline['details']['data']
                        # Delete not matched rules messages and anomaly score checks
                        if message != "" and data != "":
                            try:
                                timestamp = rawdata['transaction']['time_stamp']
                            except:
                                timestamp = 'NULL'
                            try:
                                attacker = rawdata['transaction']['request']['headers']['X-Forwarded-For']
                            except:
                                attacker = rawdata['transaction']['client_ip']
                                #attacker = 'NULL'
                            try:
                                useragent = rawdata['transaction']['request']['headers']['User-Agent']
                            except:
                                useragent = 'NULL'
                            try:
                                host = rawdata['transaction']['request']['headers']['Host']
                            except:
                                host = 'NULL'
                            try:
                                url = rawdata['transaction']['request']['uri']
                            except:
                                url = 'NULL'
                            try:
                                method = rawdata['transaction']['request']['method']
                            except:
                                method = 'NULL'
                            try:
                                payload = messageline['details']['data']
                            except:
                                payload = 'NULL'
                                
                            print '>> Timestamp: ' + str(timestamp)
                            print 'Attacker: ' + str(attacker)
                            print 'UserAgent: ' + str(useragent)
                            print 'Message: ' + str(message)
                            print 'Host: ' + str(host)
                            print 'URL: ' + str(url)
                            print 'Method: ' + str(method)
                            print 'Payload: ' + str(payload)

                            print 'Checking redis IP: ' + str(attacker)
                            if redisconnection.get(attacker):
                                rediscounter = redisconnection.get(attacker)
                                #print 'rediscounter: ' + str(rediscounter)
                                if int(rediscounter) >= 5:
                                    blacklistpath = '/etc/.blacklists/modsecurity.list'
                                    if os.path.isfile(blacklistpath):
                                        # Check if attacker is in blacklist
                                        attackerfound = 0
                                        print 'Checking if attacker is in blacklist'
                                        with open(blacklistpath, "r") as blacklistcontent:
                                            for blacklistline in blacklistcontent:
                                                blacklistline = blacklistline.strip('\n')
                                                print 'Comparing: ' + str(blacklistline) + ' against ' + str(attacker)
                                                if blacklistline == attacker:
                                                    print 'Matched'
                                                    attackerfound = 1
                                                    break
                                                    
                                        # Add attacker to blacklist
                                        if attackerfound == 0:
                                            blacklistfile = open(blacklistpath,"a")
                                            blacklistfile.write(attacker+'\n')
                                            blacklistfile.close()
                                            print 'Writting attacker ip to blacklist: ' + str(attacker)
                                    else:
                                        blacklistfile = open(blacklistpath,"w+")
                                        blacklistfile.write(attacker+'\n')
                                        blacklistfile.close()

                                    # Check if attacker is in running iptables rules
                                    print 'Checking in running iptables rules'
                                    iptablesdata = iptc.easy.dump_table('filter', ipv6=False)
                                    attackerfoundiptables = 0
                                    for rule in iptablesdata['INPUT']:
                                        if 'src' in rule and 'target' in rule:
                                            if rule['src'] == attacker+'/32' and rule['target'] == 'DROP':
                                                attackerfoundiptables = 1
                                                print 'Matched'
                                                break

                                    # Add attacker to running iptables rules
                                    if attackerfoundiptables == 0:
                                        print 'Inserting in running iptables rules'
                                        rule = iptc.Rule()
                                        rule.src = attacker
                                        rule.create_target("DROP")
                                        rule.target = iptc.Target(rule, "DROP")
                                        chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT")
                                        chain.insert_rule(rule)

                                        msg = 'Banning time for: ' + str(attacker)
                                        data = {"chat_id":userid, "text":msg}
                                        try:
                                            r = requests.post(telegramurl,json=data)
                                        except:
                                            print '++ Error sending telegram message'
                                            continue

                                        print 'Sending telegram alert'
                                        msg = 'Date: ' + str(timestamp) + '\nAttacker: ' + str(attacker) + '\nUserAgent: ' + str(useragent) + '\nHost: ' + str(host) + '\nUrl: ' + str(url) + '\nAlert: ' + str(message) + '\nPayload: ' + str(payload)
                                        data = {"chat_id":userid, "text":msg}
                                        try:
                                            r = requests.post(telegramurl,json=data)
                                        except:
                                            print '++ Error sending telegram message'
                                            continue
                                else:
                                    print 'Incrementing redis key value for IP: ' + str(attacker)
                                    redisconnection.incr(attacker)
                            else:
                                print 'Creating redis key for IP: ' + str(attacker)
                                redisconnection.incr(attacker)
                                redisconnection.expire(attacker, 60)
                            

wm = pyinotify.WatchManager()
notifier = pyinotify.Notifier(wm)
wm.add_watch('/opt/modsecurity/var/audit/', pyinotify.IN_CREATE, rec=True, auto_add=True, proc_fun=CommitFunction())
notifier.loop(daemonize=False, callback=None)

Asignamos los permisos necesarios:

chmod 700 /usr/local/modsecurityNotifier.py

Podemos testear el parser mediante varias herramientas:

curl -X POST -F “username=kr0m’ or ‘1 == 1;’” -F “password=something” http://modsecuritytest.alfaexploit.com:8000
python sqlmap.py -u http://modsecuritytest.alfaexploit.com --data=“username=&password=”
perl nikto.pl -host http://modsecuritytest.alfaexploit.com:8001
ZAP

A continuación podemos ver las distintas notificaciones recibidas en Telegram:




Para que el parser autoarranque creamos el script:

vi /etc/local.d/modsecurity.start

/usr/bin/python /usr/local/modsecurityNotifier.py &
chmod 700 /etc/local.d/modsecurity.start

Lo arrancamos manualmente:

/etc/local.d/modsecurity.start

NOTA: Si compilamos iptables con la use flag static-libs el script en python fallará por alguna razón:

iptc.errors.XTablesError: can't find target DROP

Si en nuestra aplicación web hay alguna funcionalidad que requiera un comportamiento un poco fuera de lo normal podemos whitelistearla para que las reglas no salten cuando se trata de este sección:

cp /usr/local/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example /usr/local/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
vi /usr/local/owasp-modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

SecRule REQUEST_URI "@beginsWith /strangefunctionality" \
 "id:1001,\
 phase:1,\
 pass,\
 nolog,\
 ctl:ruleEngine=Off"

Recargamos la configuración reiniciando los servidores web:

/etc/init.d/apache2/nginx restart


Si por otro lado queremos deshabilitar alguna regla en concreto utilizaremos la directiva SecRuleRemoveById

Nota : Esta directiva debe especificarse después de la regla a deshabilitar.

En mi caso estaba teniendo problemas con la regla:

CGI source code leakage
"ruleId": "950140",
          "file": "/usr/local/owasp-modsecurity-crs/rules/RESPONSE-950-DATA-LEAKAGES.conf",
          "lineNumber": "66",

Al mostrar código por la web la alarma es disparada por pensar que se está filtrando código fuente de la propia web:

vi /usr/local/etc/modsec/main.conf

# Include the recommended configuration
Include /usr/local/etc/modsec/modsecurity.conf

# OWASP CRS v3 rules
Include /usr/local/owasp-modsecurity-crs/crs-setup.conf
Include /usr/local/owasp-modsecurity-crs/rules/*.conf

# Disabled rules
Include /usr/local/etc/modsec/disabledRules.conf
vi /usr/local/etc/modsec/disabledRules.conf
SecRuleRemoveById 950140

DEBUG

Si queremos ver las peticiones de forma visual para incluir otros campos en las notificaciones podemos utilizar esta web:
http://jsonviewer.stack.hu/

También podemos aumentar el nivel de debug:

vi /etc/modsec/modsecurity.conf

SecDebugLog /opt/modsecurity/var/log/debug.log
SecDebugLogLevel 9
mkdir -p /opt/modsecurity/var/log/
touch /opt/modsecurity/var/log/debug.log

Reiniciamos los servidores web:

/etc/init.d/apache2/nginx restart

Consultamos los logs de debug:

tail -f /opt/modsecurity/var/log/debug.log

Los posibles debug levels son:

  • 0    No logging
  • 1    Errors (e.g., fatal processing errors, blocked transactions)
  • 2    Warnings (e.g., non-blocking rule matches)
  • 3    Notices (e.g., non-fatal processing errors)
  • 4    Informational
  • 5    Detailed
  • 9    Everything!

Para entornos en producción podemos generar solo logs pero no bloquear las peticiones hasta tener las reglas pulidas:

vi /etc/modsec/modsecurity.conf

SecRuleEngine DetectionOnly

Otra opción es tan solo analizar un porcentaje de las peticiones: Adjust the percentage of requests that are funnelled into the Core Rules by setting TX.sampling_percentage below. The default is 100, meaning that every request gets checked by the CRS.

vi /usr/local/owasp-modsecurity-crs/crs-setup.conf

SecAction "id:900400,\
 phase:1,\
 pass,\
 nolog,\
 setvar:tx.sampling_percentage=10"

Performance

Por último dejo un enlace a un artículo sobre optimización de rendimiento:
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/modsecurity-performance-recommendations/

Si te ha gustado el artículo puedes invitarme a un RedBull aquí