In this post, I’m going to show how to make an ip-blocking firewall using Python and Suricata. I’ve recently started learning Python and this was a project to help solidify what I’ve learned so far while applying it to cybersecurity. This program operates as follows:
- Runs in the background as a linux daemon
- Scans the Suricata eve.json file to find certain alerts and blocks the related IP addresses in iptables
- Can also block IPs based on pcap files fed to Suricata
In practice, this program would run on a linux box set up as a transparent bridge in order to monitor network traffic. Suricata on this machine would trigger alerts and this program would block the IPs for the entire network.
Requirements
Since this program uses pcaps that contain malware, this demonstration is taking place on virtual machines on an isolated network without internet access. The necessary packages were installed before isolation.
Besides Suricata, the machine running this script also needs ipset installed. Supervisor is not necessary to run the script but is used to manage it as a daemon. On Ubuntu/Debian they can be installed with:
sudo apt ipset supervisor -y
Create Ipset
Ipset allows us to efficiently block IPs with iptables. Rather than adding a massive amount of IP addresses to block into iptables directly, we can instead add the IPs to an ipset list, then add that ipset list to iptables. Not only does it make our iptables look cleaner but more importantly makes it easier to manage. Create a new ipset list with the following command:
sudo ipset create blocklist hash:ip hashsize 4096
If you run the command: sudo ipset list, you’ll see the created.
Next, we need to add this list to iptables. Run the following command:
sudo iptables -I INPUT -m set --match-set blocklist src -j DROP
Now, any IP added to our ipset list will be blocked by iptables.
Code Explanation
The code can be downloaded from my github here:
https://github.com/willjroberts/Suricata-Firewall/blob/main/suricata_firewall.py
The script is run from the terminal with two arguments. The first is the path of the Suricata eve.json file. The second is the name of the ipset list. The main loop function starts with creating variables for the json file and ipset list.
The basic flow of the program is as follows:
- Stores the eve.json path and ipset list as variables
- Scans the provided eve.json file for relevant keywords and adds the IPs of the alerts to the ipset list
- Records the number of alerts currently in eve.json
- Waits for a short period of time
- Gets the current number of alerts in eve.json again. If the number of alerts is higher than last recorded, meaning new alerts were added, scan these new alerts
- Wait again and repeat indefinitely
Function Explanations
Log Formatter
An essential function. This function is needed to convert the eve.json file into a Python-readable format. This function opens eve.json and uses the json library to create a list of Python dictionaries. This was necessary since despite the format of the eve.json file looking like a Python dictionary, it’s type is actually a string. The function returns a list with each item as a Suricata alert.
IP List
This function gets the IPs currently in the provided ipset list. When used with another function, it tells the program to add a new IP to the ipset list if it not in it already.
Alert Count
This function is for keeping track the number of alerts in eve.json.
Log Parser
The main workhorse of the program. This function scans eve.json for relevant keywords and adds the IPs to the ipset list if they’re not the list already. If there are new IPs added, the IP and time it was added is output to the screen and recorded in a log file.
In this program, there are two lists for keywords. The reason for this is Suricata records two IPs per alert, a source IP and a destination IP. For some alerts such as those related to malware, the IP you want to block is the source IP / source of the malware. In others cases, alerts deal with information getting out of your network such as malware trying to reach a C2 server. In this case, you want the destination IP since the source IP will be the machine on your network. The lists in this program, src_keywords and dest_keywords are examples of keywords can be used for scanning eve.json for alerts of interest.
Main Loop
The necessary libraries are imported. When the program starts, it stores the path of the provided eve.json file and ipset list as variables.
Then, the function records the current alert count by calling the alert_count function. The alert_count function needs first to use the log_formatter function to make the provided eve.json file readable. A second alert count variable is made and is set equal to the initial alert count. These two variables are used later to keep determine if there new alerts in eve.json. If the current alert count is greater than the initial count, then there are new alerts to scan.
After getting the alert counts, the program scans eve.json for relevant alerts. Then the programs sleeps.
After the initial scans are done, a while loop starts. First, the number of current number of alerts is recorded again by calling the alert_count function. If the current number of alerts (currentAlertCount) is not higher than the inital alert count, the loop sleeps for 5 seconds. If the current number if alerts is higher, the loop continues.
First, it creates an empty list called reduced log. Then, the log formatter function is called on the current eve.json file and stored in the variable updatedLog. A for loop is then performed on updatedLog which appends only the newest eve.json alerts to reducedLog. The loop is able to know the number of alerts to add by using the difference the initial alert count and the current alert count as ranges.
Then, the log_parser function is called using the reducedLog as an argument. If any of the alerts contain the relevant keywords, their IP will be added to the ipset list.
Finally, initialAlertCount is set equal currentAlertCount to reset their difference and the loop sleeps for 5 seconds.
The two exception clauses are included for manually stopping the program with a keyboard interrupt or some other unexpected reason. In both cases, the reason and time of the error are logged to an error file.
Daemon Setup
While this program can run in the background by default, we can use supervisor to automatically run it at startup. Start by moving the program to the /opt folder.
Next, we need to create the configuration file in supervisor to manage the firewall program as a daemon. Navigate to the supervisor directory and create the configuration file with the following commands:
cd /etc/supervisor/conf.d
sudo vim suricata-firewall.conf
Copy the following into the configuration file:
[program:suricata_firewall]
process_name=suricata_firewall
command=python3 /opt/suricata_firewall.py /var/log/suricata/eve.json blocklist
autostart=true
autorestart=unexpected
numprocs=1
startsecs=1
startretries=30
stopsignal=QUIT
stopwaitsecs=30
On the command line, we’re running this script with the default path of the Suricata eve.json file and the name of the ipset blocklist used earlier. You can change these based on your system’s setup.
Now, restart supervisor with:
sudo service restart supervisor
Then, run supervisor to make sure the firewall program is running:
sudo supervisorctl
Add Program to Cron
The final step in setting up the daemon is using another daemon manager called cron. Cron allows us to run commands at certain intervals for us. In this case, we’re going to have cron restart supervisor every 24 hours or in other words, reset all of our daemons every day.
Run the command:
crontab -e
At the end of the file add:
@daily supervisorctl restart all
Firewall Demonstration
Now that the firewall is up and running, we’re going to test it by:
- Trying to SSH into this machine from an unauthorized machine
- Running a pcap that will trigger alerts related to malware
Make sure Suricata is running. You can run it with:
sudo systemctl start suricata.service
Block IPs from SSH Brute Force
In this scenario, our machine is only allows SSH access from certain machines. In Suricata, we’ll make a custom rule in the local.rules file.
sudo vim /etc/suricata/rules/local.rules
The rule says to alert if any machine that is not 10.80.80.2 that tries to connect to this device over port 22.
From our program, recall that we made one of the keywords ‘SSH”.
To test this alert and the firewall, we need to attempt to SSH into this machine from another one on the same isolated network. On the other machine, I make an SSH attempt.
SSH is blocked on the target machine by default but we should still see an alert from Suricata and the IP added to the ipset list.
Checking the fast.log on our target machine, we see the alert.
Now, if the firewall is working properly, the IP of the machine that tried to SSH should be added to the ipset blocklist. We can check the list with:
sudo ipset list
This is exactly what we needed to see. Now this IP is blocked. We can confirm this by enabling the SSH service on the target machine and trying to SSH into again. We’ll see the connection refuse again. Re-enable the SSH service with:
sudo systemctl start ssh.service
When trying to SSH into the target machine, the connection times out.
Blocking Malicious IPs
For this next part, we’re going to replay a pcap file containing malware from https://www.malware-traffic-analysis.net/. This file contains packets that downloaded Dridex malware which is a type of malware that harvests credentials and return them to a C2 server. For the firewall, it means it needs to block the IPs that downloaded the malware and the IPs of the C2 servers the malware will try to connect to. Our keywords in our firewall program reflect this.
This pcap file is going to be replayed in offline mode in Suricata to simulate the firewall reacting the packets in a real scenario. When pcaps are replayed in offline mode, Suricata creates a separate eve.json file so we need to run the firewall program on this new json file.
First, we need to stop supervisor so we can run the program with the new eve.json file.
Then, we’re going to run the replay the pcap file in the directory it was downloaded.
Before running the firewall, let’s view some of the alerts generated to understand why the keywords chosen for the firewall program.
Here’s where the alert generated from downloading the malware. From this alert, we added the keyword DLL and EXE.
And this is the alert for catching possible dridex traffic.
Now, we’ll run the firewall program on the pcap.
sudo python3 /opt/suricata_firewall.py eve.json blocklist
Upon starting the program, the malicious IPs are added to the blocklist.
Conclusion
That wraps up the program demonstration. I had to do a fair amount of research to get this program working. Specifically, I had to learn how to run commands from a program using the subprocess library, error handling, and daemon management using supervisor and cron. There are improvements I would like to make including a better method of abstracting the keywords to scan Suricata alerts and a more efficient way to scan the eve.json file.