Esta pagina se ve mejor con JavaScript habilitado

libModsecurity en Apache/Raspbian

 ·  🎃 kr0m

La plataforma Raspberrypi se apoya sobre un microprocesador ARM, esto no es que esté mal pero impone algunas limitaciones en cuanto a software, no todos los paquetes están compilados para esta arquitectura e incluso algún software no llega a funcionar como es el caso de libModSecurity3, este compila y carga el módulo pero al reiniciar Apache crashea el proceso. Esto no quiere decir que no podamos utilizarlo pero tendremos que conformarnos con las versión número 2 que sí que viene empaquetada para Raspbian.

Como ya explicamos en un artículo anterior actualmente existen dos versiones de libmodsecurity, la versión 2 y 3 esta última es preferible ya que está mas optimizada y obtendremos un mejor rendimiento, sobretodo si nuestro servidor web no es Apache, pero en Raspbian solo tenemos el paquete para la versión 2 y la versión 3 compilada crashea Apache, apareciendo el siguiente error al reiniciarlo:

apachectl[4941]: Segmentation fault

Procedemos con la instalación de la versión 2:

apt install libapache2-mod-security2
cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

Configuramos modsecurity modificando los siguientes parámetros:

vi /etc/modsecurity/modsecurity.conf

SecRuleEngine On
SecAuditLogFormat json
SecAuditEngine On
SecAuditLogType Concurrent
SecAuditLogStorageDir /opt/modsecurity/var/audit/
mkdir -p /opt/modsecurity/var/audit/
chown -R root:www-data /opt/modsecurity/var/audit/
chmod 775 /opt/modsecurity/var/audit/

Nos bajamos las reglas de OWASP:

mv /usr/share/modsecurity-crs /usr/share/modsecurity-crs_ori
git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git /usr/share/modsecurity-crs
cp /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf

Configuramos Apache para que cargue las reglas:

vi /etc/apache2/mods-enabled/security2.conf

<IfModule security2_module>
        # Default Debian dir for modsecurity's persistent data
        SecDataDir /var/cache/modsecurity

        # Include all the *.conf files in /etc/modsecurity.
        # Keeping your local configuration in that directory
        # will allow for an easy upgrade of THIS file and
        # make your life easier
        IncludeOptional /etc/modsecurity/*.conf

        # Include OWASP ModSecurity CRS rules if installed
        #IncludeOptional /usr/share/modsecurity-crs/owasp-crs.load
        IncludeOptional /usr/share/modsecurity-crs/*.conf
        IncludeOptional /usr/share/modsecurity-crs/rules/*.conf
</IfModule>

Reiniciamos Apache:

/etc/init.d/apache2 restart

Cambiamos el modo de funcionamiento de Anomaly Scoring mode a Self-contained mode ya que este es mas liviano y en una raspberrypi conviene:

vi /usr/share/modsecurity-crs/crs-setup.conf

#SecDefaultAction "phase:1,log,auditlog,pass"
#SecDefaultAction "phase:2,log,auditlog,pass"
SecDefaultAction "phase:1,log,auditlog,deny,status:403"
SecDefaultAction "phase:2,log,auditlog,deny,status:403"

Reiniciamos Apache:

/etc/init.d/apache2 restart

Para que el script de parseo funcione precisa de un servidor Redis:

apt install redis-server

Configuramos el password de acceso a Redis:

vi /etc/redis/redis.conf

requirepass XXXXXXXXXXXXXXXXX

Reiniciamos el servicio:

/etc/init.d/redis-server restart

Instalamos las librerias necesarias para que el script de parseo funcione:

pip install redis

El parser varía respecto al del artículo anterior al tener que procesar logs de la versión 2.

vi /usr/local/modsecurityNotifier.py

#!/usr/bin/python

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

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

try:
        redisconnection = redis.Redis(host="127.0.0.1", port=6379, db=0, password='XXXXXXXXXXXXXXXXXXX')
    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
    
                    if rawdata != "":
                        try:
                            timestamp = rawdata['transaction']['time']
                        except:
                            timestamp = 'NULL'
                        try:
                            attacker = rawdata['transaction']['remote_address']
                        except:
                            attacker = 'NULL'
                        try:
                            useragent = rawdata['request']['headers']['User-Agent']
                        except:
                            useragent = 'NULL'
                        try:
                            host = rawdata['request']['headers']['Host']
                        except:
                            host = 'NULL'
                        try:
                            url = rawdata['request']['request_line']
                        except:
                            url = 'NULL'
                        try:
                            message = rawdata['audit_data']['messages'][0].split("[msg \"")[1].split("\"]")[0]
                        except:
                            #message = 'NULL'
                            continue
                        try:
                            payload = rawdata['audit_data']['messages'][0].split("[data \"")[1].split("\"]")[0]
                        except:
                            #payload = 'NULL'
                            continue

                        print '>> Timestamp: ' + str(timestamp)
                        print 'Attacker: ' + str(attacker)
                        print 'UserAgent: ' + str(useragent)
                        print 'Host: ' + str(host)
                        print 'URL: ' + str(url)
                        print 'Message: ' + str(message)
                        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

Creamos una unidad de systemd:

vi /etc/systemd/system/modsecurityNotifier.service

[Unit]
Description=Modsecurity Notifier
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/modsecurityNotifier.py

[Install]
WantedBy=default.target

Asignamos los permisos al fichero de la unidad:

chmod 664 /etc/systemd/system/modsecurityNotifier.service

Recargamos la configuración de systemd:

systemctl daemon-reload
systemctl start modsecurityNotifier.service
systemctl enable modsecurityNotifier.service

Ahora cada vez que se genere una alerta la recibiremos vía Telegram :)

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