Setting up the Suricata IDPS

Introduction: Perimetric versus in-and-out protection

In a previous post, I described how to set up a basic router in a virtual machine.  One of the things I didn’t include was setting up an IPS to analyze the network traffic and detect bad behaviour.

In the router article, we only had two virtual machines setup: the ISP Lan and the Home Lan.  In practice (and including in my own network), the situation is usually a little more complex: you’ll have several VLANs defined behind your router:

Settings up and IPS: Setup with several VLANs

To use an IPS, you have two alternatives:

  • You only want a perimetric protection: you want to inspect what’s going in and out of your border router, but not analyze and each every packet exchanged between hosts.  That approach will protect you against traffic like malware and botnets, but not against insider trying to attack other machines, and allows for the IPS to be installed on your router itself.
  • You want an in-and-out protection: you’re interested in the traffic between each and every node, no matter if it’s inside the network or with the Internet.  In that case, you need to have all your network traffic be sent back, using techniques such as NetFlow, to your IPS.  I’ve seen that being called Network-as-a-Sensor in some publications.  When this approach is used, it is recommended having the IPS run on a dedicated (virtual) machine to keep a good processes segregation.

As my access point (Linksys E3000) doesn’t have any flows exportation feature, I can setup a real in and out IPS at the moment.  So, we’ll focus on a perimetric protection by installing the Suricata IPS on the router itself, in order to inspect the traffic on the ens4u1c2 interface.

Optional: Preparing the storage

As I put my home router VM storage on an SSD with a very small partition (4 GB), I’ll create another one on my HDD RAID 1 array and place it into Suricata.

This step is pretty specific to my setup and not mandatory.  It will in any case require adaptations versus your setup.

After a new 50 GB volume has been added to the virtual machine as /dev/vdb:

# parted /dev/vdb
(parted) mktable msdos
(parted) mkpart primary xfs 1 -1
(parted) quit
# mkfs.xfs -f /dev/vdb1
# blkid
/dev/vdb1: UUID="84f0430f-7a09-4588-b340-cbe31b4c21bb" TYPE="xfs"
# echo 'UUID="84f0430f-7a09-4588-b340-cbe31b4c21bb" /suricata xfs defaults 0 0' >> /etc/fstab
# mkdir /suricata
# mount -a

Suricata installation and configuration

Initial setup: Getting to know Suricata

Suricata has been packaged by the EPEL guys, who are about the only ones I really trust when it comes to security packages besides RedHat themselves.  At the time I’m writing this, they’ve based the package on Suricata 3.1.2, and the latest version is 3.1.3, so we won’t be too far behind functional-wise.

We’ll skip building from source and install it from the package:

# yum install epel-release
# yum install suricata

If you’re using a dedicated /suricata partition like me:

mv /etc/suricata /suricata/etc
ln -s /suricata/etc /etc/suricata
mkdir -p /suricata/var
mv /var/log/suricata /suricata/var/log
ln -s /suricata/var/log /var/log/suricata

The rest of this section is loosely based on [1].

The configuration file provided with the EPEL package is good to go from the start, but we’ll tune it a little bit.  Start by editing the /etc/suricata/suricata.yaml file.  The provided file is self-explanatory.  We’ll start with only the built-in ruleset at first:

  • decoder-events.rules
  • stream-events.rules
  • http-events.rules
  • smtp-events.rules
  • dns-events.rules
  • tls-events.rules
  • app-layer-events.rules

We’ll start with the following outputs enabled:

  • stats
  • fast
  • eve-log, without payload dump
  • dns-log

Initially, have Suricata listen (af-packet -> interface setting) on the private interface, e.g. eth0 in the setup I described here.

With this initial setup, Suricata will print the raised alerts in logfile, without any active action taken (that’s for later).  The DNS requests will also be logged, even when they are legitimate ones.  Basically, we’re not taking any action yet, only looking at the traffic.

Enable and start Suricata:

# systemctl enable suricata
# systemctl start suricata

