Signature-based IDS / IPS reference. Modes, rule grammar, custom-rule workflow, Snort 2 vs Snort 3 differences. Pairs with Zeek (signature for known-bad fast, behavioural for the long tail). Verified against Snort 3.11.1.0 on Kali.
Side: blue
Modes
snort -V # version banner (confirms Snort 2 vs 3)
snort -dev -i eth0 # sniffer mode (decoded headers + payload)
snort -l <dir> -K ASCII # packet logger (every packet to disk)
sudo snort -r capture.pcap -c snort.conf -A console # offline IDS, alerts to stdout
sudo snort -r capture.pcap -c snort.conf -A full -l /var/log/snort # offline IDS, full alert to disk
sudo snort -A console -q -u snort -g snort -c snort.conf -i eth0 # live NIDS| Mode | Flag pattern | Use |
|---|---|---|
| Sniffer | snort -v | print decoded packets to stdout |
| Packet logger | snort -l <dir> -K ASCII | log every packet to disk |
| NIDS | snort -c <conf> -A <mode> | apply rules, alert via console / fast / full / unified2 |
| IPS (inline) | requires DAQ inline mode | drop / reject in real time |
drop and reject rule actions only fire in inline IPS mode. In offline -r or passive sniffer mode they degrade to alert.
Validate config + rules
sudo snort -T -c /etc/snort/snort.conf # Snort 2: test mode parses config + rules and exits
sudo snort -c /etc/snort/snort.lua --warn-all # Snort 3: parse Lua config, warn-everythingRule structure
A rule is a single line: header (who/where/protocol) + options (what to look for, what to call the alert).
action protocol src_ip src_port direction dst_ip dst_port (option1; option2; ...;)
\-------- Header --------/ \-- Options --/
Header fields
| Field | Values | Notes |
|---|---|---|
| Action | alert, log, pass, drop, reject, sdrop | drop / reject only fire in inline IPS. pass allowlists. log records without alerting |
| Protocol | tcp, udp, icmp, ip | ip catches anything; useful for L3 anomalies |
| IPs | literal 1.2.3.4, CIDR 10.0.0.0/24, variable $HOME_NET, list [1.1.1.1,2.2.2.2], negation !10.0.0.0/8, any | use variables so rules move between environments |
| Ports | literal 80, range 1024:, list [80,443], negation !22, any | port 0 is rare and worth investigating |
| Direction | -> source-to-destination, <> bidirectional | no <- — flip the rule instead |
Common options
Options are separated by ;. The terminating ; is mandatory.
| Option | Purpose | Example |
|---|---|---|
msg | text written to the alert | msg:"possible lateral movement via SMB"; |
content | byte string the rule matches in the payload | content:"GET /shell.php"; |
nocase | make the previous content case-insensitive | content:"admin"; nocase; |
pcre | full regex match | pcre:"/\/[a-z]{8}\.exe$/i"; |
flow | TCP state filter | flow:to_server,established; |
flags | exact TCP flag set | flags:S; (SYN only) |
dsize | payload size constraint | dsize:>1000; |
sid | Snort ID — unique. Custom rules use sid >= 1000000 | sid:1000001; |
rev | revision counter | rev:1; |
classtype | predefined alert category | classtype:attempted-admin; |
priority | numeric severity | priority:1; |
reference | pointer to CVE / URL | reference:cve,2017-0144; |
Flow-state values
to_server/to_client— direction within an established TCP sessionestablished— only after the TCP three-way handshake completesstateless— apply regardless of TCP state (useful on UDP)
Example rules
alert icmp any any -> $HOME_NET any (msg:"ICMP packet detected"; sid:1000001; rev:1;)
Fires on every ICMP packet entering the home network. Cheap demo; in production constrain by itype (e.g. itype:8 for echo request only).
alert tcp any any -> $EXTERNAL_NET 80 (msg:"Suspicious HTTP content";
flow:to_server,established; content:"malware_file"; nocase; sid:1000002; rev:1;)
Outbound HTTP request whose payload contains malware_file. Bound to established so it does not fire on incomplete handshakes.
alert tcp any any -> $HOME_NET 445 (msg:"SMB write to admin share";
flow:to_server,established; content:"\PIPE\svcctl"; sid:1000003; rev:1;)
Lateral-movement candidate — SMB session to TCP/445 with the svcctl named pipe in payload. Cobalt-Strike-style execution chains hit this signature.
drop tcp $EXTERNAL_NET any -> $HOME_NET 22 (msg:"Drop SSH from outside";
flow:to_server,established; sid:1000004; rev:1;)
Inline-mode IPS rule that drops external SSH attempts. Only effective when Snort runs in inline IPS mode.
Custom rule workflow
- Define the traffic — protocol, source/dest IPs (use
$HOME_NET/$EXTERNAL_NETvariables, not literals), ports, direction - Draft the header —
alert tcp $HOME_NET any -> $HOME_NET 21 - Add options —
(msg:"FTP outbound from internal host"; flow:to_server,established; sid:1000010; rev:1;) - Save to
/etc/snort/rules/local.rules(Snort 2) or pass via--rule/ include in Lua config (Snort 3) - Wire into config — confirm
include $RULE_PATH/local.rulesis in section 7 ofsnort.conf(Snort 2) - Validate syntax —
sudo snort -T -c /etc/snort/snort.conf - Test against a known PCAP —
sudo snort -r sample.pcap -c snort.conf -A console
Direction discipline
Direction errors are the single most common rule bug. The header src -> dst describes packets flowing from src to dst. A rule matching server-side replies will not catch client-initiated requests, and vice versa.
# WRONG: matches server-side replies leaving port 445, NOT lateral movement
alert tcp 10.0.0.0/24 445 -> 10.0.0.0/24 any (msg:"SMB"; sid:1000001; rev:1;)
# RIGHT: matches client-initiated SMB push to port 445
alert tcp 10.0.0.0/24 any -> 10.0.0.0/24 445
(msg:"Lateral SMB push"; flow:to_server,established; sid:1000001; rev:1;)
When in doubt, use <> bidirectional and tighten later.
Snort 2 vs Snort 3
Kali ships Snort 3 under /usr/sbin/snort. Most online tutorials and lesson material use Snort 2. The rule grammar is largely portable; the config format is not.
| Aspect | Snort 2 | Snort 3 |
|---|---|---|
| Config | flat-text snort.conf (9 sections) | Lua: snort.lua, snort_defaults.lua |
| CLI | snort -c snort.conf -A console -r file.pcap | snort -c snort.lua -A alert_fast -r file.pcap |
| Inline rule | append to local.rules | --rule '...' flag, or in Lua |
| Validate | snort -T -c snort.conf | snort -c snort.lua --warn-all |
| Distros | older Ubuntu LTS, legacy snort Debian | Kali 2025+, Ubuntu 22.04+ |
# Snort 3 inline rule, no config file
snort --rule 'alert tcp any any -> any 445 (msg:"SMB"; sid:1; rev:1;)' -r capture.pcap -A alert_fastIf you must run Snort 2 syntax exactly, use a VM that ships v2 or install the legacy snort2 package where the distro provides it.
Pitfalls
- Custom SID collision:
sid< 1,000,000 is reserved for official rule sets. In-house rules withsid:1000collide on the next rule-set update. Always usesid >= 1000000. - Direction confusion: see Direction discipline above.
- Preprocessor misconfiguration: rules see normalised input. If
frag3(IP fragmentation) orstream5(TCP reassembly) are off or wrong, attacker traffic that exploits fragmentation or out-of-order delivery slips past every rule. Test against fragmented sample PCAPs before relying on the ruleset. - Encrypted traffic: TLS 1.2/1.3 hides payload.
contentrules become useless against ciphertext. Pivot to Zeek’sssl.log(SNI, JA3, certificate chain) for encrypted-traffic triage. dropin offline mode: Snort run with-r capture.pcapcannot actually drop anything. The action degrades toalert. Test inline rules in inline mode.unified2output: high-volume sites writeunified2binary and parse off-box (Barnyard2 etc.) to keep up with traffic.-A fullto disk during high traffic is a performance trap.- Rule ordering: Snort matches every rule by default.
passrules need higher priority to allowlist before alerts fire. Snort 2 ordering rules differ from Snort 3 — read the docs for the version you run.
links:
Field Manual | Network Security Monitoring | Network Forensics | Zeek