diff --git a/docs/content/docs/line-endings/bare-lf-header.md b/docs/content/docs/line-endings/bare-lf-header.md index b3cdc2d..04fafc7 100644 --- a/docs/content/docs/line-endings/bare-lf-header.md +++ b/docs/content/docs/line-endings/bare-lf-header.md @@ -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 @@ -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 diff --git a/docs/content/docs/line-endings/bare-lf-request-line.md b/docs/content/docs/line-endings/bare-lf-request-line.md index 977ef47..62fd4ca 100644 --- a/docs/content/docs/line-endings/bare-lf-request-line.md +++ b/docs/content/docs/line-endings/bare-lf-request-line.md @@ -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 @@ -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 diff --git a/src/Http11Probe/TestCases/Suites/ComplianceSuite.cs b/src/Http11Probe/TestCases/Suites/ComplianceSuite.cs index 4f89951..96e8127 100644 --- a/src/Http11Probe/TestCases/Suites/ComplianceSuite.cs +++ b/src/Http11Probe/TestCases/Suites/ComplianceSuite.cs @@ -22,28 +22,42 @@ public static IEnumerable 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; + } } };