Skip to content

Commit b7493ca

Browse files
MDA2AVclaude
andcommitted
Add 5 chunked TE attack vector tests to smuggling suite
New scored tests: SMUG-CHUNK-EXT-CR (bare CR in chunk extension), SMUG-TE-VTAB, SMUG-TE-FORMFEED, SMUG-TE-NULL (control char obfuscation in Transfer-Encoding value). New unscored test: SMUG-TRAILER-AUTH (Authorization in chunked trailers). STRICT dict entries added for all 5. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3cbea60 commit b7493ca

2 files changed

Lines changed: 146 additions & 5 deletions

File tree

.github/workflows/probe.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -549,11 +549,6 @@ jobs:
549549
'expected': '400/505/close/timeout',
550550
'reason': 'HTTP/2 preface sent to HTTP/1.1 server is not valid HTTP/1.1'
551551
},
552-
'MAL-CHUNK-EXTENSION-LONG': {
553-
'accept': [400, 431], 'close_ok': True, 'timeout_ok': False,
554-
'expected': '400/431 or close',
555-
'reason': '100 KB chunk extension exceeds reasonable limits'
556-
},
557552
'MAL-CL-EMPTY': {
558553
'accept': [400], 'close_ok': True, 'timeout_ok': False,
559554
'expected': '400 or close',
@@ -648,6 +643,26 @@ jobs:
648643
'expected': '400 or close',
649644
'reason': 'Negative chunk size must be rejected (RFC 9112 §7.1)'
650645
},
646+
'SMUG-CHUNK-EXT-CR': {
647+
'accept': [400], 'close_ok': True, 'timeout_ok': False,
648+
'expected': '400 or close',
649+
'reason': 'Bare CR in chunk extension is invalid (RFC 9112 §7.1.1)'
650+
},
651+
'SMUG-TE-VTAB': {
652+
'accept': [400], 'close_ok': True, 'timeout_ok': False,
653+
'expected': '400 or close',
654+
'reason': 'VTAB in TE value is a control char obfuscation vector (RFC 9112 §6.1)'
655+
},
656+
'SMUG-TE-FORMFEED': {
657+
'accept': [400], 'close_ok': True, 'timeout_ok': False,
658+
'expected': '400 or close',
659+
'reason': 'Form feed in TE value is a control char obfuscation vector (RFC 9112 §6.1)'
660+
},
661+
'SMUG-TE-NULL': {
662+
'accept': [400], 'close_ok': True, 'timeout_ok': False,
663+
'expected': '400 or close',
664+
'reason': 'NUL in TE value — C-string truncation attack (RFC 9112 §6.1)'
665+
},
651666
# ── Missing Smuggling tests (unscored) ───────────────────────
652667
'SMUG-TRAILER-CL': {
653668
'accept': [400], 'close_ok': True, 'timeout_ok': False,
@@ -667,6 +682,12 @@ jobs:
667682
'expected': '400 or 2xx',
668683
'reason': 'Host in trailers must not be used for routing (RFC 9110 §6.5.2)'
669684
},
685+
'SMUG-TRAILER-AUTH': {
686+
'accept': [400], 'close_ok': True, 'timeout_ok': False,
687+
'warn_on_2xx': True, 'scored': False,
688+
'expected': '400 or 2xx',
689+
'reason': 'Authorization in trailers is prohibited — 2xx means ignored (RFC 9110 §6.5.1)'
690+
},
670691
'SMUG-HEAD-CL-BODY': {
671692
'accept': [400], 'close_ok': True, 'timeout_ok': False,
672693
'warn_on_2xx': True, 'scored': False,

src/Http11Probe/TestCases/Suites/SmugglingSuite.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,103 @@ public static IEnumerable<TestCase> GetTestCases()
590590
}
591591
};
592592

