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
8 changes: 4 additions & 4 deletions docs/content/docs/line-endings/bare-lf-header.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 2
| **Category** | Compliance |
| **RFC** | [RFC 9112 Section 2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2) |
| **Requirement** | MAY |
| **Expected** | `400` or close |
| **Expected** | `400` or close (pass), `2xx` (warn) |

## What it sends

Expand Down Expand Up @@ -93,12 +93,12 @@ This directly supports why bare LF in headers is a security concern: if the fron

### Scored / Unscored justification

This test is **scored (Pass/Fail)**. Although the RFC requirement level is MAY, Http11Probe enforces strict rejection:
This test is **scored (Pass/Warn)**:

- **Pass** for `400` or connection close --- strict rejection prevents header boundary disagreements between hops.
- **Fail** for `2xx` --- the server accepted bare LF in a header, which introduces a smuggling vector in multi-hop architectures.
- **Warn** for `2xx` --- the server accepted bare LF in a header. This is permitted by the RFC (MAY) but introduces a smuggling vector in multi-hop architectures.

The scoring reflects Http11Probe's security-first philosophy: when the RFC gives discretion (MAY), the tool rewards the stricter posture. Header-level bare LF is arguably more dangerous than request-line bare LF because header boundaries directly control how Content-Length, Transfer-Encoding, and Host are parsed --- all of which are smuggling-critical fields.
The strict posture is rewarded because header-level bare LF is particularly sensitive --- header boundaries directly control how Content-Length, Transfer-Encoding, and Host are parsed, all of which are smuggling-critical fields.

## Sources

Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/line-endings/bare-lf-request-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ weight: 1
| **Category** | Compliance |
| **RFC** | [RFC 9112 Section 2.2](https://www.rfc-editor.org/rfc/rfc9112#section-2.2) |
| **Requirement** | MAY |
| **Expected** | `400` or close |
| **Expected** | `400` or close (pass), `2xx` (warn) |

## What it sends

Expand Down Expand Up @@ -94,12 +94,12 @@ This explains the security motivation for rejecting bare LF: when a front-end pr

### Scored / Unscored justification

This test is **scored (Pass/Fail)**. Although the RFC requirement level is MAY, Http11Probe enforces strict rejection:
This test is **scored (Pass/Warn)**:

- **Pass** for `400` or connection close --- strict rejection eliminates parser-differential attacks.
- **Fail** for `2xx` --- the server accepted bare LF as a line terminator, which introduces a smuggling vector in multi-hop architectures.
- **Warn** for `2xx` --- the server accepted bare LF as a line terminator. This is permitted by the RFC (MAY) but introduces a smuggling vector in multi-hop architectures.

The scoring reflects Http11Probe's security-first philosophy: when the RFC gives discretion (MAY), the tool rewards the stricter posture because bare LF acceptance is a well-known source of parser disagreements that enable request smuggling.
The strict posture is rewarded because bare LF acceptance is a well-known source of parser disagreements that enable request smuggling.

## Sources

Expand Down
26 changes: 20 additions & 6 deletions src/Http11Probe/TestCases/Suites/ComplianceSuite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,42 @@ public static IEnumerable<TestCase> GetTestCases()
yield return new TestCase
{
Id = "RFC9112-2.2-BARE-LF-REQUEST-LINE",
Description = "Bare LF in request line must be rejected",
Description = "Bare LF in request line should be rejected, but MAY be accepted",
Category = TestCategory.Compliance,
RfcReference = "RFC 9112 §2.2",
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\nHost: {ctx.HostHeader}\r\n\r\n"),
Expected = new ExpectedBehavior
{
ExpectedStatus = StatusCodeRange.Exact(400),
AllowConnectionClose = true
Description = "400 or close (pass), 2xx (warn)",
CustomValidator = (response, state) =>
{
if (response is null)
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
if (response.StatusCode == 400) return TestVerdict.Pass;
if (response.StatusCode is >= 200 and < 300) return TestVerdict.Warn;
return TestVerdict.Fail;
}
}
};

yield return new TestCase
{
Id = "RFC9112-2.2-BARE-LF-HEADER",
Description = "Bare LF in header must be rejected",
Description = "Bare LF in header should be rejected, but MAY be accepted",
Category = TestCategory.Compliance,
RfcReference = "RFC 9112 §2.2",
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\nX-Test: value\r\n\r\n"),
Expected = new ExpectedBehavior
{
ExpectedStatus = StatusCodeRange.Exact(400),
AllowConnectionClose = true
Description = "400 or close (pass), 2xx (warn)",
CustomValidator = (response, state) =>
{
if (response is null)
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
if (response.StatusCode == 400) return TestVerdict.Pass;
if (response.StatusCode is >= 200 and < 300) return TestVerdict.Warn;
return TestVerdict.Fail;
}
}
};

Expand Down
Loading