Restrict x-forwarded-for evalutation. Credit: pmhsfelix.

Use x-forwarded-for validation to restrict other forwarders.

Rename OverrideHeaderMiddleware to ForwardedHeadersMiddleware.

Fix tests

Fix package version.
This commit is contained in:
Chris R 2015-12-21 14:45:37 -08:00
parent ab34d08bc1
commit 44f03ef83f
15 changed files with 948 additions and 360 deletions

View File

@ -10,10 +10,9 @@ namespace HttpOverridesSample
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseOverrideHeaders(new OverrideHeaderOptions
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedOptions = ForwardedHeaders.All
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHttpMethodOverride();

View File

@ -4,9 +4,9 @@
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.HttpOverrides": "1.0.0-*",
"Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNetCore.HttpOverrides": "0.1.0-*"
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*"
},
"commands": {
"web": "HttpOverridesSample"
@ -23,4 +23,4 @@
"**.user",
"**.vspscc"
]
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -7,31 +7,30 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
public static class OverrideHeaderExtensions
public static class ForwardedHeadersExtensions
{
/// <summary>
/// Forwards proxied headers onto current request
/// </summary>
/// <param name="builder"></param>
/// <param name="options">Enables the different override options.</param>
/// <returns></returns>
public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder)
public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.UseMiddleware<OverrideHeaderMiddleware>();
return builder.UseMiddleware<ForwardedHeadersMiddleware>();
}
/// <summary>
/// Forwards proxied headers onto current request
/// </summary>
/// <param name="builder"></param>
/// <param name="options">Enables the different override options.</param>
/// <param name="options">Enables the different forwarding options.</param>
/// <returns></returns>
public static IApplicationBuilder UseOverrideHeaders(this IApplicationBuilder builder, OverrideHeaderOptions options)
public static IApplicationBuilder UseForwardedHeaders(this IApplicationBuilder builder, ForwardedHeadersOptions options)
{
if (builder == null)
{
@ -42,7 +41,7 @@ namespace Microsoft.AspNetCore.Builder
throw new ArgumentNullException(nameof(options));
}
return builder.UseMiddleware<OverrideHeaderMiddleware>(Options.Create(options));
return builder.UseMiddleware<ForwardedHeadersMiddleware>(Options.Create(options));
}
}
}

View File

