Bug #8940 » Suricata漏检复现说明.md
Bug Report: HTTP traffic detected by Zeek but missed by Suricata (af-packet cluster_cpu breaks TCP stream reassembly)
- Project: https://github.com/monstertsl/SenseMind
- Related forum thread: https://forum.suricata.io/t/http-traffic-detected-by-zeek-but-missed-by-suricata-on-incomplete-tcp-streams/6441?u=monstertsl
- Suricata version: 8.0.6 RELEASE
- Mode: IDS (passive), af-packet capture
-
Attachment:
poc/suri_capture.pcap(captured inside the Suricata container, on the same interface Suricata monitors) - Reproduced: 2026-08-24
Summary
A scanner on host 10.10.168.224 sends normal, well-formed HTTP requests (complete 3-way handshake, valid sequence numbers, server returns 404) to 10.10.168.197:80.
-
Zeek (libpcap, same interface
eno1np0) logs all HTTP transactions correctly. -
Suricata (live) produces zero events for these flows — not even a
flowevent ineve.json. -
Suricata (offline
-rreplay of the same pcap) matches the rules 100%.
The traffic does reach the monitored interface; the problem is in Suricata's live capture → stream pipeline.
Environment
Deployment (Docker Compose, host network)
Both engines listen on the same physical interface eno1np0 (SPAN mirror port):
| Suricata | Zeek | |
|---|---|---|
| Image |
jasonish/suricata:latest (8.0.6) |
zeek/zeek:latest (8.2.0) |
| Network | host |
host |
| CapAdd | CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_NICE |
CAP_NET_ADMIN CAP_NET_RAW |
| Interface | eno1np0 |
eno1np0 |
| Cmd | -i eno1np0 |
zeek -i eno1np0 ... |
| Capture | af-packet | libpcap |
af-packet config (suricata.yaml)
af-packet:
- interface: default # wildcard; real iface passed via -i eno1np0
threads: 16
cluster-id: 99
cluster-type: cluster_cpu # <-- load-balances by CPU, NOT by flow
ring-size: 65536
Host NIC
eno1np0 : SPAN mirror port (PROMISC, no IP), monitored by both engines
eno3np2 : management NIC, host IP 10.10.64.113/24
Evidence
1. Two capture points see identical packets (traffic DOES reach the mirror port)
While the attack was running, we captured eno1np0 at two points simultaneously (host 10.10.168.197 and tcp port 80):
| Capture point | File | MD5 |
|---|---|---|
Inside Suricata container (tcpdump -i eno1np0) |
suri_capture.pcap |
819fdd3f37445b93bd0e054593878078 |
Host (tcpdump -i eno1np0) |
host_capture.pcap |
819fdd3f37445b93bd0e054593878078 |
Both pcaps are byte-identical. The captured flows are complete and well-formed:
10.10.168.224.57707 > 10.10.168.197.80 GET /.git/index HTTP/1.1
10.10.168.197.80 > 10.10.168.224.57707 HTTP/1.1 404 Not Found (720 bytes)
10.10.168.224.57709 > 10.10.168.197.80 GET /WEB-INF/web.xml HTTP/1.1
10.10.168.197.80 > 10.10.168.224.57709 HTTP/1.1 404 Not Found (720 bytes)
10.10.168.224.57710 > 10.10.168.197.80 GET /druid/index.html HTTP/1.1
10.10.168.197.80 > 10.10.168.224.57710 HTTP/1.1 404 Not Found (720 bytes)
... (6 attack URIs total, all tagged vlan 168)
2. Live Suricata produces zero events for these flows
# inside the suricata container, search eve.json for the attack source ports
grep -E "57707|57708|57709|57710|57711|57712" /var/log/suricata/eve.json
# → only unrelated DNS (UDP 53) records from OTHER hosts; no http/flow for this TCP stream
3. Offline replay of the same pcap matches rules 100%
suricata -r suri_capture.pcap -c test.yaml -k none -l out
08/24/2026-07:00:15.596530 [1:90015:1] file read:/.git/ Git 仓库源码泄露 10.10.168.224:57707 -> 10.10.168.197:80
08/24/2026-07:00:15.596530 [1:91025:1] source leak:GIT 源码目录泄露探测 10.10.168.224:57707 -> 10.10.168.197:80
08/24/2026-07:00:16.176527 [1:91272:1] auth bypass:Druid 未授权访问漏洞 10.10.168.224:57712 -> 10.10.168.197:80
08/24/2026-07:00:16.176527 [1:91273:1] auth bypass:若依 未授权访问漏洞 10.10.168.224:57712 -> 10.10.168.197:80
Rules that matched offline (for reference)
alert http any any -> any any (msg:"file read:/.git/ Git 仓库源码泄露"; flow:established,to_server; http.uri; content:"/.git/"; fast_pattern; nocase; classtype:web-application-attack; sid:90015; rev:1;)
alert http any any -> any any (msg:"source leak:GIT 源码目录泄露探测"; flow:established,to_server; http.uri; content:"/.git/"; fast_pattern; classtype:attempted-recon; sid:91025; rev:1;)
alert http any any -> any any (msg:"auth bypass:Druid 未授权访问漏洞"; flow:established,to_server; http.uri; content:"/druid/index.html"; nocase; fast_pattern; classtype:web-application-attack; sid:91272; rev:1;)
alert http any any -> any any (msg:"auth bypass:若依管理系统 未授权访问漏洞"; flow:established,to_server; http.uri; content:"/prod-api/druid/index.html"; nocase; fast_pattern; classtype:web-application-attack; sid:91273; rev:1;)
Root cause hypothesis
stats.log shows massive stream-reassembly failures that correlate with the cluster_cpu load-balancing:
tcp.pkt_on_wrong_thread = 1,478,842,804 (packets delivered to the wrong thread)
tcp.reassembly_gap = 369,926,805 (reassembly gaps)
tcp.insert_data_normal_fail = 369,702,088 (insert failures)
With cluster-type: cluster_cpu (16 threads), packets of the same TCP flow get hashed to different CPU threads, producing reassembly gaps and incomplete HTTP streams. libhtp then cannot parse the request URI, so http.uri rules never match. Offline -r replay runs single-threaded and is unaffected — hence the 100% match.
This is the accurate meaning of the forum thread title "HTTP traffic detected by Zeek but missed by Suricata on incomplete TCP streams": it is not that the scanner sent incomplete streams, but that Suricata's thread fan-out broke complete streams apart.
Prior incorrect diagnoses (corrected)
Two earlier hypotheses were ruled out during investigation and are documented here for completeness:
-
"Scanners send half-open / seq-broken HTTP data" — Incorrect. ES logs (source
1.13.9.36) + Zeek offline analysis confirmed real scanner attack requests are complete, well-formed HTTP (GET + 404). Half-open probes (history:ShAR) are data-less port-liveness checks (nmap-sS), not attack requests. -
"Same-VLAN L2 traffic bypasses the SPAN mirror port" — Incorrect. The dual-capture experiment above proves the traffic does reach
eno1np0(byte-identical pcaps at two capture points).
Next steps
-
Test
cluster-type: cluster_flow(flow-hash to keep a flow on one thread) and observe whethertcp.reassembly_gapdrops and detection recovers — while monitoringcapture.kernel_drops(a prior 12.5% drop issue on a single hot flow was the reason we switched tocluster_cpu, so this is a trade-off to validate). -
The attached
suri_capture.pcapreproduces "Zeek sees it, Suricata misses it" deterministically and is available for your analysis.
- « Previous
- 1
- 2
- 3
- Next »