Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Reference documentation for every test in Http11Probe, organized by topic. Each

{{< cards >}}
{{< card link="rfc-basics" title="RFC Basics" subtitle="What RFCs are, how to read requirement levels (MUST/SHOULD/MAY), and which RFCs define HTTP/1.1." icon="book-open" >}}
{{< card link="baseline" title="Baseline" subtitle="Sanity request used to confirm the target is reachable before running negative tests." icon="check-circle" >}}
{{< card link="line-endings" title="Line Endings" subtitle="CRLF requirements, bare LF handling, and bare CR rejection per RFC 9112 Section 2.2." icon="code" >}}
{{< card link="request-line" title="Request Line" subtitle="Request-line format, multiple spaces, missing target, fragments, HTTP version validation." icon="terminal" >}}
{{< card link="headers" title="Header Syntax" subtitle="Obs-fold, space before colon, empty names, invalid characters, missing colon." icon="document-text" >}}
Expand Down
25 changes: 25 additions & 0 deletions docs/content/docs/baseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: "BASELINE"
description: "BASELINE test documentation"
weight: 2
---

| | |
|---|---|
| **Test ID** | `COMP-BASELINE` |
| **Category** | Compliance |
| **Expected** | `2xx` |

## What it sends

A well-formed minimal HTTP/1.1 GET request.

