Suricata rule writing guide
Using diligent Suricata testing and correct syntax, one can develop signatures based on forensics of events tested on a VM setup. A core base of rules are at the Emerging Threat Suricata rules download, installed with the update command shown lower down, and the Corelight rules alerts feed.
Setup Linux for Suricata testing
Setup of Suricata on a Linux VM can help with defining logic, syntax and working alerting. The bulk of the Suricata configuration is located at /etc/suricata and the rules are stored at /var/lib/suricata/rules.
It's important to note for Suricata to monitor the VM network interface, promiscuous port monitoring should be on ($IFACE replaced with the interface identity):
sudo /sbin/ip link set $IFACE promisc on
VMware when working with a promiscuous network adapter should prompt for monitoring, on startup. Also, when stopping the Suricata service, the interface stats are displayed in suricata.log when it's run correctly:
[2560 - Suricata-Main] 2025-04-24 14:59:51 Notice: suricata: Signal Received. Stopping engine.
[2560 - Suricata-Main] 2025-04-24 14:59:52 Info: suricata: time elapsed 181.453s
[2566 - FR#01] 2025-04-24 14:59:53 Perf: flow-manager: 55 flows processed
[2563 - W#01-ens33] 2025-04-24 14:59:53 Perf: af-packet: ens33: (W#01-ens33) kernel: Packets 684, dropped 0
[2564 - W#02-ens33] 2025-04-24 14:59:53 Perf: af-packet: ens33: (W#02-ens33) kernel: Packets 154, dropped 0
To start with a good set of structured rules, download Emerging Threat rules with the simple command:
sudo suricata-update
This gives a good set of Suricata rules into /var/lib/suricata/rules. The location of the rules is set in the configuration file /etc/suricata/suricata.yaml as shown below:
default-rule-path: /etc/suricata/rules
other rules
rule-files: - suricata.rules
- local.rules
To utilise a test rule and associated command, the instructions on the Suricata quickstart / alerting guide show there is a rule created just for confirming setup. The test rule is found by looking through the downloaded Emerging Threat rules:
sudo cat /var/lib/suricata/rules/suricata.rules | grep 2100498
Then you need to restart after any rule changes including downloading all these Emerging Threat rules:
sudo systemctl restart suricata
Check the /var/log/suricata/suricata.log output by using tail -f suricata.log on the file in a window. If there is a cluster error message 'cluster-id 97 already in use' it's likely to be true and the cluster-id value in suricata.yaml needs changing. Which requires running the test command:
curl http://testmynids.org/uid/index.html
The below output should now be seen in the log when the test has returned the following string to the CLI: uid=0(root) gid=0(root) groups=0(root):
[1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 217.160.0.187:80 -> 10.0.0.23:41618
In order to start writing and adapting one's own rules, we create the local.rules file in /var/lib/suricata/rules.
Writing Suricata rules
An example of an overly noisy alert to start with is the below one which will fire for any ICMP traffic:
alert icmp any any -> any any (msg:"ICMP Packet found on the system this is a noisy alert"; sid:201010;)
The structure of Suricata rules must follow the following format:
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ET TROJAN Likely Bot Nick in IRC (USA +..)"; flow:established,to_server; flowbits:isset,is_proto_irc; content:"NICK "; pcre:"/NICK .*USA.*[0-9]{3,}/i"; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
To alert on failed attempts to a site:
alert http any any -> any any (http_response_line; content:"403 Forbidden"; sid:1;)
To alert on unusual contents in an HTTP header:
alert ip any any -> any any (ipv4.hdr; content:"|3A|"; offset:9; depth:1; sid:1234; rev:5;)
To pick up on ICMP SMURF scanning:
alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"GPL SCAN Broadscan Smurf Scanner"; dsize:4; icmp_id:0; icmp_seq:0; itype:8; classtype:attempted-recon; sid:2100478; rev:4;)
Using regular expressions with a content match allows the best performance; this is used with the pcre command, Perl-compatible regular expression:
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ET TROJAN Likely Bot Nick in IRC (USA +..)"; flow:established,to_server; flowbits:isset,is_proto_irc; content:"NICK "; pcre:"/NICK .*USA.*[0-9]{3,}/i"; reference:url,doc.emergingthreats.net/2008124; classtype:trojan-activity; sid:2008124; rev:2;)
sid: in the written signature, otherwise in testing you will see a signature error in suricata.log.The following two resources can guide on the exact meaning of the rule components: