Esta web utiliza cookies, puedes ver nuestra política de cookies aquí. Si continuas navegando estás aceptándola

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

Autor: Kr0m -- 06/12/2019 09:22:26