@ -0,0 +1,275 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class ForwardedHeadersMiddleware
{
private const string XForwardedForHeaderName = "X-Forwarded-For";
private const string XForwardedHostHeaderName = "X-Forwarded-Host";
private const string XForwardedProtoHeaderName = "X-Forwarded-Proto";
private const string XOriginalForName = "X-Original-For";
private const string XOriginalHostName = "X-Original-Host";
private const string XOriginalProtoName = "X-Original-Proto";
private readonly ForwardedHeadersOptions _options;
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ForwardedHeadersMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ForwardedHeadersOptions> options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options.Value;
_logger = loggerFactory.CreateLogger<ForwardedHeadersMiddleware>();
_next = next;
}
public Task Invoke(HttpContext context)
{
ApplyForwarders(context);
return _next(context);
}
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(XForwardedForHeaderName);
if (StringValues.IsNullOrEmpty(forwardedFor))
{
return;
}
entryCount = forwardedFor.Length;
}
if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedProto) == ForwardedHeaders.XForwardedProto)
{
checkProto = true;
forwardedProto = context.Request.Headers.GetCommaSeparatedValues(XForwardedProtoHeaderName);
if (StringValues.IsNullOrEmpty(forwardedProto))
{
return;
}
if (checkFor && forwardedFor.Length != forwardedProto.Length)
{
_logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto.");
return;
}
entryCount = forwardedProto.Length;
}
if ((_options.ForwardedHeaders & ForwardedHeaders.XForwardedHost) == ForwardedHeaders.XForwardedHost)
{
checkHost = true;
forwardedHost = context.Request.Headers.GetCommaSeparatedValues(XForwardedHostHeaderName);
if (StringValues.IsNullOrEmpty(forwardedHost))
{
return;
}
if ((checkFor && forwardedFor.Length != forwardedHost.Length)
|| (checkProto && forwardedProto.Length != forwardedHost.Length))
{
_logger.LogDebug(1, "Parameter count mismatch between X-Forwarded-Host and X-Forwarded-For or X-Forwarded-Proto.");
return;
}
entryCount = forwardedHost.Length;
}
// Apply ForwardLimit, if any
int offset = 0;
if (_options.ForwardLimit.HasValue && entryCount > _options.ForwardLimit)
{
offset = entryCount - _options.ForwardLimit.Value;
entryCount = _options.ForwardLimit.Value;
}
// Group the data together.
var sets = new List<SetOfForwarders>(entryCount);
for (int i = 0; i < entryCount; i++)
{
var set = new SetOfForwarders();
if (checkFor)
{
set.IpAndPortText = forwardedFor[offset + i];
}
if (checkProto)
{
set.Scheme = forwardedProto[offset + i];
}
if (checkHost)
{
set.Host = forwardedHost[offset + i];
}
sets.Add(set);
}
// They get processed in reverse order, right to left.
sets.Reverse();
// 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;
foreach (var set in sets)
{
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;
}
if (!IPEndPointParser.TryParse(set.IpAndPortText, out set.RemoteIpAndPort))
{
_logger.LogDebug(2, $"Failed to parse forwarded IPAddress: {currentValues.IpAndPortText}");
return;
}
}
if (checkProto)
{
if (string.IsNullOrEmpty(set.Scheme))
{
_logger.LogDebug(3, $"Failed to parse forwarded scheme: {set.Scheme}");
return;
}
}
if (checkHost)
{
if (string.IsNullOrEmpty(set.Host))
{
_logger.LogDebug(4, $"Failed to parse forwarded host: {set.Host}");
return;
}
}
applyChanges = true;
currentValues = set;
entriesConsumed++;
}
if (applyChanges)
{
if (checkFor)
{
if (connection.RemoteIpAddress != null)
{
// Save the original
request.Headers[XOriginalForName] = new IPEndPoint(connection.RemoteIpAddress, connection.RemotePort).ToString();
}
if (forwardedFor.Length > entriesConsumed)
{
// Truncate the consumed header values
request.Headers[XForwardedForHeaderName] = forwardedFor.Take(forwardedFor.Length - entriesConsumed).ToArray();
}
else
{
// All values were consumed
request.Headers.Remove(XForwardedForHeaderName);
}
connection.RemoteIpAddress = currentValues.RemoteIpAndPort.Address;
connection.RemotePort = currentValues.RemoteIpAndPort.Port;
}
if (checkProto)
{
// Save the original
request.Headers[XOriginalProtoName] = request.Scheme;
if (forwardedProto.Length > entriesConsumed)
{
// Truncate the consumed header values
request.Headers[XForwardedProtoHeaderName] = forwardedProto.Take(forwardedProto.Length - entriesConsumed).ToArray();
}
else
{
// All values were consumed
request.Headers.Remove(XForwardedProtoHeaderName);
}
request.Scheme = currentValues.Scheme;
}
if (checkHost)
{
// Save the original
request.Headers[XOriginalHostName] = request.Host.ToString();
if (forwardedHost.Length > entriesConsumed)
{
// Truncate the consumed header values
request.Headers[XForwardedHostHeaderName] = forwardedHost.Take(forwardedHost.Length - entriesConsumed).ToArray();
}
else
{
// All values were consumed
request.Headers.Remove(XForwardedHostHeaderName);
}
request.Host = HostString.FromUriComponent(currentValues.Host);
}
}
}
private bool CheckKnownAddress(IPAddress address)
{
if (_options.KnownProxies.Contains(address))
{
return true;
}
foreach (var network in _options.KnownNetworks)
{
if (network.Contains(address))
{
return true;
}
}
return false;
}
private class SetOfForwarders
{
public string IpAndPortText;
public IPEndPoint RemoteIpAndPort;
public string Host;
public string Scheme;
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.HttpOverrides;
namespace Microsoft.AspNetCore.Builder
{
public class ForwardedHeadersOptions
{
/// <summary>
/// Identifies which forwarders should be processed.
/// </summary>
public ForwardedHeaders ForwardedHeaders { get; set; }
/// <summary>
/// Limits the number of entries in the headers that will be processed. The default value is 1.
/// Set to null to disable the limit, but this should only be done if
/// KnownProxies or KnownNetworks are configured.
/// </summary>
public int? ForwardLimit { get; set; } = 1;
/// <summary>
/// Addresses of known proxies to accept forwarded headers from.
/// </summary>
public IList<IPAddress> KnownProxies { get; } = new List<IPAddress>() { IPAddress.IPv6Loopback };
/// <summary>
/// Address ranges of known proxies to accept forwarded headers from.
/// </summary>
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class IPNetwork
{
public IPNetwork(IPAddress prefix, int prefixLength)
{
Prefix = prefix;
PrefixLength = prefixLength;
PrefixBytes = Prefix.GetAddressBytes();
Mask = CreateMask();
}
public IPAddress Prefix { get; }
private byte[] PrefixBytes { get; }
/// <summary>
/// The CIDR notation of the subnet mask
/// </summary>
public int PrefixLength { get; }
private byte[] Mask { get; }
public bool Contains(IPAddress address)
{
if (Prefix.AddressFamily != address.AddressFamily)
{
return false;
}
var addressBytes = address.GetAddressBytes();
for (int i = 0; i < PrefixBytes.Length && Mask[i] != 0; i++)
{
if (PrefixBytes[i] != (addressBytes[i] & Mask[i]))
{
return false;
}
}
return true;
}
private byte[] CreateMask()
{
var mask = new byte[PrefixBytes.Length];
int remainingBits = PrefixLength;
int i = 0;
while (remainingBits >= 8)
{
mask[i] = 0xFF;
i++;
remainingBits -= 8;
}
if (remainingBits > 0)
{
mask[i] = (byte)(0xFF << (8 - remainingBits));
}
return mask;
}
}
}

View File

@ -3,7 +3,7 @@
using System.Net;
namespace Microsoft.AspNetCore.HttpOverrides
namespace Microsoft.AspNetCore.HttpOverrides.Internal
{
public static class IPEndPointParser
{

View File

@ -1,107 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class OverrideHeaderMiddleware
{
private const string XForwardedForHeaderName = "X-Forwarded-For";
private const string XForwardedHostHeaderName = "X-Forwarded-Host";
private const string XForwardedProtoHeaderName = "X-Forwarded-Proto";
private const string XOriginalForName = "X-Original-For";
private const string XOriginalHostName = "X-Original-Host";
private const string XOriginalProtoName = "X-Original-Proto";
private readonly OverrideHeaderOptions _options;
private readonly RequestDelegate _next;
public OverrideHeaderMiddleware(RequestDelegate next, IOptions<OverrideHeaderOptions> options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options.Value;
_next = next;
}
public Task Invoke(HttpContext context)
{
UpdateRemoteIp(context);
UpdateHost(context);
UpdateScheme(context);
return _next(context);
}
private void UpdateRemoteIp(HttpContext context)
{
if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedFor) != 0)
{
var xForwardedForHeaderValue = context.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0)
{
IPEndPoint endpoint;
if (IPEndPointParser.TryParse(xForwardedForHeaderValue[0], out endpoint))
{
var connection = context.Connection;
var remoteIP = connection.RemoteIpAddress;
if (remoteIP != null)
{
var remoteIPString = new IPEndPoint(remoteIP, connection.RemotePort).ToString();
context.Request.Headers[XOriginalForName] = remoteIPString;
}
connection.RemoteIpAddress = endpoint.Address;
connection.RemotePort = endpoint.Port;
}
}
}
}
private void UpdateHost(HttpContext context)
{
if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedHost) != 0)
{
var xForwardHostHeaderValue = context.Request.Headers[XForwardedHostHeaderName];
if (!string.IsNullOrEmpty(xForwardHostHeaderValue))
{
var hostString = context.Request.Host.ToString();
if (!string.IsNullOrEmpty(hostString))
{
context.Request.Headers[XOriginalHostName] = hostString;
}
context.Request.Host = HostString.FromUriComponent(xForwardHostHeaderValue);
}
}
}
private void UpdateScheme(HttpContext context)
{
if ((_options.ForwardedOptions & ForwardedHeaders.XForwardedProto) != 0)
{
var xForwardProtoHeaderValue = context.Request.Headers[XForwardedProtoHeaderName];
if (!string.IsNullOrEmpty(xForwardProtoHeaderValue))
{
if (!string.IsNullOrEmpty(context.Request.Scheme))
{
context.Request.Headers[XOriginalProtoName] = context.Request.Scheme;
}
context.Request.Scheme = xForwardProtoHeaderValue;
}
}
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.HttpOverrides;
namespace Microsoft.AspNetCore.Builder
{
public class OverrideHeaderOptions
{
public ForwardedHeaders ForwardedOptions { get; set; }
}
}

View File

@ -1,5 +1,5 @@
{
"version": "0.1.0-*",
"version": "1.0.0-*",
"compilationOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
@ -11,6 +11,7 @@
},
"dependencies": {
"Microsoft.AspNetCore.Http.Extensions": "1.0.0-*",
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
"Microsoft.Extensions.Options": "1.0.0-*"
},
"frameworks": {

View File

@ -0,0 +1,522 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class ForwardedHeadersMiddlewareTests
{
[Fact]
public async Task XForwardedForDefaultSettingsChangeRemoteIpAndPort()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
Assert.Equal(9090, context.Connection.RemotePort);
// No Original set if RemoteIpAddress started null.
Assert.False(context.Request.Headers.ContainsKey("X-Original-For"));
// Should have been consumed and removed
Assert.False(context.Request.Headers.ContainsKey("X-Forwarded-For"));
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11:9090");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData(1, "11.111.111.11.12345", "10.0.0.1", 99)] // Invalid
public async Task XForwardedForFirstValueIsInvalid(int limit, string header, string expectedIp, int expectedPort)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.Use((context, next) =>
{
context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
context.Connection.RemotePort = 99;
return next();
});
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor,
ForwardLimit = limit,
});
app.Run(context =>
{
Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString());
Assert.Equal(expectedPort, context.Connection.RemotePort);
Assert.False(context.Request.Headers.ContainsKey("X-Original-For"));
Assert.True(context.Request.Headers.ContainsKey("X-Forwarded-For"));
Assert.Equal(header, context.Request.Headers["X-Forwarded-For"]);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData(1, "11.111.111.11:12345", "11.111.111.11", 12345, "")]
[InlineData(10, "11.111.111.11:12345", "11.111.111.11", 12345, "")]
[InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "11.111.111.11", 12345, "12.112.112.12:23456")]
[InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")]
[InlineData(10, "12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "")]
[InlineData(10, "12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99, "12.112.112.12.23456, 11.111.111.11:12345")] // Invalid
[InlineData(10, "13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345", "10.0.0.1", 99,
"13.113.113.13:34567, 12.112.112.12.23456, 11.111.111.11:12345")] // Invalid
[InlineData(2, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "12.112.112.12", 23456, "13.113.113.13:34567")]
[InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "13.113.113.13", 34567, "")]
public async Task XForwardedForForwardLimit(int limit, string header, string expectedIp, int expectedPort, string remainingHeader)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.Use((context, next) =>
{
context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
context.Connection.RemotePort = 99;
return next();
});
var options = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor,
ForwardLimit = limit,
};
options.KnownProxies.Clear();
options.KnownNetworks.Clear();
app.UseForwardedHeaders(options);
app.Run(context =>
{
Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString());
Assert.Equal(expectedPort, context.Connection.RemotePort);
Assert.Equal(remainingHeader, context.Request.Headers["X-Forwarded-For"].ToString());
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData("11.111.111.11", false)]
[InlineData("127.0.0.1", true)]
[InlineData("127.0.1.1", true)]
[InlineData("::1", true)]
[InlineData("::", false)]
public async Task XForwardedForLoopback(string originalIp, bool expectForwarded)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.Use((context, next) =>
{
context.Connection.RemoteIpAddress = IPAddress.Parse(originalIp);
context.Connection.RemotePort = 99;
return next();
});
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor,
});
app.Run(context =>
{
if (expectForwarded)
{
Assert.Equal("10.0.0.1", context.Connection.RemoteIpAddress.ToString());
Assert.Equal(1234, context.Connection.RemotePort);
Assert.True(context.Request.Headers.ContainsKey("X-Original-For"));
Assert.Equal(new IPEndPoint(IPAddress.Parse(originalIp), 99).ToString(),
context.Request.Headers["X-Original-For"]);
}
else
{
Assert.Equal(originalIp, context.Connection.RemoteIpAddress.ToString());
Assert.Equal(99, context.Connection.RemotePort);
Assert.False(context.Request.Headers.ContainsKey("X-Original-For"));
}
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.TryAddWithoutValidation("X-Forwarded-For", "10.0.0.1:1234");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData(1, "11.111.111.11:12345", "20.0.0.1", "10.0.0.1", 99)]
[InlineData(1, "", "10.0.0.1", "10.0.0.1", 99)]
[InlineData(1, "11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
[InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
[InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1", "11.111.111.11", 12345)]
[InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "11.111.111.11", 12345)]
[InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11", "12.112.112.12", 23456)]
[InlineData(1, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "11.111.111.11", 12345)]
[InlineData(2, "12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "12.112.112.12", 23456)]
[InlineData(3, "13.113.113.13:34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "13.113.113.13", 34567)]
[InlineData(3, "13.113.113.13:34567, 12.112.112.12;23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "10.0.0.1", 99)] // Invalid 2nd IP
[InlineData(3, "13.113.113.13;34567, 12.112.112.12:23456, 11.111.111.11:12345", "10.0.0.1,11.111.111.11,12.112.112.12", "10.0.0.1", 99)] // Invalid 3rd IP
public async Task XForwardedForForwardKnownIps(int limit, string header, string knownIPs, string expectedIp, int expectedPort)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.Use((context, next) =>
{
context.Connection.RemoteIpAddress = IPAddress.Parse("10.0.0.1");
context.Connection.RemotePort = 99;
return next();
});
var options = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor,
ForwardLimit = limit,
};
foreach (var ip in knownIPs.Split(',').Select(text => IPAddress.Parse(text)))
{
options.KnownProxies.Add(ip);
}
app.UseForwardedHeaders(options);
app.Run(context =>
{
Assert.Equal(expectedIp, context.Connection.RemoteIpAddress.ToString());
Assert.Equal(expectedPort, context.Connection.RemotePort);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.TryAddWithoutValidation("X-Forwarded-For", header);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor
});
app.Run(context =>
{
Assert.Null(context.Connection.RemoteIpAddress);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "BAD-IP");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task XForwardedHostOverrideChangesRequestHost()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedHost
});
app.Run(context =>
{
Assert.Equal("testhost", context.Request.Host.ToString());
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Host", "testhost");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData(0, "h1", "http")]
[InlineData(1, "", "http")]
[InlineData(1, "h1", "h1")]
[InlineData(3, "h1", "h1")]
[InlineData(1, "h2, h1", "h1")]
[InlineData(2, "h2, h1", "h2")]
[InlineData(10, "h3, h2, h1", "h3")]
public async Task XForwardedProtoOverrideChangesRequestProtocol(int limit, string header, string expected)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto,
ForwardLimit = limit,
});
app.Run(context =>
{
Assert.Equal(expected, context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Proto", header);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData(0, "h1", "::1", "http")]
[InlineData(1, "", "::1", "http")]
[InlineData(1, "h1", "::1", "h1")]
[InlineData(3, "h1", "::1", "h1")]
[InlineData(3, "h2, h1", "::1", "http")]
[InlineData(5, "h2, h1", "::1, ::1", "h2")]
[InlineData(10, "h3, h2, h1", "::1, ::1, ::1", "h3")]
[InlineData(10, "h3, h2, h1", "::1, badip, ::1", "http")]
public async Task XForwardedProtoOverrideLimitedByXForwardedForCount(int limit, string protoHeader, string forHeader, string expected)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
ForwardLimit = limit,
});
app.Run(context =>
{
Assert.Equal(expected, context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Proto", protoHeader);
req.Headers.Add("X-Forwarded-For", forHeader);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Theory]
[InlineData("", "", "::1", false, "http")]
[InlineData("h1", "", "::1", false, "http")]
[InlineData("h1", "F::", "::1", false, "h1")]
[InlineData("h1", "F::", "E::", false, "h1")]
[InlineData("", "", "::1", true, "http")]
[InlineData("h1", "", "::1", true, "http")]
[InlineData("h1", "F::", "::1", true, "h1")]
[InlineData("h1", "", "F::", true, "http")]
[InlineData("h1", "E::", "F::", true, "http")]
[InlineData("h2, h1", "", "::1", true, "http")]
[InlineData("h2, h1", "F::, D::", "::1", true, "h1")]
[InlineData("h2, h1", "E::, D::", "F::", true, "http")]
[InlineData("h2, h1", "E::, D::", "F::", true, "http")]
public async Task XForwardedProtoOverrideLimitedByLoopback(string protoHeader, string forHeader, string remoteIp, bool loopback, string expected)
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.Use((context, next) =>
{
context.Connection.RemoteIpAddress = IPAddress.Parse(remoteIp);
return next();
});
var options = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
ForwardLimit = 5,
};
if (!loopback)
{
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
}
app.UseForwardedHeaders(options);
app.Run(context =>
{
Assert.Equal(expected, context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Proto", protoHeader);
req.Headers.Add("X-Forwarded-For", forHeader);
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public void AllForwardsDisabledByDefault()
{
var options = new ForwardedHeadersOptions();
Assert.True(options.ForwardedHeaders == ForwardedHeaders.None);
Assert.Equal(1, options.ForwardLimit);
Assert.Equal(1, options.KnownNetworks.Count());
Assert.Equal(1, options.KnownProxies.Count());
}
[Fact]
public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
Assert.Equal("testhost", context.Request.Host.ToString());
Assert.Equal("Protocol", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Host", "testhost");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task AllOptionsDisabledRequestDoesntChange()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.None
});
app.Run(context =>
{
Assert.Null(context.Connection.RemoteIpAddress);
Assert.Equal("localhost", context.Request.Host.ToString());
Assert.Equal("http", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Host", "otherhost");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
Assert.Equal("localhost", context.Request.Host.ToString());
Assert.Equal("Protocol", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
}
}

View File

@ -4,7 +4,7 @@
using System.Net;
using Xunit;
namespace Microsoft.AspNetCore.HttpOverrides
namespace Microsoft.AspNetCore.HttpOverrides.Internal
{
public class IPEndPointParserTests
{

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Xunit;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class IPNetworkTest
{
[Theory]
[InlineData("10.1.1.0", 8, "10.1.1.10")]
[InlineData("174.0.0.0", 7, "175.1.1.10")]
[InlineData("10.174.0.0", 15, "10.175.1.10")]
[InlineData("10.168.0.0", 14, "10.171.1.10")]
public void Contains_Positive(string prefixText, int length, string addressText)
{
var network = new IPNetwork(IPAddress.Parse(prefixText), length);
Assert.True(network.Contains(IPAddress.Parse(addressText)));
}
[Theory]
[InlineData("10.1.0.0", 16, "10.2.1.10")]
[InlineData("174.0.0.0", 7, "173.1.1.10")]
[InlineData("10.174.0.0", 15, "10.173.1.10")]
[InlineData("10.168.0.0", 14, "10.172.1.10")]
public void Contains_Negative(string prefixText, int length, string addressText)
{
var network = new IPNetwork(IPAddress.Parse(prefixText), length);
Assert.False(network.Contains(IPAddress.Parse(addressText)));
}
}
}

View File

@ -1,223 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace Microsoft.AspNetCore.HttpOverrides
{
public class OverrideMiddlewareHeaderTests
{
[Fact]
public async Task XForwardedForOverrideChangesRemoteIp()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.XForwardedFor
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task XForwardedForOverrideBadIpDoesntChangeRemoteIp()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.XForwardedFor
});
app.Run(context =>
{
Assert.Null(context.Connection.RemoteIpAddress);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "BAD-IP");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task XForwardedHostOverrideChangesRequestHost()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.XForwardedHost
});
app.Run(context =>
{
Assert.Equal("testhost", context.Request.Host.ToString());
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Host", "testhost");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task XForwardedProtoOverrideChangesRequestProtocol()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.XForwardedProto
});
app.Run(context =>
{
Assert.Equal("TestProtocol", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-Proto", "TestProtocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public void AllForwardsDisabledByDefault()
{
var options = new OverrideHeaderOptions();
Assert.True(options.ForwardedOptions == 0);
}
[Fact]
public async Task AllForwardsEnabledChangeRequestRemoteIpHostandProtocol()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.All
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
Assert.Equal("testhost", context.Request.Host.ToString());
Assert.Equal("Protocol", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Host", "testhost");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task AllOptionsDisabledRequestDoesntChange()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.None
});
app.Run(context =>
{
Assert.Null(context.Connection.RemoteIpAddress);
Assert.Equal("localhost", context.Request.Host.ToString());
Assert.Equal("http", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Host", "otherhost");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
[Fact]
public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
{
var assertsExecuted = false;
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseOverrideHeaders(new OverrideHeaderOptions
{
ForwardedOptions = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.Run(context =>
{
Assert.Equal("11.111.111.11", context.Connection.RemoteIpAddress.ToString());
Assert.Equal("localhost", context.Request.Host.ToString());
Assert.Equal("Protocol", context.Request.Scheme);
assertsExecuted = true;
return Task.FromResult(0);
});
});
var server = new TestServer(builder);
var req = new HttpRequestMessage(HttpMethod.Get, "");
req.Headers.Add("X-Forwarded-For", "11.111.111.11");
req.Headers.Add("X-Forwarded-Proto", "Protocol");
await server.CreateClient().SendAsync(req);
Assert.True(assertsExecuted);
}
}
}

View File

@ -4,7 +4,7 @@
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNetCore.HttpOverrides": "0.1.0-*",
"Microsoft.AspNetCore.HttpOverrides": "1.0.0-*",
"Microsoft.AspNetCore.TestHost": "1.0.0-*",
"Microsoft.Extensions.Logging.Testing": "1.0.0-*",
"xunit": "2.1.0"