Suricata Network IDS
Network-based detection using Suricata intrusion detection system
Overview
Suricata rules are specifically designed to detect attack patterns targeting the WordPress honeypot, organized by CVE and OWASP WSTG categories. SIDs range from 1000001 to 1000070+. The individual signatures, with their scores and provenance, are catalogued alongside every other rule on the Pattern Database page, which is generated from the same patterns/patterns.json the dashboard uses.
Rule Anatomy
Rules are grouped by the WSTG category they exercise. A rule carries a stable SID, a severity, and a signature that becomes the human-readable reason string when an alert fires.
alert http any any -> $HOME_NET any (
msg:"HONEYPOT CVE-2023-28121 WooCommerce Payments auth bypass";
flow:to_server,established;
http.uri; content:"/wp-json/wp/v2/users";
http.header; content:"X-WCPAY-PLATFORM-CHECKOUT-USER";
classtype:attempted-admin;
sid:1000001; rev:1;
)
- msg becomes
suricata: <signature>in the stored reason. - severity determines the score delta; the rule metadata stays human-auditable.
- sid is allocated from the project range and never reused after publication.
OWASP WSTG Category Index
- WSTG-INFO – Information Gathering (SIDs 1000017-1000020, 1000049)
- WSTG-CONF – Configuration Management (SIDs 1000019-1000020)
- WSTG-IDNT – Identity Management (SID 1000018)
- WSTG-ATHN – Authentication Testing (SID 1000017)
- WSTG-AUTHZ – Authorization / Access Control (SIDs 1000001-1000008)
- WSTG-INPV – Input Validation (SIDs 1000011-1000016, 1000026-1000041)
- WSTG-SESS – Session Management (SID 1000017)
SID Allocation Summary
| Range | Purpose |
|---|---|
| 1000001-1000016 | CVE-specific detection rules |
| 1000017-1000025 | General WordPress attack patterns + plugin detection |
| 1000026-1000029 | SQL injection generic patterns (WSTG-INPV-05) |
| 1000030-1000032 | XSS patterns (WSTG-INPV-02) |
| 1000033-1000035 | Command injection (WSTG-INPV-12) |
| 1000036-1000038 | Directory traversal (WSTG-INPV-01) |
| 1000039-1000041 | File inclusion wrappers (WSTG-INPV-11) |
| 1000042-1000048 | Malicious user-agent strings (WSTG-INFO-02) |
| 1000049-1000052 | Scanning and reconnaissance probes |
| 1000053-1000055 | Suspicious file uploads |
| 1000056-1000057 | Rate limiting and DoS detection |
| 1000058-1000060 | Honeypot specific rules |
| 1000061-1000063 | Session management integration |
| 1000064-1000066 | Zero-day and generic attack patterns |
| 1000067-1000068 | API abuse detection |
| 1000069-1000070 | Custom malware and backdoor detection |
Suricata Integration Details
suricata_rules.lua holds the pure, testable pieces of the Suricata alert pipeline. It parses one eve.json line into an alert record (or nil for non-alert event types — flow, netflow, http, dns, tls) and turns an alert into a risk-score delta and human-readable reason string.
| Function | Purpose |
|---|---|
| is_alert_line(line) | Cheap pre-check — substring search for "event_type":"alert" to skip full JSON decode |
| extract_alert(decoded) | Extracts alert record from decoded eve.json. Returns nil for non-alert or malformed events. |
| severity_to_score(severity) | Maps Suricata severity (1=high → +50, 2=medium → +25, default → +10) |
| next_score(prev, severity) | Applies delta to running score, capped at 100. Never decreases on its own. |
| decayed_score(entry, time, half_life, cfg) | Time-decayed view of stored raw_score. Escalation-aware: repeat offenders decay slower; permaflagged never decay. |
| build_reason(alert) | Builds "suricata: " + alert.signature |
| is_private_ip(ip) | Filters RFC1918/loopback so container-internal hops never pollute threat_ips |
init_worker.lua runs the reader every 30 seconds in worker 0 and stores the results as one JSON value under the
threat_ips Redis key, mirrored into the threat_intel shared dict; scores are decayed when
read, using the shared escalation-aware decay. Resume position and
per-cycle limits are on Lua Internals.
Known Caveats
- Same-host (loopback) testing: Suricata runs with network_mode: host and interface "any", so it sees every hop. In same-host test setups, Suricata's src_ip may be a container-internal address that never correlates with nginx's remote_addr. Stage 6 IP reputation gating never fires locally from Suricata alerts. For genuine external traffic, Docker DNAT-based port publishing preserves source IP.
- Private IP filtering: is_private_ip() filters RFC1918/loopback src_ip values at ingestion time.
- fast.log vs eve.json: Previous fast.log regex parser had 8 capture groups into 7 variables — Lua silently drops excess, shifting all fields. Now uses structured eve.json.