The Raspberrypi platform relies on an ARM microprocessor, which is not bad but imposes some limitations on software. Not all packages are compiled for this architecture and even some software does not work, such as libModSecurity3. It compiles and loads the module, but when Apache is restarted, the process crashes. This does not mean that we cannot use it, but we will have to settle for version 2, which is packaged for Raspbian.
As we explained in a previous article here , there are currently two versions of libmodsecurity, version 2 and 3. The latter is preferable as it is more optimized and we will get better performance, especially if our web server is not Apache. However, in Raspbian we only have the package for version 2 and the compiled version 3 crashes Apache, displaying the following error when restarting it:
apachectl[4941]: Segmentation fault
We proceed with the installation of version 2:
cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
We configure modsecurity by modifying the following parameters:
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/
We download the OWASP rules:
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
We configure Apache to load the rules:
<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>
We restart Apache:
We change the operation mode from Anomaly Scoring mode to Self-contained mode since it is lighter and more convenient for a Raspberry Pi:
#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"
We restart Apache:
For the parsing script to work, a Redis server is required:
We configure the Redis access password:
requirepass XXXXXXXXXXXXXXXXX
We restart the service:
We install the necessary libraries for the parsing script to work:
The parser differs from the previous article’s as it has to process logs from version 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)
We assign the necessary permissions:
We create a systemd unit:
[Unit]
Description=Modsecurity Notifier
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/modsecurityNotifier.py
[Install]
WantedBy=default.target
We assign permissions to the unit file:
We reload the systemd configuration:
systemctl start modsecurityNotifier.service
systemctl enable modsecurityNotifier.service
“Now, every time an alert is generated, we will receive it via Telegram :)”