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
17 changes: 16 additions & 1 deletion src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,22 @@ private void ValidateNonOriginHostHeader(string hostText)
if (!_absoluteRequestTarget.IsDefaultPort
|| hostText != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
// Superseded by RFC 7230, but notable for back-compat.
// https://datatracker.ietf.org/doc/html/rfc2616/#section-5.2
// 1. If Request-URI is an absoluteURI, the host is part of the
// Request-URI. Any Host header field value in the request MUST be
// ignored.
// We don't want to leave the invalid value for the app to accidentally consume,
// replace it with the value from the request line.
if (_context.ServiceContext.ServerOptions.EnableInsecureAbsoluteFormHostOverride)
{
hostText = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture);
HttpRequestHeaders.HeaderHost = hostText;
}
else
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ public class KestrelServerOptions

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

private bool? _enableInsecureAbsoluteFormHostOverride;
internal bool EnableInsecureAbsoluteFormHostOverride
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a public version of this the API proposal for .NET 7?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow for a missing Host header if this is set and there's an absolute form request target?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customer is currently asking for a short term mitigation, it's not clear we'll still need this in 7.0.

Should we allow for a missing Host header if this is set and there's an absolute form request target?

We could expand to that if needed, but it hasn't come up yet. So far the client is sending a Host header, but it's malformed.

Copy link
Member

@halter73 halter73 Jan 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's weird to require a HOST header if we're going to ignore it anyway. What does httpsys do?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Http.Sys also always requires the Host header to be present (even if empty), even if it overrides it with the host from the request line.

{
get
{
if (!_enableInsecureAbsoluteFormHostOverride.HasValue)
{
_enableInsecureAbsoluteFormHostOverride =
AppContext.TryGetSwitch("Microsoft.AspNetCore.Server.Kestrel.EnableInsecureAbsoluteFormHostOverride", out var enabled) && enabled;
}
return _enableInsecureAbsoluteFormHostOverride.Value;
}
set => _enableInsecureAbsoluteFormHostOverride = value;
}

// 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
using BadHttpRequestException = Microsoft.AspNetCore.Http.BadHttpRequestException;
Expand Down Expand Up @@ -137,6 +138,30 @@ public Task BadRequestIfHostHeaderDoesNotMatchRequestTarget(string requestTarget
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(host.Trim()));
}

[Fact]
public async Task CanOptOutOfBadRequestIfHostHeaderDoesNotMatchRequestTarget()
{
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()
{
EnableInsecureAbsoluteFormHostOverride = true,
}
});
using var client = server.CreateConnection();

await client.SendAll($"GET http://www.foo.com/api/data HTTP/1.1\r\nHost: www.foo.comConnection: keep-alive\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