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:
cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
Configuramos modsecurity modificando los siguientes parámetros:
SecRuleEngine On
SecAuditLogFormat json
SecAuditEngine On
SecAuditLogType Concurrent
SecAuditLogStorageDir /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:
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:
<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:
Cambiamos el modo de funcionamiento de Anomaly Scoring mode a Self-contained mode ya que este es mas liviano y en una raspberrypi conviene:
#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:
Para que el script de parseo funcione precisa de un servidor Redis:
Configuramos el password de acceso a Redis:
requirepass XXXXXXXXXXXXXXXXX
Reiniciamos el servicio:
Instalamos las librerias necesarias para que el script de parseo funcione:
El parser varÃa respecto al del artÃculo anterior al tener que procesar logs de la versión 2.
#!/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:
Creamos una unidad de systemd:
[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:
Recargamos la configuración de systemd:
systemctl start modsecurityNotifier.service
systemctl enable modsecurityNotifier.service
Ahora cada vez que se genere una alerta la recibiremos vÃa Telegram :)