If your router acts as the DNS server of the network, running tail -f /var/log/suricata/dns.log should show a lot of activity, especially if you have Windows machines running.

Pulling rules with Pulled Pork

Before advancing with tuning the Suricata settings, we need to setup rules updating.  An IDS/IPS is nothing without updated rules to know what to look for inside the packets.

Oinkmaster was very popular back in the day, but it’s not developed anymore.  PulledPork, on the other hand, still gets updates and will get the job done very well.  To install and configure it, we’ll roughly be following [2] and [3].

# cd
# yum install git perl-libwww-perl perl-Crypt-SSLeay perl-Sys-Syslog perl-Archive-Tar perl-LWP-Protocol-https
# git clone https://github.com/shirkdog/pulledpork.git
# cd pulledpork/
# cp pulledpork.pl /usr/local/bin
# chmod +x /usr/local/bin/pulledpork.pl
# cp -v etc/*.conf /etc/suricata
# mkdir /etc/suricata/rules/iplists
# touch /etc/suricata/rules/iplists/default.blacklist
# /usr/local/bin/pulledpork.pl -V

You should see Pulled Pork start without any alert, but not do anything.  Now, we need to configure it.  As a starter, we’ll use the rules from Emerging Threats (Public platform for IDS rules publication, updated daily) and the IP blacklist from TALOS (Cisco Threat Intelligence platform).  Depending on the country you live in, you might be able to get rules from your local CERT or MISP platform, but I can’t give advice here.

Sadly, it’s not the case in my country of residence 🙁

Back to topic.  My Pulled Pork config file looks like this:

# Config file for pulledpork
# Be sure to read through the entire configuration file
# If you specify any of these items on the command line, it WILL take 
# precedence over any value that you specify in this file!

#######
#######  The below section defines what your oinkcode is (required for 
#######  VRT rules), defines a temp path (must be writable) and also 
#######  defines what version of rules that you are getting (for your 
#######  snort version and subscription etc...)
####### 


# You can specify one or as many rule_urls as you like, they 
# must appear as http://what.site.com/|rulesfile.tar.gz|1234567.  You can specify
# each on an individual line, or you can specify them in a , separated list
# i.e. rule_url=http://x.y.z/|a.tar.gz|123,http://z.y.z/|b.tar.gz|456
# note that the url, rule file, and oinkcode itself are separated by a pipe |
# i.e. url|tarball|123456789, 
rule_url=http://rules.emergingthreats.net/open/suricata|emerging.rules.tar.gz|open-nogpl
rule_url=http://talosintelligence.com/feeds/ip-filter.blf|IPBLACKLIST|open

# Specify rule categories to ignore from the tarball in a comma separated list
# with no spaces.  There are four ways to do this:
# 1) Specify the category name with no suffix at all to ignore the category
#    regardless of what rule-type it is, ie: netbios
# 2) Specify the category name with a '.rules' suffix to ignore only gid 1
#    rulefiles located in the /rules directory of the tarball, ie: policy.rules
# 3) Specify the category name with a '.preproc' suffix to ignore only
#    preprocessor rules located in the /preproc_rules directory of the tarball,
#    ie: sensitive-data.preproc
# 4) Specify the category name with a '.so' suffix to ignore only shared-object
#    rules located in the /so_rules directory of the tarball, ie: netbios.so
# The example below ignores dos rules wherever they may appear, sensitive-
# data preprocessor rules, p2p so-rules (while including gid 1 p2p rules),
# and netbios gid-1 rules (while including netbios so-rules):
# ignore = dos,sensitive-data.preproc,p2p.so,netbios.rules
# These defaults are reasonable for the VRT ruleset with Snort 2.9.0.x.
ignore=deleted.rules,experimental.rules,local.rules

# What is our temp path, be sure this path has a bit of space for rule 
# extraction and manipulation, no trailing slash
temp_path=/tmp

