A Blog on digital investigations

Dockerized Snort3 for Rule Crafting and Triage Forensics

<2022-08-31>

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

  1. craft and tweak snort rules running it against some .pcap-file,
  2. quickly triage a few .pcaps-files for know indicators, or even
  3. 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:

https://hub.docker.com/repository/docker/jgru/snort3

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:

1

Hoover, C. (2022). Comparative Study of Snort 3 and Suricata Intrusion Detection Systems; https://scholarworks.uark.edu/cgi/viewcontent.cgi?article=1104&context=csceuht

2

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

3

Pull the container via docker pull jgru/snort3:latest

4

Use the following commands for building the container

git clone https://github.com/jgru/docker-snort3.git
cd docker-snort3
docker build . -t snort3
Tags: DFIR TI