File tree Expand file tree Collapse file tree 4 files changed +61
-1
lines changed
test/InMemory.FunctionalTests Expand file tree Collapse file tree 4 files changed +61
-1
lines changed Original file line number Diff line number Diff line change @@ -626,7 +626,15 @@ private void ValidateNonOriginHostHeader(string hostText)
626626 if ( ! _absoluteRequestTarget . IsDefaultPort
627627 || hostText != _absoluteRequestTarget . Authority + ":" + _absoluteRequestTarget . Port . ToString ( CultureInfo . InvariantCulture ) )
628628 {
629- KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
629+ if ( _context . ServiceContext . ServerOptions . AllowHostHeaderOverride )
630+ {
631+ hostText = _absoluteRequestTarget . Authority + ":" + _absoluteRequestTarget . Port . ToString ( CultureInfo . InvariantCulture ) ;
632+ HttpRequestHeaders . HeaderHost = hostText ;
633+ }
634+ else
635+ {
636+ KestrelBadHttpRequestException . Throw ( RequestRejectionReason . InvalidHostHeader , hostText ) ;
637+ }
630638 }
631639 }
632640 }
Original file line number Diff line number Diff line change @@ -35,6 +35,29 @@ public class KestrelServerOptions
3535
3636 private Func < string , Encoding ? > _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector ;
3737
38+ /// <summary>
39+ /// In HTTP/1.x, when a request target is in absolute-form (see RFC 9112 Section 3.2.2),
40+ /// for example
41+ /// <code>
42+ /// GET http://www.example.com/path/to/index.html HTTP/1.1
43+ /// </code>
44+ /// the Host header is redundant. In fact, the RFC says
45+ ///
46+ /// When an origin server receives a request with an absolute-form of request-target,
47+ /// the origin server MUST ignore the received Host header field (if any) and instead
48+ /// use the host information of the request-target.
49+ ///
50+ /// However, it is still sensible to check whether the request target and Host header match
51+ /// because a mismatch might indicate, for example, a spoofing attempt. Setting this property
52+ /// to true bypasses that check and unconditionally overwrites the Host header with the value
53+ /// from the request target.
54+ /// </summary>
55+ /// <remarks>
56+ /// This option does not apply to HTTP/2 or HTTP/3.
57+ /// </remarks>
58+ /// <seealso href="https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.2-8"/>
59+ public bool AllowHostHeaderOverride { get ; set ; }
60+
3861 // The following two lists configure the endpoints that Kestrel should listen to. If both lists are empty, the "urls" config setting (e.g. UseUrls) is used.
3962 internal List < ListenOptions > CodeBackedListenOptions { get ; } = new List < ListenOptions > ( ) ;
4063 internal List < ListenOptions > ConfigurationBackedListenOptions { get ; } = new List < ListenOptions > ( ) ;
Original file line number Diff line number Diff line change 11#nullable enable
22Microsoft.AspNetCore.Server.Kestrel.Core.Features.ISslStreamFeature
33Microsoft.AspNetCore.Server.Kestrel.Core.Features.ISslStreamFeature.SslStream.get -> System.Net.Security.SslStream!
4+ Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.AllowHostHeaderOverride.get -> bool
5+ Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.AllowHostHeaderOverride.set -> void
46Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ListenNamedPipe(string! pipeName) -> void
57Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ListenNamedPipe(string! pipeName, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions!>! configure) -> void
68Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.PipeName.get -> string?
Original file line number Diff line number Diff line change 1111using Microsoft . AspNetCore . Testing ;
1212using Microsoft . AspNetCore . WebUtilities ;
1313using Microsoft . Extensions . Logging ;
14+ using Microsoft . Extensions . Primitives ;
1415using Moq ;
1516using Xunit ;
1617using BadHttpRequestException = Microsoft . AspNetCore . Http . BadHttpRequestException ;
@@ -140,6 +141,32 @@ public Task BadRequestIfHostHeaderDoesNotMatchRequestTarget(string requestTarget
140141 CoreStrings . FormatBadRequest_InvalidHostHeader_Detail ( host . Trim ( ) ) ) ;
141142 }
142143
144+ [ Theory ]
145+ [ InlineData ( "Host: www.foo.comConnection: keep-alive" ) ] // Corrupted - missing line-break
146+ [ InlineData ( "Host: www.notfoo.com" ) ] // Syntactically correct but not matching
147+ public async Task CanOptOutOfBadRequestIfHostHeaderDoesNotMatchRequestTarget ( string hostHeader )
148+ {
149+ var receivedHost = StringValues . Empty ;
150+ await using var server = new TestServer ( context =>
151+ {
152+ receivedHost = context . Request . Headers . Host ;
153+ return Task . CompletedTask ;
154+ } , new TestServiceContext ( LoggerFactory )
155+ {
156+ ServerOptions = new KestrelServerOptions ( )
157+ {
158+ AllowHostHeaderOverride = true ,
159+ }
160+ } ) ;
161+ using var client = server . CreateConnection ( ) ;
162+
163+ await client . SendAll ( $ "GET http://www.foo.com/api/data HTTP/1.1\r \n { hostHeader } \r \n \r \n ") ;
164+
165+ await client . Receive ( "HTTP/1.1 200 OK" ) ;
166+
167+ Assert . Equal ( "www.foo.com:80" , receivedHost ) ;
168+ }
169+
143170 [ Fact ]
144171 public Task BadRequestFor10BadHostHeaderFormat ( )
145172 {
You can’t perform that action at this time.
0 commit comments