#######
#######  The below section is for rule processing.  This section is 
#######  required if you are not specifying the configuration using
#######  runtime switches.  Note that runtime switches do SUPERSEED 
#######  any values that you have specified here!
#######

# What path you want the .rules file containing all of the processed 
# rules? (this value has changed as of 0.4.0, previously we copied 
# all of the rules, now we are creating a single large rules file
# but still keeping a separate file for your so_rules!
rule_path=/etc/suricata/rules/snort.rules

# What path you want the .rules files to be written to, this is UNIQUE
# from the rule_path and cannot be used in conjunction, this is to be used with the
# -k runtime flag, this can be set at runtime using the -K flag or specified
# here.  If specified here, the -k option must also be passed at runtime, however
# specifying -K <path> at runtime forces the -k option to also be set
out_path=/etc/suricata/rules/

# If you are running any rules in your local.rules file, we need to
# know about them to properly build a sid-msg.map that will contain your
# local.rules metadata (msg) information.  You can specify other rules
# files that are local to your system here by adding a comma and more paths...
# remember that the FULL path must be specified for EACH value.
# local_rules=/path/to/these.rules,/path/to/those.rules
local_rules=/etc/suricata/rules/local.rules

# Where should I put the sid-msg.map file?
sid_msg=/etc/suricata/sid-msg.map

# New for by2 and more advanced msg mapping.  Valid options are 1 or 2
# specify version 2 if you are running barnyard2.2+.  Otherwise use 1
sid_msg_version=2

# Where do you want me to put the sid changelog?  This is a changelog 
# that pulledpork maintains of all new sids that are imported
sid_changelog=/var/log/suricata/sid_changes.log
# this value is optional

#######
#######  The below section is for so_rule processing only.  If you don't
#######  need to use them.. then comment this section out!
#######  Alternately, if you are not using pulledpork to process 
#######  so_rules, you can specify -T at runtime to bypass this altogether
#######

# What path you want the .so files to actually go to *i.e. where is it
# defined in your snort.conf, needs a trailing slash
#sorule_path=/usr/local/lib/snort_dynamicrules/
#sorule_path=/etc/suricata/rules/

# Path to the snort binary, we need this to generate the stub files
#snort_path=/usr/local/bin/snort
#snort_path=/usr/sbin/suricata

# We need to know where your snort.conf file lives so that we can
# generate the stub files
#config_path=/etc/suricata/suricata.yaml

##### Deprecated - The stubs are now  categorically written to the  single rule file!
# sostub_path=/usr/local/etc/snort/rules/so_rules.rules

# Define your distro, this is for the precompiled shared object libs!
# Valid Distro Types:
# Debian-6-0, Ubuntu-10-4
# Ubuntu-12-04, Centos-5-4
# FC-12, FC-14, RHEL-5-5, RHEL-6-0
# FreeBSD-8-1, FreeBSD-9-0, FreeBSD-10-0
# OpenBSD-5-2, OpenBSD-5-3
# OpenSUSE-11-4, OpenSUSE-12-1
# Slackware-13-1
#distro=RHEL-6-0

#######  This next section is optional, but probably pretty useful to you.
#######  Please read thoroughly!

# If you are using IP Reputation and getting some public lists, you will probably
# want to tell pulledpork where your blacklist file lives, PP automagically will
# de-dupe any duplicate IPs from different sources.
black_list=/etc/suricata/rules/iplists/default.blacklist

# IP Reputation does NOT require a full snort HUP, it introduces a concept whereby
# the IP list can be reloaded while snort is running through the use of a control
# socket.  Please be sure that you built snort with the following optins:
# -enable-shared-rep and --enable-control-socket.  Be sure to read about how to
# configure these!  The following option tells pulledpork where to place the version
# file for use with control socket ip list reloads!
# This should be the same path where your black_list lives!
IPRVersion=/etc/suricata/rules/iplists

# The following option tells snort where the snort_control tool is located.
#snort_control=/usr/local/bin/snort_control
#snort_control=/tmp/snort_control

