From a802b777400ff93775a89fbcc965b8e5f6b81fa2 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 7 Jan 2022 12:58:40 -0800 Subject: [PATCH 1/2] Allow overriding the host header if doesn't match the absolute-form host (#39334) * Allow overriding the host header if doesn't match the absolute-form host * Apply suggestions from code review Co-authored-by: Stephen Halter --- .../Core/src/Internal/Http/Http1Connection.cs | 17 ++++++++++++- .../Kestrel/Core/src/KestrelServerOptions.cs | 15 +++++++++++ .../BadHttpRequestTests.cs | 25 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 87998e408288..840d50ee5476 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -629,7 +629,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); + } } } } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index ba5c6151b4eb..ef3ed4d95495 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -34,6 +34,21 @@ public class KestrelServerOptions private Func _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector; + private bool? _enableInsecureAbsoluteFormHostOverride; + internal bool EnableInsecureAbsoluteFormHostOverride + { + 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 CodeBackedListenOptions { get; } = new List(); internal List ConfigurationBackedListenOptions { get; } = new List(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/BadHttpRequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/BadHttpRequestTests.cs index 5bde02517fff..308378ef7330 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/BadHttpRequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/BadHttpRequestTests.cs @@ -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; @@ -140,6 +141,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() { From 7e1077d23a2908ed3cc803a3e6fa04c6e9e92e0e Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 24 May 2023 12:07:44 -0700 Subject: [PATCH 2/2] Add explanatory comment --- src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 840d50ee5476..38e204bdf95c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -636,6 +636,9 @@ private void ValidateNonOriginHostHeader(string hostText) // 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. + // This is insecure in the sense that we would ordinarily regard a mismatched + // request target and host as suspicious (e.g. indicative of a spoofing attempt) + // and reject the request. if (_context.ServiceContext.ServerOptions.EnableInsecureAbsoluteFormHostOverride) { hostText = _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture);