@@ -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 \n Host: { ctx . HostHeader } \r \n Transfer-Encoding: chunked\r \n \r \n 5;a") ;
603+ byte [ ] bareCr = [ 0x0d ] ; // bare CR
604+ var after = Encoding . ASCII . GetBytes ( "X\r \n hello\r \n 0\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 \n Host: { ctx . HostHeader } \r \n Transfer-Encoding: ") ;
627+ byte [ ] vtab = [ 0x0b ] ;
628+ var after = Encoding . ASCII . GetBytes ( "chunked\r \n Content-Length: 5\r \n \r \n hello" ) ;
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 \n Host: { ctx . HostHeader } \r \n Transfer-Encoding: ") ;
651+ byte [ ] ff = [ 0x0c ] ;
652+ var after = Encoding . ASCII . GetBytes ( "chunked\r \n Content-Length: 5\r \n \r \n hello" ) ;
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 \n Host: { ctx . HostHeader } \r \n Transfer-Encoding: chunked") ;
675+ byte [ ] nul = [ 0x00 ] ;
676+ var after = Encoding . ASCII . GetBytes ( "\r \n Content-Length: 5\r \n \r \n hello" ) ;
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 \n Host: { ctx . HostHeader } \r \n Transfer-Encoding: chunked\r \n \r \n 5\r \n hello\r \n 0\r \n Authorization: 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