# What do you want to backup and archive?  This is a comma separated list
# of file or directory values.  If a directory is specified, PP will recurse
# through said directory and all subdirectories to archive all files.
# The following example backs up all snort config files, rules, pulledpork
# config files, and snort shared object binary rules.
# backup=/usr/local/etc/snort,/usr/local/etc/pulledpork,/usr/local/lib/snort_dynamicrules/
backup=/etc/suricata

# what path and filename should we use for the backup tarball?
# note that an epoch time value and the .tgz extension is automatically added
# to the backup_file name on completeion i.e. the written file is:
# pp_backup.1295886020.tgz
backup_file=/suricata/backup/pulled-pork

# Where do you want the signature docs to be copied, if this is commented 
# out then they will not be copied / extracted.  Note that extracting them 
# will add considerable runtime to pulledpork.
# docs=/path/to/base/www

# The following option, state_order, allows you to more finely control the order
# that pulledpork performs the modify operations, specifically the enablesid
# disablesid and dropsid functions.  An example use case here would be to
# disable an entire category and later enable only a rule or two out of it.
# the valid values are disable, drop, and enable.
# state_order=disable,drop,enable


# Define the path to the pid files of any running process that you want to
# HUP after PP has completed its run.
# pid_path=/var/run/snort.pid,/var/run/barnyard.pid,/var/run/barnyard2.pid
# and so on...
# pid_path=/var/run/snort_eth0.pid


# This defines the version of snort that you are using, for use ONLY if the 
# proper snort binary is not on the system that you are fetching the rules with
# This value MUST contain all 4 minor version
# numbers. ET rules are now also dependant on this, verify supported ET versions
# prior to simply throwing rubbish in this variable kthx!
# snort_version=2.9.0.0

# Here you can specify what rule modification files to run automatically.
# simply uncomment and specify the apt path.
enablesid=/etc/suricata/enablesid.conf
dropsid=/etc/suricata/dropsid.conf
disablesid=/etc/suricata/disablesid.conf
modifysid=/etc/suricata//modifysid.conf

# What is the base ruleset that you want to use, please uncomment to use
# and see the README.RULESETS for a description of the options.  
# Note that setting this value will disable all ET rulesets if you are 
# Running such rulesets
# ips_policy=security

####### Remember, a number of these values are optional.. if you don't 
####### need to process so_rules, simply comment out the so_rule section
####### you can also specify -T at runtime to process only GID 1 rules.

version=0.7.2

Main elements of this configuration:

  • Everything is adapted to use Suricata paths instead of the Snort defaults
  • All the rules from Emerging Threats are enabled
  • If the ruleset gets any modification, it will be backed up in the /suricata/backups folder
  • Dynamic rules, an element specific to Snort, are disabled

Execute Pulled Pork for the first time by running:

# pulledpork.pl -c /etc/suricata/pulledpork.conf

You should see one or two warnings about unintialized variables, which you can ignore.  The final output should be something like:

Writing /var/log/suricata/sid_changes.log....
        Done
Rule Stats...
        New:-------0
        Deleted:---0
        Enabled Rules:----19204
        Dropped Rules:----0
        Disabled Rules:---4287
        Total Rules:------23491
No IP Blacklist Changes

Done
Please review /var/log/suricata/sid_changes.log for additional details
Fly Piggy Fly!

Now, create the backup folder and modify the suricata.yaml file to add the snort.rules file and restart Suricata:

# mkdir /suricata/backups
# cat /etc/suricata/suricata.yaml
rule-files:
 - snort.rules # Emerging Threats rules downloaded by Pulled Pork
# systemctl restart suricata

Emerging Threats has a lot of rules, not only for malicious behaviour detection, but also to help enforce corporte policies.  Let’s say you have a Dropbox client running in your network.  You should soon see something in the /var/log/suricata/fast.log, like:

11/12/2016-10:53:32.027434  [**] [1:2012648:3] ET POLICY Dropbox Client Broadcasting [**] [Classification: Potential Corporate Privacy Violation] [Priority: 1] {UDP} x.x.x.x:17500 -> x.x.x.x:17500
11/12/2016-10:53:35.468559  [**] [1:2012648:3] ET POLICY Dropbox Client Broadcasting [**] [Classification: Potential Corporate Privacy Violation] [Priority: 1] {UDP} x.x.x.x:17500 -> 255.255.255.255:17500

The ET prefix shows Suricata is now using the Emerging Threats rules on top of the built-in SURICATA rules.

Automatic Pulled Pork rules upgrade

Threats are an ever-changing landscape.  As such, if rules are not updated regularly, they’re useless.  We’ll setup a script to update the rules daily and restart Suricata if they changed.

I came up with this little script that I put under /suricata/sbin/update-rules.sh:

#!/bin/bash
# Suricata Rules updater 
# Written for ev1z.be by Eric Viseur <eric.viseur@gmail.com>

# v1.0
# Changelog
# 1.0 - Initial release

###############
## VARIABLES ##
###############

# Cloned Pulled Pork git repo location
gitrepo=/root/pulledpork
# Pulled Pork binary location
pulledpork=/usr/local/bin/pulledpork.pl
# Pulled Pork configuration file
cfg=/etc/suricata/pulledpork.conf

logMessage() {
        logger -t "update-rules.sh" "$1"
}


###############################
## PULLED PORK BINARY UPDATE ##
###############################

# We pull the master git repo and update the Pulled Pork binary
# if it changed
logMessage "Pulled Pork git repo update"
cd $gitrepo
git pull
if [[ -n $(diff -q pulledpork.pl /usr/local/bin/pulledpork.pl) ]]; then
        logMessage "Pulled Pork git repo has changed, updating the binary"
        cp pulledpork.pl /usr/local/bin/pulledpork.pl
        chmod +x /usr/local/bin/pulledpork.pl
        newVersion=$(grep 'version=' etc/pulledpork.conf | grep -v sid | grep -v snort | sed -s 's/version=//g')
        sed -i '$ d' $cfg
        echo "version=$newVersion" >> $cfg
fi

##################
## RULES UPDATE ##
##################

# Compute current files hash
oldRulesHash=$(sha512sum $(grep 'rule_path=' /etc/suricata/pulledpork.conf | grep -v sorule_path | sed -s 's/rule_path=//g'))
oldIpBlacklistHash=$(sha512sum $(grep 'black_list=' /etc/suricata/pulledpork.conf | sed -s 's/black_list=//g'))
# Execute the Pulled Pork binary to fetch updates
logMessage "Fetching Suricata rules and IP blacklist"
$pulledpork -P -c $cfg

# Compute new files hash
newRulesHash=$(sha512sum $(grep 'rule_path=' /etc/suricata/pulledpork.conf | grep -v sorule_path | sed -s 's/rule_path=//g'))
newIpBlacklistHash=$(sha512sum $(grep 'black_list=' /etc/suricata/pulledpork.conf | sed -s 's/black_list=//g'))

# If the rules or IP blacklist have changed, restart Suricata
if [[ "$oldRulesHash" != "$newRulesHash" ]] || [[ "$oldIpBlacklistHash" != "$newIpBlacklistHash" ]]; then
        logMessage "The rules and/or the IP blacklist have changed, reloading"     
        systemctl restart suricata
fi

Everything the script does is logged to /var/log/messages.  We give it it’s own dedicated logfile through rsyslog configuration:

# cat /etc/rsyslog.d/suricata-update-rules.conf
if $programname == 'update-rules.sh' then /var/log/suricata/update-rules.log
& ~
# systemctl restart rsyslog
# cat /etc/cron.daily/update-rules
#!/bin/sh
/suricata/sbin/update-rules.sh 2>&1 > /dev/null
# chmod +x /etc/cron.daily/update-rules

We don’t need to worry about log rotation as the configuration provided by the Suricata package covers all files in the /var/log/suricata folder.

