tl;dr
In this blog post, you'll learn about a dockerized setup of snort3
and its usage for offline and online analyses. I'll demonstrate its
usage and value by creating a Snort rule to detect the loader dubbed
SVCready.
This setup might be especially helpful to
- craft and tweak snort rules running it against some .pcap-file,
- quickly triage a few .pcaps-files for know indicators, or even
- monitor a small segment of a network that has not been equipped with a NIDS yet.
Find all needed files and documentation in this Github-repo. Using the
dockerized Snort installation, is as easy as executing "installing" a
shell function to call snort ...
or running docker-compose up
.
Background on Snort3
Snort was one of the first network intrusion prevention systems. Shortly after its release in 1999, it became the industry standard for network signatures – the infamous snort rules. Snort was originally authored by Martin Roesch, who was the lead developer of the project. Later Cisco acquired Roesch's company Sourcefire and used Snort as a base for its IPS-products.
snort3
is a multithreaded rewrite of the classical but aged network
intrusion detection system snort
. Nowadays, Suricata seems to be
slightly more popular, e.g., snort3
is (currently) not included in
Debian's package archives. However, regarding their performance these
two open-source systems they seem to be on par 1.
The Dockerized Setup
Motivation
Since snort3
has not been included in the package archives of
Debian-based OSes yet, docker is a convenient way of using and
deploying snort3
– both in cloud environments and locally. While
this is probably enough reason to build such a container, another
advantage is having an easy to create, pre-configured, and, therefore,
reproducible environment at hand – something which is especially
helpful in stressful incidents when you don't want to fiddle around
with download libraries and building from source.
There have been pre-existing alternatives. One is a docker container with snort3 provided by Cisco's threat intelligence servie Talos, which is intended for interactive use. However, the source Dockerfile is not available; therefore, I decided to craft my own version, which is geared and configured toward my personal use cases, i.e., triaging .pcaps and crafting rules. The Docker container can be found on the Docker hub as well which is builded and published via Github actions:
Technicalities
The dockerized version consists of a Dockerfile that installs all
necessary dependencies on top of a debian:bookworm-slim
-image. It
then retrieves the sources of libdaq
and snort3
to build both from
source. Afterward, the configuration files are deployed into the
container.
The Dockerfile is accompanied by a docker-compose.yml
, which
illustrates the usage and houses some sane defaults to use the
container in an online, i.e., continuous capturing packages, or in an
offline scenario using the provided directory structure.
The configuration files for the dockerized snort3
are placed with in
/etc/snort
. The most relevant ones, which have been modified, are:
snort.lua
- Main configuration file specifying which rules to use and what logging mechanismus to employ
snort_defaults.lua
- Default variables
/etc/rules/snort.rules
- The Snort rules to use for detection as
specified in
snort.lua
In addition to the default logging, JSON logging is set up in the
Docker, so that the alerts could be easily shipped to a SIEM via the
agent of choice. This is accomplished by adding the following block to
/etc/snort/snort.lua
as described here by Noah Dietrich 2:
alert_json = { file = true, limit = 100, fields = 'timestamp iface src_addr src_port dst_addr dst_port proto action msg priority class sid' }
Usage
Shell Function
It might be most convenient to declare a shell function inside your
favorite shell (.bashrc
or .zshrc
) to run the dockerized snort3
that can be pulled from the Dockerhub 3 or built from
"source" 4.
function snort () { # Spin up a docker container docker run -it --rm --net=host -v $(pwd):/tmp \ snort3 snort -c /etc/snort/snort.lua -k none -l /tmp/ $@; # Fix permissions sudo chmod 644 alert*.txt }
Here, we bind-mount the current working directory into the container
and define some rudimentary configuration for snort. The
chmod
-command in the function definition ensures that the resulting
logs files are readable for an unpriviledged user.
Note that this necessitates that the rules (an eventually the .pcap-files to analyze) are present in the current working directory's.
To use snort3
for continously capturing packets on a specified
network interface, retrieve the name of the NIC in question and supply
it via -i
to the previously defined shellfunction wrapping the
Docker call:
# Retrieve NIC to capture on NIC=$(ip -j a show \ | jq '.[] | .addr_info | .[] | select(.scope=="global") | .label'\ | grep "wlp" | sed 's/"//g') # Run snort snort -R /tmp/snort.rules -i $NIC
docker-compose
If you prefer a more static setup and just want to check .pcap-files,
placed in the sub-directory ./pcaps
using some Snort-rules placed
in ./etc/rules/snort.rules
, then run:
docker-compose up
This will append the arguments in the command
-field of the
docker-compose.yml
to the entrypoint
specified in there.
To run snort3
in continuous online mode, run the following command.
NIC="eth0" # or another NIC available on the host machine docker-compose run snort3 -i ${NIC}
If this is your primary use case, modify the command
-field in the
docker-compose.yml
and set CAPTURE_NIC
in the .env
-file
accordingly:
<snip> command: # capture contiuosly on ${CAPTURE_NIC} as defined in .env-file, ["-i", "${CAPTURE_NIC}"]
Updating rules
If you want to quickly pull all the Snort rules from its community
feed, just run the script named grab_rules.sh
provided by the Git
repo. This will download, extract and move the rules to the file read
by snort3
5. In future versions, I might consider to
incorporate something like pulledpork3
but for now this script
serves me well.
So, after this initial description of the technicalities, let's bring this setup to action and use it for crafting and testing an illustrative Snort rule.
Exemplifying Rule Creation Process For Svcready-Loader
Since the end of April 2022, SVCready has been spread via shellcode in properties of Microsoft Office documents by TA551 6.
In the .pcap provided by Brad Duncan from
malware-traffic-analysis.net 7, we can observe beaconing in
regular intervals after infection. Those are POST-requests adressed to
the three endpoints starting with the string /xl/gate
. Public threat
intelligence suggests that they are used for the following
tasks 8:
/xl/gate/check
- Validate domain
/xl/gate
- Upload information
/xl/gate/task
- Request a task
HTTP-Header Content
But not only the URIs are characteristic, some other information in the HTTP-header looks interesting as well, as the following listing illustrates.
POST /xl/gate HTTP/1.1 Accept: */* Content-Type: application/octet-stream X-JsonSize: 73 g: 3f070578-177d-f039-564e-f46ef128a461 pr: 3 sg: 0910DCC1-63FA-4433-8D22-5379E6F79F91 Authorization: Bearer {602ED31E-B7A8-4E3A-802F-EDCD33E4D5C8} User-Agent: svc/1.0 Host: <some host> Content-Length: 695088 Cache-Control: no-cache
Looking at this exemplary HTTP header, we could infer several
suspicious elements that we could use for detection. Beside the
API-endpoint used for POSTing some stolen data, the user-agent appears
to be rather striking: svc/1.0
. Additionally, there are two strings
in there that look like UUIDs. Those seem to comprise more or less
robust features for detection since fields, like a User-Agent, could
be changed rather easily. To validate this hypothesis static binary
reversing would be needed.
A Potential Snort Rule For Svcready-Beacons
Nevertheless, based on the information in the HTTP headers of those beaconing requests, we might come up with the following Snort rule:
cat << EOF > svcready.rule alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS ( msg:"SVCready-CNC outbound connection"; flow:to_server,established; priority:1; service:http; http_method; content:"POST"; http_uri:path; content:"/xl/gate",nocase; http_header; content:"User-Agent|3A 20|svc/1.0|0D 0A|"; pcre:"/[a-zA-Z]{1,2}\x3A\x20[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\x0D\x0A/"; classtype:trojan-activity; sid:2000001; rev:1; ) EOF
Here, Snort should only consider POST-requests
(http_method;content:"POST";
) from IP-addresses matching $HOME_NET
to externals servers after a connection has already been established
(flow:to_server,established;
). Furthermore, we check the URI-path
for the presence of the API-endpoint regardless of case
(http_uri:path;content:"/xl/gate",nocase;
) and the striking
User-Agent svc/1.0
. Lastly, we ensure the presence of a UUID after a
field-name of one or two letters.
/Note: If you are using Emacs, you might want to check out
snort-mode
, which is major mode for editing snort rules. Though a
little dated, the syntax highlighting and formatting is rather
nice 9./
Applying the Rule
Coming back to our dockerized setup, we could check the newly created
rule (saved as snort.rules
) against B. Duncan's SVCgready-Pcaps in
the following way.
First, retrieve the .pcaps:
cd tmp wget -O ./pcaps/ https://www.malware-traffic-analysis.net/2022/06/08/2022-06-08-SVCready-infection.pcap.zip unzip -d . -P infected pcaps/2022-06-08-SVCready-infection.pcap.zip
Second, run snort with the newly created rule, which will output some
alert_{fast,full,json}.txt
-files.
snort -R svcready.rule -r 2022-06-08-SVCready-infection.pcap
Lastly, check the alerts:
jq -s 'length ' alert_json.txt 332
Note that we are using the shell-function which actually calls the
Docker container as defined above. To access the files and store the
results, the current working directory is mapped to the container's
workdir (its /tmp
-directory). Alternatively, one could use the "more
static" approach with docker-compose
filling pre-defined
directories.
To ensure that the newly created rule does its job, we can peek into
one of the log files produced by snort when applying the rule. Here,
we use alert_json.txt
and see that it actually matched. We have 332
matches:
Looking at the first entry, we could retrieve the relevant metadata:
jq -s '.[0]' alert_json.txt { "timestamp": "06/08-14:40:25.345040", "iface": "2022-06-08-SVCready-infection.pcap", "src_addr": "10.6.6.103", "src_port": 60868, "dst_addr": "XXX.XXX.90.154", "dst_port": 80, "proto": "TCP", "action": "allow", "msg": "SVCready-CNC outbound connection", "priority": 0, "class": "none", "sid": 0 }
Summary
In this blog post, you learned about setting up and running the
intrusion detection system snort3
inside a Docker container. It was
shown how to use it both for processing live packets and .pcap-files
by setting up a conventient shell function. To illustrate its usage
and demonstrate the capabilities of the Docker container, we crafted
an exemplifying Snort rule for a new loader malware named SVCready,
tested it using the Docker container.
Footnotes:
Hoover, C. (2022). Comparative Study of Snort 3 and Suricata Intrusion Detection Systems; https://scholarworks.uark.edu/cgi/viewcontent.cgi?article=1104&context=csceuht
See Dietrich, N. (2021). Snort 3.1.18.0 on Ubuntu 18 & 20 Configuring a Full NIDS & SIEM. pp. 15; https://snort-org-site.s3.amazonaws.com/production/document_files/files/000/012/147/original/Snort_3.1.8.0_on_Ubuntu_18_and_20.pdf
Pull the container via docker pull jgru/snort3:latest
Use the following commands for building the container
git clone https://github.com/jgru/docker-snort3.git cd docker-snort3 docker build . -t snort3