593+
yield return new TestCase
594+
{
595+
Id = "SMUG-CHUNK-EXT-CR",
596+
Description = "Bare CR (not CRLF) in chunk extension — some parsers treat CR alone as line ending",
597+
Category = TestCategory.Smuggling,
598+
RfcReference = "RFC 9112 §7.1.1",
599+
PayloadFactory = ctx =>
600+
{
601+
// "5;a\rX\r\n" — the \r after "a" is NOT followed by \n
602+
var before = Encoding.ASCII.GetBytes($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nTransfer-Encoding: chunked\r\n\r\n5;a");
603+
byte[] bareCr = [0x0d]; // bare CR
604+
var after = Encoding.ASCII.GetBytes("X\r\nhello\r\n0\r\n\r\n");
605+
var payload = new byte[before.Length + bareCr.Length + after.Length];
606+
before.CopyTo(payload, 0);
607+
bareCr.CopyTo(payload, before.Length);
608+
after.CopyTo(payload, before.Length + bareCr.Length);
609+
return payload;
610+
},
611+
Expected = new ExpectedBehavior
612+
{
613+
ExpectedStatus = StatusCodeRange.Exact(400),
614+
AllowConnectionClose = true
615+
}
616+
};
617+
618+
yield return new TestCase
619+
{
620+
Id = "SMUG-TE-VTAB",
621+
Description = "Vertical tab before 'chunked' in TE value — control char obfuscation vector",
622+
Category = TestCategory.Smuggling,
623+
RfcReference = "RFC 9112 §6.1",
624+
PayloadFactory = ctx =>
625+
{
626+
var before = Encoding.ASCII.GetBytes($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nTransfer-Encoding: ");
627+
byte[] vtab = [0x0b];
628+
var after = Encoding.ASCII.GetBytes("chunked\r\nContent-Length: 5\r\n\r\nhello");
629+
var payload = new byte[before.Length + vtab.Length + after.Length];
630+
before.CopyTo(payload, 0);
631+
vtab.CopyTo(payload, before.Length);
632+
after.CopyTo(payload, before.Length + vtab.Length);
633+
return payload;
634+
},
635+
Expected = new ExpectedBehavior
636+
{
637+
ExpectedStatus = StatusCodeRange.Exact(400),
638+
AllowConnectionClose = true
639+
}
640+
};
641+
642+
yield return new TestCase
643+
{
644+
Id = "SMUG-TE-FORMFEED",
645+
Description = "Form feed before 'chunked' in TE value — control char obfuscation vector",
646+
Category = TestCategory.Smuggling,
647+
RfcReference = "RFC 9112 §6.1",
648+
PayloadFactory = ctx =>
649+
{
650+
var before = Encoding.ASCII.GetBytes($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nTransfer-Encoding: ");
651+
byte[] ff = [0x0c];
652+
var after = Encoding.ASCII.GetBytes("chunked\r\nContent-Length: 5\r\n\r\nhello");
653+
var payload = new byte[before.Length + ff.Length + after.Length];
654+
before.CopyTo(payload, 0);
655+
ff.CopyTo(payload, before.Length);
656+
after.CopyTo(payload, before.Length + ff.Length);
657+
return payload;
658+
},
659+
Expected = new ExpectedBehavior
660+
{
661+
ExpectedStatus = StatusCodeRange.Exact(400),
662+
AllowConnectionClose = true
663+
}
664+
};
665+
666+
yield return new TestCase
667+
{
668+
Id = "SMUG-TE-NULL",
669+
Description = "NUL byte appended to 'chunked' in TE value — C-string truncation attack",
670+
Category = TestCategory.Smuggling,
671+
RfcReference = "RFC 9112 §6.1",
672+
PayloadFactory = ctx =>
673+
{
674+
var before = Encoding.ASCII.GetBytes($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nTransfer-Encoding: chunked");
675+
byte[] nul = [0x00];
676+
var after = Encoding.ASCII.GetBytes("\r\nContent-Length: 5\r\n\r\nhello");
677+
var payload = new byte[before.Length + nul.Length + after.Length];
678+
before.CopyTo(payload, 0);
679+
nul.CopyTo(payload, before.Length);
680+
after.CopyTo(payload, before.Length + nul.Length);
681+
return payload;
682+
},
683+
Expected = new ExpectedBehavior
684+
{
685+
ExpectedStatus = StatusCodeRange.Exact(400),
686+
AllowConnectionClose = true
687+
}
688+
};
689+
593690
yield return new TestCase
594691
{
595692
Id = "SMUG-CHUNK-LF-TRAILER",
@@ -798,6 +895,29 @@ public static IEnumerable<TestCase> GetTestCases()
798895
}
799896
};
800897

898+
yield return new TestCase
899+
{
900+
Id = "SMUG-TRAILER-AUTH",
901+
Description = "Authorization header in chunked trailers — prohibited per RFC 9110 §6.5.1",
902+
Category = TestCategory.Smuggling,
903+
RfcReference = "RFC 9110 §6.5.1",
904+
PayloadFactory = ctx => MakeRequest(
905+
$"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\nAuthorization: Bearer evil\r\n\r\n"),
906+
Expected = new ExpectedBehavior
907+
{
908+
CustomValidator = (response, state) =>
909+
{
910+
if (response is null)
911+
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
912+
if (response.StatusCode == 400)
913+
return TestVerdict.Pass;
914+
if (response.StatusCode is >= 200 and < 300)
915+
return TestVerdict.Warn;
916+
return TestVerdict.Fail;
917+
}
918+
}
919+
};
920+
801921
yield return new TestCase
802922
{
803923
Id = "SMUG-HEAD-CL-BODY",

0 commit comments

Comments
 (0)