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
ModeFlag patternUse
Sniffersnort -vprint decoded packets to stdout
Packet loggersnort -l <dir> -K ASCIIlog every packet to disk
NIDSsnort -c <conf> -A <mode>apply rules, alert via console / fast / full / unified2
IPS (inline)requires DAQ inline modedrop / 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-everything

Rule 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
FieldValuesNotes
Actionalert, log, pass, drop, reject, sdropdrop / reject only fire in inline IPS. pass allowlists. log records without alerting
Protocoltcp, udp, icmp, ipip catches anything; useful for L3 anomalies
IPsliteral 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, anyuse variables so rules move between environments
Portsliteral 80, range 1024:, list [80,443], negation !22, anyport 0 is rare and worth investigating
Direction-> source-to-destination, <> bidirectionalno <- — flip the rule instead
Common options

Options are separated by ;. The terminating ; is mandatory.

OptionPurposeExample
msgtext written to the alertmsg:"possible lateral movement via SMB";
contentbyte string the rule matches in the payloadcontent:"GET /shell.php";
nocasemake the previous content case-insensitivecontent:"admin"; nocase;
pcrefull regex matchpcre:"/\/[a-z]{8}\.exe$/i";
flowTCP state filterflow:to_server,established;
flagsexact TCP flag setflags:S; (SYN only)
dsizepayload size constraintdsize:>1000;
sidSnort ID — unique. Custom rules use sid >= 1000000sid:1000001;
revrevision counterrev:1;
classtypepredefined alert categoryclasstype:attempted-admin;
prioritynumeric severitypriority:1;
referencepointer to CVE / URLreference:cve,2017-0144;
Flow-state values
  • to_server / to_client — direction within an established TCP session
  • established — only after the TCP three-way handshake completes
  • stateless — 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

  1. Define the traffic — protocol, source/dest IPs (use $HOME_NET/$EXTERNAL_NET variables, not literals), ports, direction
  2. Draft the headeralert tcp $HOME_NET any -> $HOME_NET 21
  3. Add options(msg:"FTP outbound from internal host"; flow:to_server,established; sid:1000010; rev:1;)
  4. Save to /etc/snort/rules/local.rules (Snort 2) or pass via --rule / include in Lua config (Snort 3)
  5. Wire into config — confirm include $RULE_PATH/local.rules is in section 7 of snort.conf (Snort 2)
  6. Validate syntaxsudo snort -T -c /etc/snort/snort.conf
  7. Test against a known PCAPsudo 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.

AspectSnort 2Snort 3
Configflat-text snort.conf (9 sections)Lua: snort.lua, snort_defaults.lua
CLIsnort -c snort.conf -A console -r file.pcapsnort -c snort.lua -A alert_fast -r file.pcap
Inline ruleappend to local.rules--rule '...' flag, or in Lua
Validatesnort -T -c snort.confsnort -c snort.lua --warn-all
Distrosolder Ubuntu LTS, legacy snort DebianKali 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_fast

If 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 with sid:1000 collide on the next rule-set update. Always use sid >= 1000000.
  • Direction confusion: see Direction discipline above.
  • Preprocessor misconfiguration: rules see normalised input. If frag3 (IP fragmentation) or stream5 (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. content rules become useless against ciphertext. Pivot to Zeek’s ssl.log (SNI, JA3, certificate chain) for encrypted-traffic triage.
  • drop in offline mode: Snort run with -r capture.pcap cannot actually drop anything. The action degrades to alert. Test inline rules in inline mode.
  • unified2 output: high-volume sites write unified2 binary and parse off-box (Barnyard2 etc.) to keep up with traffic. -A full to disk during high traffic is a performance trap.
  • Rule ordering: Snort matches every rule by default. pass rules need higher priority to allowlist before alerts fire. Snort 2 ordering rules differ from Snort 3 — read the docs for the version you run.

Field Manual | Network Security Monitoring | Network Forensics | Zeek