Project

General

Profile

Actions

Security #8413

open

websocket: injected control frames can lead to evasion

Added by Philippe Antoine 6 days ago. Updated about 17 hours ago.

Status:
Resolved
Priority:
Normal
Target version:
Affected Versions:
Label:
CVE:
Git IDs:
Severity:
LOW
Disclosure Date:

Description

RFC 6455 Section 5.4

Control frames (see Section 5.5) MAY be injected in the middle of a fragmented message.

Submitted report:

[Summary]
The WebSocket parser's reassembly logic (`rust/src/websocket/websocket.rs`, lines 222-241) does not distinguish control frames (Ping, Pong, Close) from data frames (Text, Binary, Continuation). Per RFC 6455 Section 5.4, control frames may be interleaved between data frame fragments but must not participate in data reassembly. When a control frame with `fin=1` arrives while a fragmented data message is being reassembled, it triggers premature reassembly completion at line 237, corrupting the reassembled payload and causing detection rules matching on `websocket.payload` to fail.

[Root Cause]
The WebSocket parser maintains per-direction reassembly buffers (`c2s_buf`, `s2c_buf`). When a non-final data frame arrives (`fin=0`), its payload is appended to the buffer. When a frame with `fin=1` arrives and the buffer is non-empty, the buffer contents are swapped into the transaction's payload as the complete reassembled message.

The problem is at lines 222-241:

```rust
// rust/src/websocket/websocket.rs:223
if !buf.data.is_empty() || !pdu.fin {
// ... append pdu.payload to buf.data (line 228)
}
tx.pdu = pdu;
// Line 237: triggers on ANY frame with fin=1 when buffer has data
if tx.pdu.fin && !buf.data.is_empty() {
compress = buf.compress;
std::mem::swap(&mut tx.pdu.payload, &mut buf.data);
buf.data.clear();
}
```

There is no check for `pdu.opcode`. Control frames (opcode >= 0x8: Ping=0x9, Pong=0xA, Close=0x8) enter the same code path as data frames. When a Ping with `fin=1` arrives during an active data reassembly:

1. Line 223: `!buf.data.is_empty()` is true (buffer has partial data from previous fragment), so the Ping's payload is appended to the buffer.
2. Line 237: `tx.pdu.fin` (Ping has fin=1) AND `!buf.data.is_empty()` — the condition is met. The buffer (now containing partial data + Ping payload) is swapped into the transaction as a "complete" reassembled message.
3. `buf.data.clear()` resets the buffer. Any subsequent Continuation frame starts fresh instead of continuing the original data message.

[PoC]
Please extract the attached compressed file and proceed.

1. docker build -t poc .
2. docker run --rm poc

[Impact]
- Detection rules using `websocket.payload` can be evaded by fragmenting the malicious content and interleaving a single Ping frame. This is a trivial attack requiring only 3 WebSocket frames.
- The interleaved Ping frame is fully RFC-compliant behavior. Legitimate WebSocket implementations may send keepalive Pings between data fragments, meaning this bug also causes false negatives on normal traffic.
- WebSocket is enabled by default in Suricata (auto-detected after HTTP upgrade).

[Affected Version]
  • Current `main` branch (commit bc4a055e7, Suricata 9.0.0-dev) is confirmed affected.

Files


Subtasks 1 (1 open0 closed)

Bug #8414: websocket: reassembly should restrict opcodes (8.0.x backport)In ReviewPhilippe AntoineActions
Actions #1

Updated by OISF Ticketbot 6 days ago

  • Subtask #8414 added
Actions #2

Updated by OISF Ticketbot 6 days ago

  • Label deleted (Needs backport to 8.0)
Actions #3

Updated by Philippe Antoine 6 days ago

  • Status changed from Assigned to In Review

Gitlab MR

Actions #4

Updated by Philippe Antoine 6 days ago

I think this can be made public

Actions #6

Updated by Victor Julien 5 days ago

  • Tracker changed from Bug to Security
  • Subject changed from websocket: reassembly should restrict opcodes to websocket: injected control frames can lead to evasion
  • Private changed from Yes to No
  • Severity set to LOW

Severity LOW because the keywords are not commonly used, would require knowledge of the rules to evade.

Since this is marked LOW, we will make the ticket and fix public.

Actions #8

Updated by Philippe Antoine about 17 hours ago

  • Status changed from In Review to Resolved
Actions

Also available in: Atom PDF