Project

General

Profile

Actions

Bug #8940

open
JT OD

Suricata 8.0.6: af-packet cluster_cpu breaks TCP reassembly — HTTP detected by Zeek but missed by Suricata

Bug #8940: Suricata 8.0.6: af-packet cluster_cpu breaks TCP reassembly — HTTP detected by Zeek but missed by Suricata

Added by JIAXIN TAN 3 days ago. Updated 1 day ago.

Status:
Feedback
Priority:
Normal
Assignee:
Target version:
Affected Versions:
Effort:
Difficulty:
Label:
Protocol

Description

  1. 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


  1. 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 `flow` event in `eve.json`.
- Suricata (offline `-r` replay 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.


  1. Environment
  1. 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
  1. af-packet config (`suricata.yaml`)

```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
```

  1. Host NIC

```
eno1np0 : SPAN mirror port (PROMISC, no IP), monitored by both engines
eno3np2 : management NIC, host IP 10.10.64.113/24
```


  1. Evidence
  1. 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)
```

  1. 2. Live Suricata produces zero events for these flows
```bash
  1. 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
  2. → only unrelated DNS (UDP 53) records from OTHER hosts; no http/flow for this TCP stream
    ```
  1. 3. Offline replay of the same pcap matches rules 100%

```bash
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
```


  1. Rules that matched offline (for reference)

```suricata
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;)
```


  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.


  1. Prior incorrect diagnoses (corrected)

Two earlier hypotheses were ruled out during investigation and are documented here for completeness:

1. "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.

2. "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).


  1. Next steps

1. Test `cluster-type: cluster_flow` (flow-hash to keep a flow on one thread) and observe whether `tcp.reassembly_gap` drops and detection recovers — while monitoring `capture.kernel_drops` (a prior 12.5% drop issue on a single hot flow was the reason we switched to `cluster_cpu`, so this is a trade-off to validate).

2. The attached `suri_capture.pcap` reproduces "Zeek sees it, Suricata misses it" deterministically and is available for your analysis.


Files

suri_capture.pcap (10.4 KB) suri_capture.pcap JIAXIN TAN, 08/24/2026 07:29 AM
repro_http_stream.py (4.5 KB) repro_http_stream.py JIAXIN TAN, 08/24/2026 07:30 AM
Suricata漏检复现说明.md (6.94 KB) Suricata漏检复现说明.md JIAXIN TAN, 08/24/2026 07:30 AM
Actions

Also available in: PDF Atom