Skip to content

Commit c960e4f

Browse files
committed
Improve strict dict, remove redkale
1 parent 4653739 commit c960e4f

24 files changed

Lines changed: 335 additions & 865 deletions

.github/workflows/probe.yml

Lines changed: 10 additions & 655 deletions
Large diffs are not rendered by default.

docs/content/docs/request-line/multi-sp-request-line.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ weight: 1
88
|---|---|
99
| **Test ID** | `RFC9112-3-MULTI-SP-REQUEST-LINE` |
1010
| **Category** | Compliance |
11-
| **RFC** | [RFC 9112 Section 3](https://www.rfc-editor.org/rfc/rfc9112#section-3) |
12-
| **Requirement** | SHOULD |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9112 §3](https://www.rfc-editor.org/rfc/rfc9112#section-3) |
12+
| **Requirement** | SHOULD reject, MAY parse leniently |
13+
| **Expected** | `400` or `2xx` |
1414

1515
## What it sends
1616

@@ -29,10 +29,19 @@ Note the double space between `GET` and `/`.
2929

3030
The request-line grammar is `method SP request-target SP HTTP-version CRLF` where `SP` is exactly one space. Multiple spaces do not match this grammar, making the request-line invalid. Recipients SHOULD respond with 400.
3131

32+
However, RFC 9112 §3 also states:
33+
34+
> "Although the request-line grammar rule requires that each of the component elements be separated by a single SP octet, recipients MAY instead parse on whitespace-delimited word boundaries."
35+
36+
This means a server that collapses multiple spaces and processes the request is also RFC-compliant.
37+
38+
**Pass:** Server rejects with `400` (strict, follows SHOULD).
39+
**Warn:** Server accepts and responds `2xx` (RFC-valid per MAY parse leniently).
40+
3241
## Why it matters
3342

3443
Some parsers are lenient and collapse multiple spaces. If a front-end collapses spaces but a back-end does not, they may parse the method, target, or version differently — leading to routing confusion or bypass.
3544

3645
## Sources
3746

38-
- [RFC 9112 Section 3 — Request Line](https://www.rfc-editor.org/rfc/rfc9112#section-3)
47+
- [RFC 9112 §3 — Request Line](https://www.rfc-editor.org/rfc/rfc9112#section-3)

docs/content/docs/smuggling/chunk-ext-lf.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ weight: 25
88
|---|---|
99
| **Test ID** | `SMUG-CHUNK-EXT-LF` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1) |
12-
| **Requirement** | MUST reject |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9112 §7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1) |
12+
| **Expected** | `400` or `2xx` |
1413

1514
## What it sends
1615

@@ -32,13 +31,20 @@ The chunk size line `5;` is terminated with bare LF (`\n`) instead of CRLF.
3231

3332
## What the RFC says
3433

35-
Chunk extensions must follow the grammar `chunk-ext = *( BWS ";" BWS chunk-ext-name [ "=" chunk-ext-val ] )` with CRLF terminating the chunk line. A bare LF in the extension area violates the line terminator requirement of RFC 9112 Section 2.2.
34+
Chunk extensions must follow the grammar `chunk-ext = *( BWS ";" BWS chunk-ext-name [ "=" chunk-ext-val ] )` with CRLF terminating the chunk line. However, RFC 9112 §2.2 states:
35+
36+
> "Although the line terminator for the start-line and fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR."
37+
38+
This means a server MAY accept bare LF — both strict rejection and lenient acceptance are RFC-compliant.
39+
40+
**Pass:** Server rejects with `400` (strict, safe).
41+
**Warn:** Server accepts and responds `2xx` (RFC-valid per §2.2 MAY accept bare LF).
3642

3743
## Why it matters
3844

3945
This is the **TERM.EXT** vector from chunked encoding research. If a parser accepts bare LF in chunk extensions, it may parse chunk boundaries differently from a strict parser, enabling desynchronization and smuggling.
4046

4147
## Sources
4248

43-
- [RFC 9112 Section 7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1)
44-
- [RFC 9112 Section 2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)
49+
- [RFC 9112 §7.1.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1.1)
50+
- [RFC 9112 §2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)

docs/content/docs/smuggling/chunk-lf-term.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ weight: 27
88
|---|---|
99
| **Test ID** | `SMUG-CHUNK-LF-TERM` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1) |
12-
| **Requirement** | MUST reject |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9112 §7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1) |
12+
| **Expected** | `400` or `2xx` |
1413

1514
## What it sends
1615

@@ -32,15 +31,22 @@ The chunk data `hello` is terminated with bare LF (`\n`) instead of CRLF (`\r\n`
3231

3332
## What the RFC says
3433

35-
> "Each chunk ends with CRLF." — RFC 9112 Section 7.1
34+
> "Each chunk ends with CRLF." — RFC 9112 §7.1
3635
37-
The CRLF after chunk data is mandatory. A bare LF violates the chunked transfer coding grammar.
36+
The CRLF after chunk data is mandatory per the grammar. However, RFC 9112 §2.2 states:
37+
38+
> "Although the line terminator for the start-line and fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR."
39+
40+
This means a server MAY accept bare LF as a line terminator — both strict rejection and lenient acceptance are RFC-compliant.
41+
42+
**Pass:** Server rejects with `400` (strict, safe).
43+
**Warn:** Server accepts and responds `2xx` (RFC-valid per §2.2 MAY accept bare LF).
3844

3945
## Why it matters
4046

4147
If one parser accepts bare LF as a chunk data terminator and another requires strict CRLF, they disagree on where the chunk data ends. The byte that the strict parser considers part of chunk data is treated as the next chunk-size line by the lenient parser — a classic desynchronization vector.
4248

4349
## Sources
4450

45-
- [RFC 9112 Section 7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1)
46-
- [RFC 9112 Section 2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)
51+
- [RFC 9112 §7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1)
52+
- [RFC 9112 §2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)

docs/content/docs/smuggling/chunk-lf-trailer.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ weight: 29
88
|---|---|
99
| **Test ID** | `SMUG-CHUNK-LF-TRAILER` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1) |
12-
| **Requirement** | MUST reject |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9112 §7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1) |
12+
| **Expected** | `400` or `2xx` |
1413

1514
## What it sends
1615

@@ -32,15 +31,22 @@ The final trailer terminator uses bare LF (`\n`) instead of CRLF (`\r\n`).
3231

3332
## What the RFC says
3433

35-
> "The trailer section is terminated by an empty line (CRLF)." — RFC 9112 Section 7.1
34+
> "The trailer section is terminated by an empty line (CRLF)." — RFC 9112 §7.1
3635
37-
The chunked message ends with `last-chunk CRLF trailer-section CRLF`. Both CRLF sequences are mandatory.
36+
The chunked message ends with `last-chunk CRLF trailer-section CRLF`. Both CRLF sequences are mandatory per the grammar. However, RFC 9112 §2.2 states:
37+
38+
> "Although the line terminator for the start-line and fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR."
39+
40+
This means a server MAY accept bare LF — both strict rejection and lenient acceptance are RFC-compliant.
41+
42+
**Pass:** Server rejects with `400` (strict, safe).
43+
**Warn:** Server accepts and responds `2xx` (RFC-valid per §2.2 MAY accept bare LF).
3844

3945
## Why it matters
4046

4147
If a front-end parser accepts bare LF as the end of the chunked body but a back-end requires strict CRLF, the back-end may continue waiting for data or interpret subsequent bytes differently. This desync between message boundary detection is a smuggling vector.
4248

4349
## Sources
4450

45-
- [RFC 9112 Section 7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1)
46-
- [RFC 9112 Section 2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)
51+
- [RFC 9112 §7.1](https://www.rfc-editor.org/rfc/rfc9112#section-7.1)
52+
- [RFC 9112 §2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2)

docs/content/docs/smuggling/cl-leading-zeros.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ weight: 3
66

77
| | |
88
|---|---|
9-
| **Test ID** | `RFC9112-6.1-CL-LEADING-ZEROS` |
9+
| **Test ID** | `SMUG-CL-LEADING-ZEROS` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9110 Section 8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) |
12-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9110 §8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) |
12+
| **Expected** | `400` or `2xx` |
1313

1414
## What it sends
1515

16-
Content-Length with leading zeros: `Content-Length: 007`.
16+
Content-Length with leading zeros: `Content-Length: 005`.
1717

1818
```http
1919
POST / HTTP/1.1\r\n
@@ -26,8 +26,15 @@ hello
2626

2727
## What the RFC says
2828

29-
While `007` matches `1*DIGIT`, leading zeros create ambiguity. Some parsers may interpret as octal, some as decimal.
29+
The `Content-Length` grammar is `1*DIGIT`. Since `005` matches `1*DIGIT`, it is technically valid. However, leading zeros create ambiguity — some parsers may interpret the value as octal (base-8), while others treat it as decimal.
30+
31+
## Why it matters
32+
33+
This is a **security vs. strict RFC compliance** tension. The value `005` is grammatically valid, so a server that accepts it and parses it as decimal 5 is not violating the RFC. However, if a front-end and back-end disagree on whether `005` means 5 (decimal) or 5 (octal), they agree by coincidence. For values like `010` (decimal 10 vs. octal 8), disagreement causes body boundary misalignment — a smuggling vector.
34+
35+
**Pass:** Server rejects with `400` (strict, safe).
36+
**Warn:** Server accepts and responds `2xx` (RFC-valid but potentially risky in proxy chains).
3037

3138
## Sources
3239

33-
- [RFC 9110 Section 8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6)
40+
- [RFC 9110 §8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6)

docs/content/docs/smuggling/cl-te-both.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ weight: 1
88
|---|---|
99
| **Test ID** | `RFC9112-6.1-CL-TE-BOTH` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
12-
| **Requirement** | "ought to" |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
12+
| **Requirement** | "ought to" handle as error |
13+
| **Expected** | `400` or `2xx` |
1414

1515
## What it sends
1616

@@ -31,13 +31,20 @@ Transfer-Encoding: chunked\r\n
3131

3232
> "If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling or response splitting and **ought to** be handled as an error."
3333
34-
The "ought to" language is between SHOULD and MAY.
34+
The "ought to" language is between SHOULD and MAY. RFC 9112 §6.3 further clarifies:
35+
36+
> "If a Transfer-Encoding header field is present and the chunked transfer coding is the final encoding, the message body length is determined by reading and decoding the chunked data..."
37+
38+
This means a server **MAY** reject the message or process it using Transfer-Encoding alone — both are RFC-compliant.
39+
40+
**Pass:** Server rejects with `400` (strict, safe).
41+
**Warn:** Server accepts and responds `2xx` (RFC-valid, using TE to determine body length).
3542

3643
## Why it matters
3744

3845
This is **the** classic request smuggling setup. If the front-end uses Content-Length and the back-end uses Transfer-Encoding (or vice versa), they disagree on body boundaries.
3946

4047
## Sources
4148

42-
- [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
43-
- [RFC 9110 Section 16.3.1](https://www.rfc-editor.org/rfc/rfc9110#section-16.3.1)
49+
- [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
50+
- [RFC 9110 §16.3.1](https://www.rfc-editor.org/rfc/rfc9110#section-16.3.1)

docs/content/docs/smuggling/te-leading-comma.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ weight: 23
88
|---|---|
99
| **Test ID** | `SMUG-TE-LEADING-COMMA` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1) |
12-
| **Requirement** | MUST reject |
13-
| **Expected** | `400` or close |
11+
| **RFC** | [RFC 9110 §5.6.1](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.1) |
12+
| **Expected** | `400` or `2xx` |
1413

1514
## What it sends
1615

17-
`Transfer-Encoding: , chunked` — leading comma.
16+
`Transfer-Encoding: , chunked` — leading comma before `chunked`.
1817

1918
```http
2019
POST / HTTP/1.1\r\n
@@ -30,12 +29,18 @@ The Transfer-Encoding value starts with a leading comma before `chunked`.
3029

3130
## What the RFC says
3231

