Libmodsecurity con balanceador de carga


Un escenario muy común en entornos web es disponer de un balanceador de tráfico y varios servidores web, si seguimos el artículo anterior donde instalábamos libmodsecurity y el script de alerta estaremos bloqueando el atacante a nivel ip en el servidor final, esto no sirve de nada ya que el táfico proviene del balanceador y estríamos baneando a este, para solventar dicha problemática montaremos un redis compartido entre el ha y los servidores web, de este modo podremos bloquear los atacantes en el propio ha.


En los servidores web instalaremos libmodsecurity como se indica aquí.

En cuanto a los scripts de bloqueo utilizaremos una versión modificada, en los servidores web finales sería esta:

vi /usr/local/modsecurityNotifier.py
#!/usr/bin/python

import pyinotify
import os
import json
import redis

try:
    redisconnection = redis.Redis(host="REDISIPADDRESS", port=6379, db=0, password='XXXXXXXXXXXXXXX')
    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 'Cheking redis IP: ' + str(attacker)
                            #if redisconnection.get(attacker):
                            if redisconnection.exists(attacker):
                                print 'Incrementing redis IP counter'
                                count = redisconnection.hmget(attacker, 'count')[0]
                                #print 'Count: ' + str(count)
                                count = int(count) + 1
                                redisconnection.hmset(attacker, {'count': count})
                            else:
                                print 'Creating redis key for IP: ' + str(attacker)
                                #redisconnection.incr(attacker)
                                redisconnection.hmset(attacker, {'count': '1', 'timestamp': timestamp, 'attacker': attacker, 'useragent': useragent, 'host': host, 'url': url, 'message': message, 'payload': payload})
                                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 hacer que autoarranque con:

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

Asignamos los permisos de ejecución:

chmod 700 /etc/local.d/modsecurity.start

Lo arrancamos manualmente:

/etc/local.d/modsecurity.start

En el HA será necesario leer los datos del redis con el siguiente script:

vi /usr/local/modsecurityNotifier.py
#!/usr/bin/python

import os
import json
import requests
import redis
import iptc
import time

apikey = "XXXXXXX:YYYYYYYYYYYYYYYYYYY"
telegramurl = "https://api.telegram.org/bot{}/sendMessage".format(apikey)
userid = "XXXXXX"

try:
    redisconnection = redis.Redis(host="REDISIPADDRESS", port=6379, db=0, password='XXXXXXXXXXXXXXX')
    redisconnection.ping()
except:
    print '++ ERROR: Cant connect to redis server'
    quit()

while True:
    for attacker in redisconnection.scan_iter("*"):
        print attacker
        #rediscounter = redisconnection.get(attacker)
        rediscounter = redisconnection.hmget(attacker, 'count')[0]
        timestamp = redisconnection.hmget(attacker, 'timestamp')[0]
        useragent = redisconnection.hmget(attacker, 'useragent')[0]
        host = redisconnection.hmget(attacker, 'host')[0]
        url = redisconnection.hmget(attacker, 'url')[0]
        message = redisconnection.hmget(attacker, 'message')[0]
        payload = redisconnection.hmget(attacker, 'payload')[0]

        print 'Rediscounter: ' + str(rediscounter)
        print 'timestamp: ' + str(timestamp)
        print 'useragent: ' + str(useragent)
        print 'host: ' + str(host)
        print 'url: ' + str(url)
        print 'message: ' + str(message)
        print 'payload: ' + str(payload)

        print 
        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
    print '----- Sleeping 10s -----'
    time.sleep(10)

Le asignamos los permisos necesarios:

chmod 700 /usr/local/modsecurityNotifier.py

Podemos hacer que autoarranque con:

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

Le asignamos permisos de ejecución:

chmod 700 /etc/local.d/modsecurity.start

Lo arrancamos manualmente:

/etc/local.d/modsecurity.start

Para evitar peticiones directas a los servidores web filtramos el tráfico HTTP para que solo se acepte cuando provenga del Haproxy, esta medida es necesaria ya que confiamos en la cabecera x-forwarded-for para bloquear ips, si el tráfico no se filtrase y un atacante inyectase dicha cabecera con un valor de su elección podría provocar una denegación de servicio para dicha ip.

iptables -I INPUT 1 -p tcp --dport 80 -j DROP
iptables -I INPUT 1 -p tcp --dport 80 -s HAIP -j ACCEPT
iptables -I INPUT 1 -p tcp --dport 443 -j DROP
iptables -I INPUT 1 -p tcp --dport 443 -s HAIP -j ACCEPT

Guardamos las reglas actuales para que en caso de reinicio se vuelvan a cargar de forma automática:

/etc/init.d/iptables save
rc-update add sshd default
Si te ha gustado el artículo puedes invitarme a un redbull aquí.
Autor: kr0m -- 06/12/2019 09:22:26