24 November 2021
Understanding Suricata Signatures
The first tutorial in this series explained how to install and configure Suricata. If you followed that tutorial, you also learned how to download and update Suricata rulesets, and how to examine logs for alerts about suspicious activity. However, the rules that you downloaded in that tutorial are numerous, and cover many different protocols, applications, and attack vectors that may not be relevant to your network and servers.
In this tutorial you’ll learn how Suricata signatures are structured, and some important options that are commonly used in most rules. Once you are familiar with how to understand the structure and fields in a signature, you’ll be able to write your own signatures that you can combine with a firewall to alert you about most suspicious traffic to your servers, without needing to use other external rulesets.
This approach to writing and managing rules means that you can use Suricata more efficiently, since it only needs to process the specific rules that you write. Once you have a ruleset that describes the majority of the legitimate and suspicious traffic that you expect to encounter in your network, you can start to selectively drop invalid traffic using Suricata in its active Intrusion Prevention (IPS) mode. The next tutorial in this series will explain how to enable Suricata’s IPS functionality.
For the purposes of this tutorial, you can run Suricata on any system, since signatures generally do not require any particular operating system. If you are following this tutorial series, then you should already have:
Understanding the Structure of Suricata Signatures
Suricata signatures can appear complex at first, but once you learn how they are structured, and how Suricata processes them, you’ll be able to create your own rules to suit your network’s requirements.
At a high level, Suricata signatures consist of three parts:
- An Action to take when traffic matches the rule.
- A Header that describes hosts, IP addresses, ports, protocols, and the direction of traffic (incoming, or outgoing).
- Options, which specify things like the Signature ID (
sid), log message, regular expressions that match the contents of packets, classification type, and other modifiers that can help narrow identify legitimate and suspicious traffic.
The general structure of a signature is the following:
[label Generic Rule Structure]
ACTION HEADER OPTIONS
The header and options portions of a signature have multiple sections. For example, in the previous tutorial, you tested Suricata using the rule with
sid 2100498. Here is the complete rule for reference:
alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)
alert portion of the signature is the action, the
ip any any -> any any section is the header, and the rest of the signature starting with
(msg:GPL ATTACK_RESPONSE... contains the rule’s options.
In the following sections you’ll examine each part of a Suricata rule in detail.
The first part of the
sid:2100498 signature is the action, in this case
alert. The action portion of a Suricata signature specifies the action to take when a packet matches the rule. An action can be one of the following depending on whether Suricata is operating in IDS or IPS mode:
- Pass - Suricata will stop scanning the packet and allow it, without generating an alert.
- Drop - When working in IPS mode, Suricata will immediately stop processing the packet and generate an alert. If the connection that generated the packet uses TCP it will time out.
- Reject - When Suricata is running IPS mode, a TCP reset packet will be sent, and Suricata will drop the matching packet.
- Alert - Suricata will generate an alert and log it for further analysis.
Each Suricata signature has a header section that describes the network protocol, source and destination IP addresses, ports, and direction of traffic. Referring to the example
sid:2100498 signature, the header section of the rule is the highlighted
<^>ip any any -> any any<^> portion:
alert <^>ip any any -> any any<^> (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)
The general format of a rule’s header section is:
[label Rule Format]
<PROTOCOL> <SOURCE IP> <SOURCE PORT> -> <DESTINATION IP> <DESTINATION PORT>
The Protocol can be one of the following:
- A number of other application protocols
The Source and Destination fields can be IP addresses or network ranges, or the special value
any, which will match all IP addresses and networks. The
-> arrow indicates the direction of traffic.
Note: Signatures can also use a non-directional marker
<> that will match traffic in both directions. However, the Suricata documentation about directional markers notes that most rules will use the
-> right matching arrow.
If you wanted to alert on malicious outbound traffic (that is traffic leaving your network), then the Source field would be the IP address or network range of your system. The Destination could be a remote system’s IP or network, or the special
Conversely, if you wanted to generate an alert for malicious incoming traffic, the Source field could be set to
any, and the Destination to your system’s IP address or network range.
You can also specify the TCP or UDP port to examine using the Port fields. Generally, traffic originating from a system is assigned a random port, so the
any value is appropriate for the left side of the
-> indicator. The destination port can also be
any if you plan to examine the contents of every incoming packet, or you can limit a signature to only scan packets on individual ports, like 22 for SSH traffic, or 443 for HTTPS.
ip any any -> any any header from
sid:2100498 is a generic header that will match all traffic, regardless of protocol, source or destination IPs, or ports. This kind of catch all header is useful when you want to ensure inbound and outbound traffic is checked for suspicious content.
Note that the Source, Destination, and Port fields can also use the special
! negation operator, which will process traffic that does not match the value of the field.
For example, the following signature would make Suricata alert on all incoming SSH packets from
any network that are destined for your network (represented by the
203.0.113.0/24 IP block), that are not destined for port 22:
[label Example Header]
alert <^>ssh any any -> 203.0.113.0/24 !22<^> (sid:1000000;)
This alert would not be that useful, since it does not contain any message about the packet, or a classification type. To add extra information to an alert, as well as match on more specific criteria, Suricata rules have an Options section where you can specify a number of additional settings for a signature.
The arguments inside the parenthesis
(. . .) in a Suricata signature contain various options and keyword modifiers that you can use to match on specific parts of a packet, classify a rule, or log custom messages. Whereas a rule’s header arguments operate on packet headers at the IP, port, and protocol level, options match on the data contained inside a packet.
Options in a Suricata rule must be separated by a
; semicolon, and generally use a key:value format. Some options do not have any settings and only the name needs to be specified in a rule.
Using the example signature from the previous section, you could add the
msg option with a value of
SSH traffic detected on non-SSH port explaining what the alert is about:
[label Example Header]
alert ssh any any -> 203.0.113.0/24 !22 (<^>msg:"SSH TRAFFIC on non-SSH port";<^> sid:1000000;)
A full explanation of how you can use each option in a Suricata rule is beyond the scope of this tutorial. The Suricata rules documentation beginning in Section 6.2 describes each keyword option in detail.
However, there are some core options like the
content keyword and various Meta keywords that are used in most signatures, which we’ll examine in the following sections.
One of the most important options for any rule is the
content keyword. Recall the example
alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; <^>content:"uid=0|28|root|29|";<^> classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)
<^>content:"uid=0|28|root|29|";<^> portion contains the
content keyword, and the value that Suricata will look for inside a packet. In the case of this example signature, all packets from any IP address on any port will be checked to ensure they do not contain the string value
uid=0|28|root|29| (which in the previous tutorial was used as an example indicating a compromised host).
content keyword can be used with most other keywords in Suricata. You can create very specific signatures using combinations of headers, and options that target specific application protocols, and then check packet contents for individual bytes, strings, or matches using regular expressions.
For example, the following signature examines DNS traffic looking for any packet with the contents
your_domain.com and generates an alert:
[label dns.query Example]
alert <^>dns<^> any any -> any any (msg:"DNS LOOKUP for your_domain.com"; <^>dns.query; content:"your_domain.com";<^> sid:1000001;)
However, this rule would not match if the DNS query used the domain
YOUR_DOMAIN.COM, since Suricata defaults to case-sensitive content matching. To make content matches insensitive to case, add the
nocase; keyword to the rule:
[label Case-insensitive dns.query Example]
alert dns any any -> any any (msg:"DNS LOOKUP for your_domain.com"; dns.query; content:"your_domain.com"; <^>nocase;<^> sid:1000001;)
Now any combination of lower or uppercase letters will still match the
The example signatures in this tutorial have all contained
msg keywords with information about a signature. While the
msg option is not required, leaving it blank makes it difficult to understand why an alert or drop action has occurred when examining Suricata’s logs.
msg option is designed to be a human-readable text description of an alert. It should be descriptive and add context to an alert so that you or someone else who is analyzing logs understand why the alert was triggered. In the [
reference Keyword) section of this tutorial you will learn about the
reference option that you can use to link to more information about a signature and the issue it is designed to detect.
Every Suricata signature needs a unique Signature ID (
sid). If two rules have the same
sid (in the following example output it is
sid:10000000), Suricata will not start and will instead generate an error like the following:
[secondary_label Example Duplicate sid Error]
. . .
19/11/2021 -- 01:17:40 - <Error> - [ERRCODE: SC_ERR_DUPLICATE_SIG(176)] - Duplicate signature "drop ssh any any -> 127.0.0.0/8 !22 (msg:"blocked invalid ssh"; sid:10000000;)"
. . .
When you create your own signatures, the range 1000000-1999999 is reserved for custom rules. Suricata’s built-in rules are in the range from 2200000-2299999. Other
sid ranges are documented on the Emerging Threats SID Allocation page.
sid option is usually the last part of a Suricata rule. However, if there have been multiple versions of a signature with changes over time, there is a
rev option that is used to specify the version of a rule. For example, the SSH alert from earlier in this tutorial could be changed to only scan for SSH traffic on port 2022:
[label Example SSH Signature with rev]
alert ssh any any -> 203.0.113.0/24 <^>2022<^> (msg:"SSH TRAFFIC on non-SSH port"; sid:1000000; <^>rev:2;<^>)
The updated signature now includes the
rev:2 option, indicating it has been updated from a previous version.
reference keyword is used in signatures to describe where to find more information about the attack or issue that a rule is meant to detect. For example, if a signature is designed to detect a new kind of exploit or attack method, the reference field can be used to link to a security researcher or company’s website that documents the issue.
The Heartbleed vulnerability in OpenSSL is an example of a widely publicized and researched bug. Suricata comes with signature that is designed to check for incorrect TLS packets and includes a reference to the main Heartbleed CVE entry :
alert tls any any -> any any (msg:"SURICATA TLS invalid heartbeat encountered, possible exploit attempt (heartbleed)"; flow:established; app-layer-event:tls.invalid_heartbeat_message; flowint:tls.anomaly.count,+,1; classtype:protocol-command-decode; <^>reference:cve,2014-0160;<^> sid:2230013; rev:1;)
Note the highlighted
<^>reference:cve,2014-0160;<^> portion of the signature. This reference option tells you or the analyst who is examining alerts from Suricata where to find more information about the particular issue.
The reference option can use any of the prefixes from the
/etc/suricata/reference.config file. For example,
url could be used in place of
cve in the preceding example, with a link directly to the Heartbleed site in place of the
2014-0160 CVE identifier.
Suricata can classify traffic according to a preconfigured set of categories that are included when you install the Suricata package with your Linux distribution’s package manager. The default classification file is usually found in
/etc/suricata/classification.config and contains entries like the following:
# config classification:shortname,short description,priority
config classification: not-suspicious,Not Suspicious Traffic,3
config classification: unknown,Unknown Traffic,3
config classification: bad-unknown,Potentially Bad Traffic, 2
. . .
As indicated by the file header, each classification entry has three fields:
- A short, machine readable name, in the above examples
- The description for a classification to be used with alerts, for example
Not Suspicious Traffic.
- A priority field, which determines the order in which a signature will be processed by Suricata. The highest priority is the value 1. Signatures that use a classifier with a higher priority will get checked first when Suricata processes a packet.
In the example
sid:2100498 signature, the classtype is
<^>classtype:bad-unknown;<^>, which is highlighted in the following example:
alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; <^>classtype:bad-unknown;<^> sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)
The implicit priority for the signature is 2, since that is the value that is assigned to the
bad-unknown classtype in
/etc/suricata/classification.config. If you would like to override the default priority for a classtype, you can add a
priority:n option to a signature, where
n is a value from 1 to 255.
Another useful option in Suricata signatures is the
target option. It can be set to one of two values:
dest_ip. The purpose of this option is to correctly identify the
target hosts in Suricata’s alert logs.
For example, the SSH signature from earlier in this tutorial can be enhanced with the
[label Example SSH Signature with target field]
alert ssh any any -> 203.0.113.0/24 2022 (msg:"SSH TRAFFIC on non-SSH port"; <^>target:dest_ip;<^> sid:1000000; rev:3;)
This example uses
dest_ip because the rule is designed to check for SSH traffic coming into our example network, so it is the destination. Adding the
target oiption to a rule will result in the following extra fields in the
alert portion of an
eve.json log entry.
. . .
. . .
With these entries in Suricata’s logs, they can be sent to a Security Information and Event Management (SIEM) tool to make it easier to search for alerts that might be originating from a common host, or attacks that are directed to a specific target on your network.
In this tutorial you examined each of the main sections that make a complete Suricata signature. Each of the Actions, Header, and Options sections in a rule have multiple options and support scanning packets using many different protocols. While this tutorial did not explore any of the sections in great depth, the structure of rule, and the important fields in the examples should be enough to get started writing your own rules.
If you want to explore complete signatures that include many more options than the ones described in this tutorial, explore the files in the
/etc/suricata/rules directory. If there is a field in a rule that you would like to know more about, the Suricata Rules Documentation is the authoritative resource on what each option and its possible values mean.
Once you are comfortable reading and testing signatures, you can proceed to the next tutorial in this series. In it you will learn how to enable Suricata’s IPS mode, which is used to drop suspicious traffic as opposed to the default IDS mode that only generates alerts.