// This does not duplicate format validations that are expected to be performed by the host. private bool CheckHost(HttpContext context, IList <StringSegment> allowedHosts) { var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim(); if (StringSegment.IsNullOrEmpty(host)) { // Http/1.0 does not require the host header. // Http/1.1 requires the header but the value may be empty. if (!_options.AllowEmptyHosts) { _logger.LogInformation("{Protocol} request rejected due to missing or empty host header.", context.Request.Protocol); return(false); } _logger.LogDebug("{Protocol} request allowed with missing or empty host header.", context.Request.Protocol); return(true); } if (_allowAnyNonEmptyHost == true) { _logger.LogTrace("All hosts are allowed."); return(true); } if (HostString.MatchesAny(host, allowedHosts)) { _logger.LogTrace("The host '{Host}' matches an allowed host.", host); return(true); } _logger.LogInformation("The host '{Host}' does not match an allowed host.", host); return(false); }
private bool CheckHostInAllowList(IList <StringSegment> allowedHosts, string host) { if (HostString.MatchesAny(new StringSegment(host), allowedHosts)) { _logger.AllowedHostMatched(host); return(true); } _logger.NoAllowedHostMatched(host); return(false); }
public void ApplyForwarders(HttpContext context) { // Gather expected headers. Enabled headers must have the same number of entries. string[] forwardedFor = null, forwardedProto = null, forwardedHost = null; bool checkFor = false, checkProto = false, checkHost = false; int entryCount = 0; if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedFor) == ForwardedHeaders.XForwardedFor) { checkFor = true; forwardedFor = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedForHeaderName); entryCount = Math.Max(forwardedFor.Length, entryCount); } if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto) { checkProto = true; forwardedProto = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedProtoHeaderName); if (_options.RequireHeaderSymmetry && checkFor && forwardedFor.Length != forwardedProto.Length) { _logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto."); return; } entryCount = Math.Max(forwardedProto.Length, entryCount); } if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost) { checkHost = true; forwardedHost = context.Request.Headers.GetCommaSeparatedValues(_options.ForwardedHostHeaderName); if (_options.RequireHeaderSymmetry && ((checkFor && forwardedFor.Length != forwardedHost.Length) || (checkProto && forwardedProto.Length != forwardedHost.Length))) { _logger.LogWarning(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto."); return; } entryCount = Math.Max(forwardedHost.Length, entryCount); } // Apply ForwardLimit, if any if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit) { entryCount = _options.ForwardLimit.Value; } // Group the data together. var sets = new SetOfForwarders[entryCount]; for (int i = 0; i < sets.Length; i++) { // They get processed in reverse order, right to left. var set = new SetOfForwarders(); if (checkFor && i < forwardedFor.Length) { set.IpAndPortText = forwardedFor[forwardedFor.Length - i - 1]; } if (checkProto && i < forwardedProto.Length) { set.Scheme = forwardedProto[forwardedProto.Length - i - 1]; } if (checkHost && i < forwardedHost.Length) { set.Host = forwardedHost[forwardedHost.Length - i - 1]; } sets[i] = set; } // Gather initial values var connection = context.Connection; var request = context.Request; var currentValues = new SetOfForwarders() { RemoteIpAndPort = connection.RemoteIpAddress != null ? new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort) : null, // Host and Scheme initial values are never inspected, no need to set them here. }; var checkKnownIps = _options.KnownNetworks.Count > 0 || _options.KnownProxies.Count > 0; bool applyChanges = false; int entriesConsumed = 0; for ( ; entriesConsumed < sets.Length; entriesConsumed++) { var set = sets[entriesConsumed]; if (checkFor) { // For the first instance, allow remoteIp to be null for servers that don't support it natively. if (currentValues.RemoteIpAndPort != null && checkKnownIps && !CheckKnownAddress(currentValues.RemoteIpAndPort.Address)) { // Stop at the first unknown remote IP, but still apply changes processed so far. _logger.LogDebug(1, $"Unknown proxy: {currentValues.RemoteIpAndPort}"); break; } IPEndPoint parsedEndPoint; if (IPEndPointParser.TryParse(set.IpAndPortText, out parsedEndPoint)) { applyChanges = true; set.RemoteIpAndPort = parsedEndPoint; currentValues.IpAndPortText = set.IpAndPortText; currentValues.RemoteIpAndPort = set.RemoteIpAndPort; } else if (!string.IsNullOrEmpty(set.IpAndPortText)) { // Stop at the first unparsable IP, but still apply changes processed so far. _logger.LogDebug(1, $"Unparsable IP: {set.IpAndPortText}"); break; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(2, $"Missing forwarded IPAddress."); return; } } if (checkProto) { if (!string.IsNullOrEmpty(set.Scheme) && TryValidateScheme(set.Scheme)) { applyChanges = true; currentValues.Scheme = set.Scheme; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(3, $"Forwarded scheme is not present, this is required by {nameof(_options.RequireHeaderSymmetry)}"); return; } } if (checkHost) { if (!string.IsNullOrEmpty(set.Host) && TryValidateHost(set.Host) && (_allowAllHosts || HostString.MatchesAny(set.Host, _allowedHosts))) { applyChanges = true; currentValues.Host = set.Host; } else if (_options.RequireHeaderSymmetry) { _logger.LogWarning(4, $"Incorrect number of x-forwarded-proto header values, see {nameof(_options.RequireHeaderSymmetry)}."); return; } } } if (applyChanges) { if (checkFor && currentValues.RemoteIpAndPort != null) { if (connection.RemoteIpAddress != null) { // Save the original request.Headers[_options.OriginalForHeaderName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString(); } if (forwardedFor.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedForHeaderName); } connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address; connection.RemotePort = currentValues.RemoteIpAndPort.Port; } if (checkProto && currentValues.Scheme != null) { // Save the original request.Headers[_options.OriginalProtoHeaderName] = request.Scheme; if (forwardedProto.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedProtoHeaderName); } request.Scheme = currentValues.Scheme; } if (checkHost && currentValues.Host != null) { // Save the original request.Headers[_options.OriginalHostHeaderName] = request.Host.ToString(); if (forwardedHost.Length > entriesConsumed) { // Truncate the consumed header values request.Headers[_options.ForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray(); } else { // All values were consumed request.Headers.Remove(_options.ForwardedHostHeaderName); } request.Host = HostString.FromUriComponent(currentValues.Host); } } }
public void HostMatchThrowsForBadPort() { Assert.Throws <FormatException>(() => HostString.MatchesAny("example.com:1abc", new StringSegment[] { "example.com" })); }
[InlineData("::1", "::1")] // Brackets are added to the host before the comparison public void HostDoesntMatch(string host, string pattern) { Assert.False(HostString.MatchesAny(host, new StringSegment[] { pattern })); }
public void HostMatches(string host, string pattern) { Assert.True(HostString.MatchesAny(host, new StringSegment[] { pattern })); }