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
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,15 @@ private void ValidateNonOriginHostHeader(string hostText)
if (!_absoluteRequestTarget.IsDefaultPort
|| hostText != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
if (_context.ServiceContext.ServerOptions.AllowHostHeaderOverride)
{
hostText = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture);
HttpRequestHeaders.HeaderHost = hostText;
}
else
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ public class KestrelServerOptions

private Func<string, Encoding?> _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector;

/// <summary>
/// In HTTP/1.x, when a request target is in absolute-form (see RFC 9112 Section 3.2.2),
/// for example
/// <code>
/// GET http://www.example.com/path/to/index.html HTTP/1.1
/// </code>
/// the Host header is redundant. In fact, the RFC says
///
/// When an origin server receives a request with an absolute-form of request-target,
/// the origin server MUST ignore the received Host header field (if any) and instead
/// use the host information of the request-target.
///
/// However, it is still sensible to check whether the request target and Host header match
/// because a mismatch might indicate, for example, a spoofing attempt. Setting this property
/// to true bypasses that check and unconditionally overwrites the Host header with the value
/// from the request target.
/// </summary>
/// <remarks>
/// This option does not apply to HTTP/2 or HTTP/3.
/// </remarks>
/// <seealso href="https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.2-8"/>
public bool AllowHostHeaderOverride { get; set; }

// 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.
internal List<ListenOptions> CodeBackedListenOptions { get; } = new List<ListenOptions>();
internal List<ListenOptions> ConfigurationBackedListenOptions { get; } = new List<ListenOptions>();
Expand Down
2 changes: 2 additions & 0 deletions src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable
Microsoft.AspNetCore.Server.Kestrel.Core.Features.ISslStreamFeature
Microsoft.AspNetCore.Server.Kestrel.Core.Features.ISslStreamFeature.SslStream.get -> System.Net.Security.SslStream!
Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.AllowHostHeaderOverride.get -> bool
Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.AllowHostHeaderOverride.set -> void
Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ListenNamedPipe(string! pipeName) -> void
Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.ListenNamedPipe(string! pipeName, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions!>! configure) -> void
Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.PipeName.get -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
Expand Down Expand Up @@ -140,6 +141,32 @@ public Task BadRequestIfHostHeaderDoesNotMatchRequestTarget(string requestTarget
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(host.Trim()));
}

[Theory]
[InlineData("Host: www.foo.comConnection: keep-alive")] // Corrupted - missing line-break
[InlineData("Host: www.notfoo.com")] // Syntactically correct but not matching
public async Task CanOptOutOfBadRequestIfHostHeaderDoesNotMatchRequestTarget(string hostHeader)
{
var receivedHost = StringValues.Empty;
await using var server = new TestServer(context =>
{
receivedHost = context.Request.Headers.Host;
return Task.CompletedTask;
}, new TestServiceContext(LoggerFactory)
{
ServerOptions = new KestrelServerOptions()
{
AllowHostHeaderOverride = true,
}
});
using var client = server.CreateConnection();

await client.SendAll($"GET http://www.foo.com/api/data HTTP/1.1\r\n{hostHeader}\r\n\r\n");

await client.Receive("HTTP/1.1 200 OK");

Assert.Equal("www.foo.com:80", receivedHost);
}

[Fact]
public Task BadRequestFor10BadHostHeaderFormat()
{
Expand Down