```http
GET / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## Why it matters

This is the sanity check for reachability and parser baseline. If this fails, later negative tests are not meaningful.
8 changes: 5 additions & 3 deletions docs/content/docs/body/chunked-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 10
| **Category** | Compliance |
| **RFC** | [RFC 9112 Section 7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1) |
| **Requirement** | MUST ignore unrecognized extensions |
| **Expected** | `2xx` or `400` |
| **Expected** | `2xx` = Pass, `400` = Warn |

## What it sends

Expand Down Expand Up @@ -75,9 +75,11 @@ chunk-ext-val = token / quoted-string
4. However, the RFC also says servers "ought to limit the total length of chunk extensions" and may generate a 4xx response if limits are exceeded. This introduces a legitimate reason for a `400` response.
5. The extension in this test (`ext=value`) is short (9 bytes), so a length-limit rejection would be unreasonable. But the RFC permits it in principle.

### Scored / Unscored justification
### Scoring justification

**Unscored.** The MUST keyword applies to *ignoring unrecognized* extensions, which implies the server should parse and skip them. However, the RFC also explicitly permits servers to reject requests with excessive chunk extensions via a 4xx response. Because the boundary between "acceptable" and "excessive" is left to the server's discretion, there is room for a compliant server to reject even short extensions. The test uses SHOULD accept (`2xx` = Pass, `400` = Warn) to acknowledge that `2xx` is the preferred behavior while `400` is not a clear violation.
This test is **scored** because the payload uses a short, syntactically valid chunk extension. For this input, RFC 9112 §7.1.1 says recipients MUST ignore unrecognized extensions and continue processing.
`2xx` is Pass.
`400` is Warn (strict behavior seen in the wild, but not the preferred RFC behavior for this specific payload).

### Edge cases

Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/request-line/absolute-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 9
| **Category** | Compliance |
| **RFC** | [RFC 9112 Section 3.2.2](https://www.rfc-editor.org/rfc/rfc9112#section-3.2.2) |
| **Requirement** | MUST accept (server) |
| **Expected** | `400` or `2xx` |
| **Expected** | `2xx` = Pass, `400` = Warn (unscored) |

## What it sends

Expand All @@ -35,8 +35,8 @@ Although the RFC says servers MUST accept absolute-form, in practice most non-pr

## Why it matters

**Pass:** Server rejects with `400` (common origin-server behavior).
**Warn:** Server accepts with `2xx` (RFC-compliant, accepts absolute-form).
**Pass:** Server accepts with `2xx` (RFC-compliant).
**Warn:** Server rejects with `400` (common in practice, but non-compliant with MUST accept).

## Deep Analysis

Expand Down Expand Up @@ -74,7 +74,7 @@ The `absolute-form` production requires a complete `absolute-URI` as defined in

### Scoring Justification

**Unscored.** Although the RFC uses "MUST accept," this requirement primarily targets proxy servers. An origin server that rejects absolute-form (returning `400`) is technically non-compliant but is not creating a security vulnerability -- it is simply refusing a request format it was not designed to handle. Both `400` and `2xx` are treated as acceptable outcomes.
**Unscored.** RFC 9112 uses a server-side MUST to accept absolute-form. In practice, many origin stacks still reject it. To preserve interoperability visibility without hard-failing broad classes of servers, this test is unscored: `2xx` is Pass and `400` is Warn.

### Edge Cases

Expand Down
17 changes: 16 additions & 1 deletion docs/content/docs/smuggling/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,28 @@ For these, `400` is the strict/safe response and `2xx` is RFC-compliant. Http11P
{{< card link="chunk-spill" title="CHUNK-SPILL" subtitle="Chunk declares size 5 but sends 7 bytes." >}}
{{< card link="chunk-lf-term" title="CHUNK-LF-TERM" subtitle="Bare LF as chunk data terminator." >}}
{{< card link="chunk-ext-ctrl" title="CHUNK-EXT-CTRL" subtitle="NUL byte in chunk extension." >}}
{{< card link="chunk-ext-cr" title="CHUNK-EXT-CR" subtitle="Bare CR inside chunk extension metadata." >}}
{{< card link="chunk-lf-trailer" title="CHUNK-LF-TRAILER" subtitle="Bare LF in trailer section termination." >}}
{{< card link="te-identity" title="TE-IDENTITY" subtitle="Transfer-Encoding: identity (deprecated) with CL." >}}
{{< card link="te-vtab" title="TE-VTAB" subtitle="Vertical tab before chunked token." >}}
{{< card link="te-formfeed" title="TE-FORMFEED" subtitle="Form-feed before chunked token." >}}
{{< card link="te-null" title="TE-NULL" subtitle="NUL byte appended to chunked token." >}}
{{< card link="chunk-negative" title="CHUNK-NEGATIVE" subtitle="Negative chunk size (-1)." >}}
{{< card link="chunk-bare-cr-term" title="CHUNK-BARE-CR-TERM" subtitle="Bare CR as chunk size line terminator." >}}
{{< card link="cl-underscore" title="CL-UNDERSCORE" subtitle="Content-Length with underscore digit separator (1_0)." >}}
{{< card link="cl-negative-zero" title="CL-NEGATIVE-ZERO" subtitle="Content-Length: -0 — not valid 1*DIGIT." >}}
{{< card link="cl-double-zero" title="CL-DOUBLE-ZERO" subtitle="Content-Length: 00 — leading zero ambiguity." >}}
{{< card link="cl-leading-zeros-octal" title="CL-LEADING-ZEROS-OCTAL" subtitle="Content-Length: 0200 — octal vs decimal disagreement." >}}
{{< card link="te-obs-fold" title="TE-OBS-FOLD" subtitle="Transfer-Encoding with obs-fold line wrapping." >}}
{{< card link="te-trailing-comma" title="TE-TRAILING-COMMA" subtitle="Transfer-Encoding: chunked, — trailing comma." >}}
{{< card link="multiple-host-comma" title="MULTIPLE-HOST-COMMA" subtitle="Host with comma-separated values." >}}
{{< /cards >}}

### Unscored

{{< cards >}}
{{< card link="cl-trailing-space" title="CL-TRAILING-SPACE" subtitle="Trailing space in CL value. OWS trimming is valid." >}}
{{< card link="cl-extra-leading-sp" title="CL-EXTRA-LEADING-SP" subtitle="Extra space after CL colon. OWS is valid." >}}
{{< card link="header-injection" title="HEADER-INJECTION" subtitle="CRLF injection in header value." >}}
{{< card link="te-double-chunked" title="TE-DOUBLE-CHUNKED" subtitle="Duplicate 'chunked' TE with CL." >}}
{{< card link="te-case-mismatch" title="TE-CASE-MISMATCH" subtitle="'Chunked' vs 'chunked'. Case is valid per RFC." >}}
{{< card link="transfer-encoding-underscore" title="TRANSFER_ENCODING" subtitle="Underscore instead of hyphen in header name." >}}
Expand All @@ -107,6 +118,10 @@ For these, `400` is the strict/safe response and `2xx` is RFC-compliant. Http11P
{{< card link="trailer-cl" title="TRAILER-CL" subtitle="Content-Length in chunked trailers (prohibited)." >}}
{{< card link="trailer-te" title="TRAILER-TE" subtitle="Transfer-Encoding in chunked trailers (prohibited)." >}}
{{< card link="trailer-host" title="TRAILER-HOST" subtitle="Host header in chunked trailers (must not route)." >}}
{{< card link="trailer-auth" title="TRAILER-AUTH" subtitle="Authorization in chunked trailers (prohibited)." >}}
{{< card link="trailer-content-type" title="TRAILER-CONTENT-TYPE" subtitle="Content-Type in chunked trailers (prohibited)." >}}
{{< card link="head-cl-body" title="HEAD-CL-BODY" subtitle="HEAD with Content-Length and body." >}}
{{< card link="options-cl-body" title="OPTIONS-CL-BODY" subtitle="OPTIONS with Content-Length and body." >}}
{{< card link="te-tab-before-value" title="TE-TAB-BEFORE-VALUE" subtitle="Tab as OWS before Transfer-Encoding value." >}}
{{< card link="absolute-uri-host-mismatch" title="ABSOLUTE-URI-HOST-MISMATCH" subtitle="Absolute-form URI with different Host header." >}}
{{< /cards >}}
37 changes: 37 additions & 0 deletions docs/content/docs/smuggling/chunk-ext-cr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: "CHUNK-EXT-CR"
description: "CHUNK-EXT-CR test documentation"
weight: 51
---

| | |
|---|---|
| **Test ID** | `SMUG-CHUNK-EXT-CR` |
| **Category** | Smuggling |
| **RFC** | [RFC 9112 §7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1), [RFC 9112 §2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2) |
| **Requirement** | MUST reject malformed chunk line |
| **Expected** | `400` or close |

## What it sends

A chunk-size line where a bare CR appears inside the extension area, not as a valid `CRLF` terminator.

```http
POST / HTTP/1.1\r\n
Host: localhost:8080\r\n
Transfer-Encoding: chunked\r\n
\r\n
5;a\rX\r\n
hello\r\n
0\r\n
\r\n
```

## Why it matters

Differential handling of bare CR in framing metadata can produce parser disagreement across hops and create desync risk.

## Sources

- [RFC 9112 §2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)
- [RFC 9112 §7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1)
35 changes: 35 additions & 0 deletions docs/content/docs/smuggling/te-formfeed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "TE-FORMFEED"
description: "TE-FORMFEED test documentation"
weight: 53
---

| | |
|---|---|
| **Test ID** | `SMUG-TE-FORMFEED` |
| **Category** | Smuggling |
| **RFC** | [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5), [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject invalid transfer-coding token |
| **Expected** | `400` or close |

## What it sends

`Transfer-Encoding: <FF>chunked` with `Content-Length` present.

```http
POST / HTTP/1.1\r\n
Host: localhost:8080\r\n
Transfer-Encoding: \x0cchunked\r\n
Content-Length: 5\r\n
\r\n
hello
```

## Why it matters

Form-feed control characters in TE values are an obfuscation vector that can trigger parser disagreement in proxy chains.

## Sources

- [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5)
- [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
4 changes: 2 additions & 2 deletions docs/content/docs/smuggling/te-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 30
| **Category** | Smuggling |
| **RFC** | [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject |
| **Expected** | `400` or close |
| **Expected** | `400`/`501` or close |

## What it sends

Expand Down Expand Up @@ -76,7 +76,7 @@ The token `identity` is syntactically valid per the ABNF (it consists entirely o

This test is **scored** (MUST reject). Although the SHOULD in RFC 9112 section 6.1 for unrecognized transfer codings is not a MUST, the combined presence of Transfer-Encoding and Content-Length triggers the MUST-level requirement in section 6.1 to close the connection. The server cannot safely process `Transfer-Encoding: identity` because it is not a recognized coding, and the dual-header scenario mandates connection closure at minimum.

- **Pass (400 or close):** The server correctly rejects the unknown transfer coding or closes the connection per the dual-header rule.
- **Pass (400/501 or close):** The server rejects the unknown transfer coding or closes the connection per the dual-header rule.
- **Fail (2xx):** The server accepted a request with an unrecognized transfer coding and conflicting Content-Length, violating the connection-closure requirement.

### Smuggling Attack Scenarios
Expand Down
35 changes: 35 additions & 0 deletions docs/content/docs/smuggling/te-null.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "TE-NULL"
description: "TE-NULL test documentation"
weight: 54
---

| | |
|---|---|
| **Test ID** | `SMUG-TE-NULL` |
| **Category** | Smuggling |
| **RFC** | [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5), [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject malformed field value |
| **Expected** | `400` or close |

## What it sends

`Transfer-Encoding: chunked<NUL>` with `Content-Length` present.

```http
POST / HTTP/1.1\r\n
Host: localhost:8080\r\n
Transfer-Encoding: chunked\x00\r\n
Content-Length: 5\r\n
\r\n
hello
```

## Why it matters

NUL handling differences (truncate vs reject) are a classic parser differential that can destabilize message framing.

## Sources

- [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5)
- [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
13 changes: 7 additions & 6 deletions docs/content/docs/smuggling/te-obs-fold.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ weight: 50
| **Test ID** | `SMUG-TE-OBS-FOLD` |
| **Category** | Smuggling |
| **RFC** | [RFC 9112 §5.2](https://www.rfc-editor.org/rfc/rfc9112#section-5.2) |
| **Requirement** | MUST |
| **Expected** | `400` |
| **Requirement** | MUST reject or unfold obs-fold |
| **Expected** | `400`, or `2xx` with connection close |

## What it sends

Expand All @@ -37,7 +37,7 @@ When obs-fold is used on the Transfer-Encoding header with Content-Length also p

## Why it matters

This is a high-confidence smuggling vector. The obs-fold mechanism was deprecated precisely because of parser disagreements. When applied to Transfer-Encoding — the header that determines message framing — it creates a situation where one parser uses chunked encoding and another uses Content-Length, enabling request smuggling. The RFC requires rejection (MUST), and no `AllowConnectionClose` alternative is acceptable because the server must actively reject the malformed header rather than simply closing the connection.
This is a high-confidence smuggling vector. The obs-fold mechanism was deprecated precisely because of parser disagreements. When applied to Transfer-Encoding, one parser can unfold to `chunked` while another ignores it and falls back to Content-Length.

## Deep Analysis

Expand Down Expand Up @@ -71,10 +71,11 @@ The `obs-fold` rule (obsolete line folding) allows a field value to be continued

### Scored / Unscored Justification

This test is **scored** (MUST reject with `400`). RFC 9112 section 5.2 provides a MUST-level requirement for servers receiving obs-fold. While the RFC allows two options (reject or unfold), this test expects strict `400` rejection because the obs-fold is applied to the Transfer-Encoding header -- the header that determines message framing. Allowing an unfolded interpretation when Content-Length is also present would require the server to then handle the CL/TE dual-header scenario, adding further complexity and risk. No `AllowConnectionClose` alternative is acceptable because the server must actively reject the malformed header.
This test is **scored**. RFC 9112 §5.2 gives two compliant server behaviors: reject with `400`, or replace obs-fold with SP and continue. If unfolded, the message still carries both TE and CL, so RFC 9112 §6.1 requires closing the connection after responding.

- **Pass (400):** The server correctly rejects the obs-fold per the MUST requirement.
- **Fail (2xx or close):** The server either silently accepted the folded header or merely closed the connection without the required `400` response.
- **Pass:** `400`.
- **Warn:** `2xx` with connection close (accepted unfold path).
- **Fail:** `2xx` without connection close.

### Smuggling Attack Scenarios

Expand Down
11 changes: 6 additions & 5 deletions docs/content/docs/smuggling/te-trailing-space.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ weight: 6
| **Test ID** | `SMUG-TE-TRAILING-SPACE` |
| **Category** | Smuggling |
| **RFC** | [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5), [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject |
| **Expected** | `400` or close |
| **Requirement** | MAY reject or process with TE; MUST close connection if CL+TE is processed |
| **Expected** | `400`/`501`, or `2xx` with connection close |

## What it sends

Expand Down Expand Up @@ -77,10 +77,11 @@ The `field-line` rule includes trailing `OWS` after `field-value`. Per RFC 9112

### Scored / Unscored Justification

This test is **scored** (MUST reject). The combined presence of Transfer-Encoding and Content-Length triggers the MUST-level connection-closure requirement in RFC 9112 section 6.1. While RFC 9110 section 5.5 requires stripping trailing whitespace (which would make the value `chunked`), the trailing space creates a practical ambiguity that many parsers handle inconsistently. The server MUST at minimum close the connection after responding to a request with both TE and CL.
This test is **scored**. RFC 9110 §5.5 requires trimming trailing OWS before field-value evaluation, so `chunked ` can become `chunked`. RFC 9112 §6.1 then applies the CL+TE rule: reject, or process with TE and close the connection.

- **Pass (400 or close):** The server correctly rejects the request or closes the connection per the dual-header rules.
- **Fail (2xx):** The server processed the request without closing the connection, violating the MUST requirement in section 6.1.
- **Pass:** `400`/`501` (strict rejection path).
- **Warn:** `2xx` with connection close (lenient parse path, still RFC-safe on connection handling).
- **Fail:** `2xx` without connection close.

### Smuggling Attack Scenarios

Expand Down
35 changes: 35 additions & 0 deletions docs/content/docs/smuggling/te-vtab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "TE-VTAB"
description: "TE-VTAB test documentation"
weight: 52
---

| | |
|---|---|
| **Test ID** | `SMUG-TE-VTAB` |
| **Category** | Smuggling |
| **RFC** | [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5), [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject invalid transfer-coding token |
| **Expected** | `400` or close |

## What it sends

`Transfer-Encoding: <VTAB>chunked` with `Content-Length` present.

```http
POST / HTTP/1.1\r\n
Host: localhost:8080\r\n
Transfer-Encoding: \x0bchunked\r\n
Content-Length: 5\r\n
\r\n
hello
```

## Why it matters

Control-character obfuscation is a known TE parsing differential. One hop can reject while another normalizes and parses differently.

## Sources

- [RFC 9110 §5.5](https://www.rfc-editor.org/rfc/rfc9110#section-5.5)
- [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
4 changes: 2 additions & 2 deletions docs/content/docs/smuggling/te-xchunked.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 5
| **Category** | Smuggling |
| **RFC** | [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
| **Requirement** | MUST reject |
| **Expected** | `400` or close |
| **Expected** | `400`/`501` or close |

## What it sends

Expand Down Expand Up @@ -74,7 +74,7 @@ The token `xchunked` is syntactically valid per the ABNF -- it consists entirely

This test is **scored** (MUST reject). The MUST-level connection-closure requirement in RFC 9112 section 6.1 applies to all requests containing both Transfer-Encoding and Content-Length. Additionally, `xchunked` is not a recognized transfer coding, so the SHOULD-level guidance to respond with `501` reinforces rejection. The server cannot safely process a body framed with an unknown coding.

- **Pass (400 or close):** The server correctly rejects the unknown transfer coding or closes the connection per the dual-header rule.
- **Pass (400/501 or close):** The server rejects the unknown transfer coding or closes the connection per the dual-header rule.
- **Fail (2xx):** The server accepted a request with an unrecognized transfer coding and conflicting Content-Length, violating the connection-closure requirement.

### Smuggling Attack Scenarios
Expand Down
Loading
Loading