33-
> The list syntax does not allow empty list members (leading comma).
32+
> "A recipient MUST parse and ignore a reasonable number of empty list elements." — RFC 9110 §5.6.1
33+
34+
The leading comma produces an empty list element before `chunked`. Since RFC 9110 §5.6.1 requires recipients to ignore empty list elements, a server that strips the empty element and processes `chunked` normally is RFC-compliant.
35+
36+
**Pass:** Server rejects with `400` (strict, safe).
37+
**Warn:** Server accepts and responds `2xx` (RFC-valid per §5.6.1 empty-element handling).
3438

3539
## Why it matters
3640

37-
Some parsers strip leading commas and see "chunked", while others reject the value. This discrepancy enables smuggling.
41+
Some parsers strip leading commas and see "chunked", while others reject the value entirely. This discrepancy enables smuggling when front-end and back-end parsers disagree on whether Transfer-Encoding is valid.
3842

3943
## Sources
4044

41-
- [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)
45+
- [RFC 9110 §5.6.1 — Lists](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.1)
46+
- [RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)

docs/content/docs/smuggling/te-not-final-chunked.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ weight: 16
88
|---|---|
99
| **Test ID** | `SMUG-TE-NOT-FINAL-CHUNKED` |
1010
| **Category** | Smuggling |
11-
| **RFC** | [RFC 9112 Section 7](https://www.rfc-editor.org/rfc/rfc9112#section-7) |
11+
| **RFC** | [RFC 9112 §6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3) |
1212
| **Requirement** | MUST reject |
1313
| **Expected** | `400` or close |
1414

@@ -28,12 +28,15 @@ Transfer-Encoding: chunked, gzip\r\n
2828

2929
## What the RFC says
3030

31-
> "If any transfer coding other than chunked is applied to a request payload body, the sender MUST apply chunked as the final transfer coding." — RFC 9112 §7
31+
> "If a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the final encoding, the message body length cannot be determined reliably; the server MUST respond with the 400 (Bad Request) status code and then close the connection." — RFC 9112 §6.3
32+
33+
This is MUST-level language — servers have no discretion here.
3234

3335
## Why it matters
3436

35-
If chunked isn't final, the server cannot determine body boundaries. This can be exploited for smuggling.
37+
If chunked isn't the final encoding, the server cannot determine body boundaries. This can be exploited for smuggling.
3638

3739
## Sources
3840

39-
- [RFC 9112 Section 7](https://www.rfc-editor.org/rfc/rfc9112#section-7)
41+
- [RFC 9112 §6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)
42+
- [RFC 9112 §7](https://www.rfc-editor.org/rfc/rfc9112#section-7)

src/Http11Probe.Cli/Reporting/ConsoleReporter.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ public static class ConsoleReporter
77
{
88
public static void Print(TestRunReport report)
99
{
10-
var scored = report.Results.Where(r => r.Verdict is TestVerdict.Pass or TestVerdict.Fail or TestVerdict.Error).ToList();
11-
var warnings = report.Results.Where(r => r.Verdict == TestVerdict.Warn).ToList();
10+
var scored = report.Results.Where(r => r.TestCase.Scored && r.Verdict is TestVerdict.Pass or TestVerdict.Fail or TestVerdict.Error).ToList();
11+
var unscored = report.Results.Where(r => !r.TestCase.Scored || r.Verdict == TestVerdict.Warn).ToList();
1212

1313
Console.WriteLine();
1414
Console.WriteLine(" {0,-35} {1,-10} {2,-6} {3}", "Test ID", "Verdict", "Status", "Details");
@@ -17,15 +17,15 @@ public static void Print(TestRunReport report)
1717
foreach (var result in scored)
1818
PrintRow(result);
1919

20-
if (warnings.Count > 0)
20+
if (unscored.Count > 0)
2121
{
2222
Console.WriteLine();
2323
var prev = Console.ForegroundColor;
2424
Console.ForegroundColor = ConsoleColor.DarkGray;
25-
Console.WriteLine(" ┄┄ Not scored (RFC-compliant behavior) ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
25+
Console.WriteLine(" ┄┄ Not scored ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
2626
Console.ForegroundColor = prev;
2727

28-
foreach (var result in warnings)
28+
foreach (var result in unscored)
2929
PrintRow(result);
3030
}
3131

0 commit comments

Comments
 (0)