Using the TALOS IP blacklisting

This section was built by collating info from [7] and [8].

Suricata embeds an IP Reputation mechanism: it is possible to define a “level of trust” for IP addresses and have that level of trust reflect in Suricata rules.  What we’ll do here is use the IP blacklist provided by Talos and process it into a drop local rule.

First, we need to define an IP reputation group to store these IP addresses from Talos.

# mkdir /etc/suricata/iprep
# echo "1,Talos,IP addresses blacklisted by Cisco Talos" > /etc/suricata/iprep/categories.txt

In the suricata.yaml file, enable the IP reputation module:

# IP Reputation
reputation-categories-file: /etc/suricata/iprep/categories.txt
default-reputation-path: /etc/suricata/iprep
reputation-files:
 - talos.txt

In the configuration above, we’re referring to the /etc/suricata/iprep/talos.txt file.  We will tune the update-rules script to create a Suricata-formatted file using the feed received from Talos:

# If the rules or IP blacklist have changed, restart Suricata
if [[ "$oldRulesHash" != "$newRulesHash" ]] || [[ "$oldIpBlacklistHash" != "$newIpBlacklistHash" ]]; then
        logMessage "The rules and/or the IP blacklist have changed, reloading"
        rm /etc/suricata/iprep/talos.txt
        for i in $(cat /etc/suricata/rules/iplists/default.blacklist); do
                echo "$i,1,127" >> /etc/suricata/iprep/talos.txt
        done
        systemctl restart suricata
fi

What we do is remove the existing file if any, and then regenerate it by mapping them all to the IP reputation group 1 (Talos) with the maximum weight (127).  Next, we create a single rule in /etc/suricata/rules/local.rules:

drop tcp any any -> any any (msg:"TALOS Blacklisted IP"; iprep:src,Talos,>,9; sid:1000000; rev:1;)
drop tcp any any -> any any (msg:"TALOS Blacklisted IP"; iprep:dst,Talos,>,9; sid:1000001; rev:1;)

Ensure this rule file is enabled in Suricata and restart it.  Job done !

From Intrusion Detection to Instrusion Protection

In the current setup, Suricata monitors for events but doesn’t take any action.  The next step is thus to move to Intrusion Protection: if selected rules are matched, Suricata will interact with Netfilter (Kernel firewall mechanism living under iptables) to block attacks.  Basically, whenever a packet matches a rule we’ve defined as a DROP, Suricata will drop the packet.  That will happen outside the perimeter of iptables, so the dropped packets due to Suricata will only be visible in the IPS logs.

The official documentation mentions using NFQUEUE to acheive this [4].  There are also options to acheive this using AF_PACKET [5], but due to my setup using multiple VLANs (see the beginning of the article), I couldn’t get it to work properly.  However, since that option is superior performance-wise, I’ll probably look further into it someday.

Firewall & Suricata configuration modifications

If you followed my previous guide about setting up and IPv4 router,  your router firewall is currently in the following state:

  • The FORWARD chain (Packets passing through the router because they are addressed to other hosts) allow all packets that are not invalid;
  • The INPUT chain lets through packets that match a certain amount of rules, denying everything else;
  • The OUTPUT chain lets through all packets that are not invalid.

We will have to adapt this.  The idea is “all packets are to be inspected by Suricata, then go back to the regular iptables processing“.  This, even packets that would have been refused by iptables will still be taken into account by Suricata for threat detection.

We’ll take advantage of iptables’ first match behaviour: the very first rule will be the inspection one, followed by the existing ones, because Suricata will reinject into iptables the packets that don’t trigger a drop rule.

There are two potential catches to that approach:

  • You could get a packet loop, where a packet is accepted then infinitely reinjected in the rule that sends the packets to Suricata.  To avoid that, we use packet marking, as per [6].
  • If you derive all packets to NFQUEUE, it means that if there is no process listening on that queue, the traffic is completely stopped.  To circumvent that, we add a –queue-bypass for the packets purely related to internal traffic.  This means you’ll still be able to work locally if Suricata is down but there will be no Internet access.

