IDS/IPS
Intrusion detection and prevention — Snort and Suricata rule management and tuning.
IDS/IPS — Suricata and Snort
IDS vs IPS
IDS (Intrusion Detection System): - Passive — monitors a copy of traffic (span port or tap) - Alerts only, does not block - No risk of breaking production traffic - Mode: af-packet with copy, or pcap IPS (Intrusion Prevention System): - Inline — sits in the traffic path - Can drop, reject, or modify packets - Risk: false positive = legitimate traffic blocked - Mode: af-packet inline, netfilter (NFQUEUE)
Suricata Basics
sudo suricata -c /etc/suricata/suricata.yaml -i eth0
# First configure iptables to send traffic to NFQUEUE
sudo iptables -I FORWARD -j NFQUEUE --queue-num 0
sudo suricata -c /etc/suricata/suricata.yaml -q 0
sudo suricata -c /etc/suricata/suricata.yaml -r capture.pcap -l /var/log/suricata/
sudo systemctl status suricata
sudo suricatasc -c "uptime"
sudo suricata-update
sudo suricata-update list-sources
sudo suricata-update enable-source et/open
sudo systemctl restart suricata
Suricata Rule Syntax
action protocol src_ip src_port -> dst_ip dst_port (rule options) action: alert | pass | drop | reject protocol: tcp | udp | icmp | http | dns | tls | ssh direction: -> (to dst) | <> (bidirectional)
alert ssh any any -> $HOME_NET 22 (msg:"SSH Brute Force Attempt"; \
flow:to_server,established; \
threshold:type both, track by_src, count 5, seconds 60; \
classtype:attempted-admin; sid:1000001; rev:1;)
alert udp $HOME_NET any -> !$DNS_SERVERS any (msg:"DNS Query to Non-Standard Server"; \
dns.query; content:"."; \
classtype:policy-violation; sid:1000002; rev:1;)
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Suspicious User-Agent - Possible C2"; \
http.user_agent; content:"Mozilla/4.0"; \
content:!"MSIE"; \
classtype:trojan-activity; sid:1000003; rev:1;)
drop icmp any any -> any any (msg:"ICMP Tunnel - Oversized Payload"; \
dsize:>800; \
classtype:policy-violation; sid:1000004; rev:1;)
alert tcp any any -> $HOME_NET any (msg:"Possible Mimikatz Usage"; \
flow:established; \
content:"mimikatz"; nocase; \
content:"sekurlsa"; nocase; distance:0; \
classtype:credential-access; sid:1000005; rev:1;)
Suricata Log Files
# Watch alerts in real time
tail -f /var/log/suricata/fast.log
# Count alerts by signature
awk -F'\\[\\*\\*\\]' '{print $2}' /var/log/suricata/fast.log \
| sort | uniq -c | sort -rn | head -20
# Parse alerts from eve.json
jq 'select(.event_type=="alert") | {timestamp, src_ip, dest_ip, alert: .alert.signature}' \
/var/log/suricata/eve.json | head -50
# Count alerts by severity
jq -r 'select(.event_type=="alert") | .alert.severity | tostring' \
/var/log/suricata/eve.json | sort | uniq -c | sort -rn
# Extract DNS queries
jq 'select(.event_type=="dns") | {timestamp, src_ip, query: .dns.rrname, type: .dns.rrtype}' \
/var/log/suricata/eve.json | head -20
# TLS fingerprints — JA3 hashes
jq 'select(.event_type=="tls") | {timestamp, src_ip, dest_ip, ja3: .tls.ja3.hash, sni: .tls.sni}' \
/var/log/suricata/eve.json | head -20
# Check for packet drops — drops mean Suricata can't keep up
awk '/capture.kernel_drops/ {print}' /var/log/suricata/stats.log | tail -5
Snort vs Suricata Comparison
| Feature | Suricata | Snort 3 |
|---|---|---|
Threading |
Multi-threaded (worker, autofp) |
Multi-threaded (Snort 3) |
Protocol parsing |
Built-in (HTTP, TLS, DNS, SSH, SMB) |
Inspectors (HTTP, SSL, DNS) |
Output format |
EVE JSON (structured) |
Unified2, JSON (Snort 3) |
Rule format |
Snort-compatible + extensions |
Snort rule language |
Lua scripting |
Yes — custom detection scripts |
Yes — via inspectors |
JA3/JA3S |
Native support |
Plugin required |
File extraction |
Built-in |
Built-in (Snort 3) |
IPS mode |
af-packet inline, NFQUEUE |
af-packet inline, DAQ |
Performance Tuning
# /etc/suricata/suricata.yaml
threading:
set-cpu-affinity: yes
detect-thread-ratio: 1.5 # 1.5x detection threads per capture thread
af-packet:
- interface: eth0
threads: 4 # Match physical cores
cluster-type: cluster_flow
defrag: yes
# Disable a specific rule by SID
echo "1:2100498" >> /etc/suricata/disable.conf
suricata-update
sudo suricatasc -c "dump-counters" | jq '.message | {
packets: .capture.kernel_packets,
drops: .capture.kernel_drops,
alerts: .detect.alert,
flows: .flow.tcp
}'
Wazuh Integration
Wazuh reads Suricata’s eve.json natively. Configure the log collector on the Wazuh agent.
<!-- /var/ossec/etc/ossec.conf on the Suricata host -->
<localfile>
<log_format>json</log_format>
<location>/var/log/suricata/eve.json</location>
</localfile>
Wazuh has built-in decoders and rules for Suricata (rule group suricata). Alerts appear in the Wazuh dashboard with full Suricata metadata — source/dest IP, protocol, signature, severity.
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="suricata,">
<rule id="100010" level="12">
<if_sid>86601</if_sid>
<field name="alert.severity">1</field>
<description>Suricata: Critical severity alert — $(alert.signature)</description>
</rule>
</group>
Bypass / Pass Rules
pass tcp 10.50.1.200 any -> $HOME_NET any (msg:"Allow Nessus Scanner"; sid:1000099; rev:1;)
Pass rules must appear before alert rules in the rule file, or use a lower sid. Suricata evaluates pass rules first when rule-action-order is set to pass, drop, reject, alert.