So, we add some rules after the policy and before the traffic-specific rules:

...
# Policies
  $iptables -P INPUT DROP
  $iptables -P OUTPUT DROP
  $iptables -P FORWARD DROP

# NFQUEUE inspection rules
  iptables -A FORWARD -i $lanHomeIf     -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A FORWARD -i $lanSrvVMsIf   -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A FORWARD                   -m mark ! --mark 1/1 -j NFQUEUE
  iptables -A INPUT -i $lanHomeIf       -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A INPUT -i $lanSrvVMsIf     -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A INPUT                     -m mark ! --mark 1/1 -j NFQUEUE 
  iptables -A OUTPUT -o $lanHomeIf      -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A OUTPUT -o $lanSrvVMsIf    -m mark ! --mark 1/1 -j NFQUEUE --queue-bypass
  iptables -A OUTPUT                    -m mark ! --mark 1/1 -j NFQUEUE

# Drop all spoofed IPs
  for ip in $spoofIps; do
        $iptables -A INPUT -i $wanIf -s $ip -j DROP
        $iptables -A INPUT -i $wanIf -s $ip -j DROP
  done
...

Don’t restart your firewall now, or you won’t have any Internet access.  Next step is to modify the Suricata configuration:

  • Disable (comment) the pcap, nflog, netmap, pfring, ipfw and af-packet traffic capture methods: we’re not going to use them
  • Enable the nfq method:
    • Set mode to repeat: Suricata inspects the traffic and re-injects the traffic it doesn’t drop
    • Set repeat-mark and repeat-mask to 1: this matches the IPtables rules that prevent the packet loop above.

Next, we modify /etc/sysconfig/suricata to tell it to listen on NFQUEUE 0:

OPTIONS="-q 0 --user suricata "

Now, restart Suricata and reload your firewall.  Now that we’ve got a consistent setup, we can also make Suricata persistent:

# systemctl restart suricata
# systemctl enable suricata
# /opt/firewall.sh

Now, let’s have a look at the iptables rules.  To make things simple, we’ll just look at the OUTPUT chain:

# iptables -vnL OUTPUT
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 2380  672K NFQUEUE    all  --  *      eth0    0.0.0.0/0            0.0.0.0/0            mark match ! 0x1/0x1 NFQUEUE num 0 bypass
 1067 75662 NFQUEUE    all  --  *      eth1    0.0.0.0/0            0.0.0.0/0            mark match ! 0x1/0x1 NFQUEUE num 0 bypass
 1609  116K NFQUEUE    all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x1/0x1 NFQUEUE num 0
 4975  854K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ! state INVALID

What’s interesting are the figures:

  • 2380 + 1067 + 1609 = 5056 packets were sent to Suricata through the NFQUEUE rules.  As we haven’t configured any drop policy on Suricata yet, no packets should disappear.
  • 4975 packets were then processed by the following rule.  The delta is the amount of packets still waiting in the NFQUEUE to be processed by Suricata.  That may look like a lot, but we haven’t done any performance optimization yet.

Let’s drop some ba… packets !

Even though we now have a real integration of Suricata into the packet processing chain, we’re still at the IDS step: we can detect threats but haven’t given our router any gun to shoot down threats.

To do that, we must configure Pulled Pork to change some rules from alert to drop: if a given signature is matched, Suricata will drop the packet.

To select the SIDs, let’s list all the rulesets that Pulled Pork gets from upstream sources:

# cat /etc/suricata/rules/snort.rules | grep "# ----- Begin"
# ----- Begin ET-botcc Rules Category ----- #
# ----- Begin ET-ciarmy Rules Category ----- #
# ----- Begin ET-compromised Rules Category ----- #
# ----- Begin ET-drop Rules Category ----- #
# ----- Begin ET-dshield Rules Category ----- #
# ----- Begin ET-emerging-activex Rules Category ----- #
# ----- Begin ET-emerging-attack_response Rules Category ----- #
# ----- Begin ET-emerging-chat Rules Category ----- #
# ----- Begin ET-emerging-current_events Rules Category ----- #
# ----- Begin ET-emerging-deleted Rules Category ----- #
# ----- Begin ET-emerging-dns Rules Category ----- #
# ----- Begin ET-emerging-dos Rules Category ----- #
# ----- Begin ET-emerging-exploit Rules Category ----- #
# ----- Begin ET-emerging-ftp Rules Category ----- #
# ----- Begin ET-emerging-games Rules Category ----- #
# ----- Begin ET-emerging-icmp Rules Category ----- #
# ----- Begin ET-emerging-icmp_info Rules Category ----- #
# ----- Begin ET-emerging-imap Rules Category ----- #
# ----- Begin ET-emerging-inappropriate Rules Category ----- #
# ----- Begin ET-emerging-info Rules Category ----- #
# ----- Begin ET-emerging-malware Rules Category ----- #
# ----- Begin ET-emerging-misc Rules Category ----- #
# ----- Begin ET-emerging-mobile_malware Rules Category ----- #
# ----- Begin ET-emerging-netbios Rules Category ----- #
# ----- Begin ET-emerging-p2p Rules Category ----- #
# ----- Begin ET-emerging-policy Rules Category ----- #
# ----- Begin ET-emerging-pop3 Rules Category ----- #
# ----- Begin ET-emerging-rpc Rules Category ----- #
# ----- Begin ET-emerging-scada Rules Category ----- #
# ----- Begin ET-emerging-scan Rules Category ----- #
# ----- Begin ET-emerging-shellcode Rules Category ----- #
# ----- Begin ET-emerging-smtp Rules Category ----- #
# ----- Begin ET-emerging-snmp Rules Category ----- #
# ----- Begin ET-emerging-sql Rules Category ----- #
# ----- Begin ET-emerging-telnet Rules Category ----- #
# ----- Begin ET-emerging-tftp Rules Category ----- #
# ----- Begin ET-emerging-trojan Rules Category ----- #
# ----- Begin ET-emerging-user_agents Rules Category ----- #
# ----- Begin ET-emerging-voip Rules Category ----- #
# ----- Begin ET-emerging-web_client Rules Category ----- #
# ----- Begin ET-emerging-web_server Rules Category ----- #
# ----- Begin ET-emerging-web_specific_apps Rules Category ----- #
# ----- Begin ET-emerging-worm Rules Category ----- #
# ----- Begin ET-tor Rules Category ----- #

There, you have two options:

  • You can block entire groups of rules, e.g. ET-botcc;
  • You can block specific rules, based on their SID.  You need to get this information directly from the snort.rules file.

Let’s say you want to block some groups.  They need to be added in /etc/suricata/dropsid.conf:

ET-botcc
ET-ciarmy
ET-compromised
ET-dshield
ET-emerging-dos
ET-emerging-exploit
ET-emerging-malware
ET-emerging-mobile_malware
ET-emerging-netbios

Then, have the update-rules.sh script we made earlier run and observe the output:

Rule Stats...
        New:-------0
        Deleted:---0
        Enabled Rules:----16675
        Dropped Rules:----2529
        Disabled Rules:---4287
        Total Rules:------23491

We now have 2529 rules that will result in traffic being dropped instead of just generating an alert.  Congratulations !  You now have a full-fledged IPS working on your network.  And this is the exact reason why you now need to that some time, read about Suricata, analyze the configuration file, and make a configuration that fits your needs.  This guide only tells you about having a working setup, not necessarily a setup that fully fits your needs.

I’m working on a small home network with a few VLANs, you may have something much more complex.  An IPS is a complex security tool that requires some site-specific tuning to fit all the needs, and this is outside this guide’s scope.

However, a further article will cover integration of the IPS into a SIEM to make alerts and drop more visible than just the fast.log file.

References