mirror of https://github.com/Squidex/squidex.git
10 changed files with 20 additions and 387 deletions
@ -1,243 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Net; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Http; |
|
||||
|
|
||||
public class SsrfHelperTests |
|
||||
{ |
|
||||
[Theory] |
|
||||
[InlineData("127.0.0.1")] |
|
||||
[InlineData("::1")] |
|
||||
public void Should_block_loopback_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("10.0.0.1")] |
|
||||
[InlineData("10.255.255.255")] |
|
||||
[InlineData("172.16.0.1")] |
|
||||
[InlineData("172.31.255.255")] |
|
||||
[InlineData("192.168.0.1")] |
|
||||
[InlineData("192.168.255.255")] |
|
||||
public void Should_block_private_ipv4_ranges(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("169.254.0.1")] |
|
||||
[InlineData("169.254.169.254")] |
|
||||
public void Should_block_link_local_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("0.0.0.0")] |
|
||||
[InlineData("0.255.255.255")] |
|
||||
public void Should_block_current_network_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("224.0.0.1")] |
|
||||
[InlineData("239.255.255.255")] |
|
||||
public void Should_block_multicast_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("240.0.0.1")] |
|
||||
[InlineData("255.255.255.255")] |
|
||||
public void Should_block_reserved_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("fe80::1")] |
|
||||
[InlineData("fec0::1")] |
|
||||
public void Should_block_ipv6_link_local_and_site_local(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("fc00::1")] |
|
||||
[InlineData("fd00::1")] |
|
||||
public void Should_block_ipv6_unique_local_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("ff00::1")] |
|
||||
[InlineData("ff02::1")] |
|
||||
public void Should_block_ipv6_multicast_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("::ffff:127.0.0.1")] |
|
||||
[InlineData("::ffff:10.0.0.1")] |
|
||||
[InlineData("::ffff:172.16.0.1")] |
|
||||
[InlineData("::ffff:192.168.0.1")] |
|
||||
[InlineData("::ffff:169.254.169.254")] |
|
||||
[InlineData("::ffff:0.0.0.0")] |
|
||||
public void Should_block_ipv4_mapped_ipv6_private_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_block_ipv4_mapped_ipv6_of_blacklisted_ipv4() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("::ffff:169.254.169.254"); |
|
||||
var blacklist = new HashSet<IPAddress> { IPAddress.Parse("169.254.169.254") }; |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, blacklist); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_block_ipv4_of_blacklisted_ipv4_mapped_ipv6() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("1.2.3.4"); |
|
||||
var blacklist = new HashSet<IPAddress> { IPAddress.Parse("::ffff:1.2.3.4") }; |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, blacklist); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("::ffff:8.8.8.8")] |
|
||||
[InlineData("::ffff:1.1.1.1")] |
|
||||
public void Should_allow_ipv4_mapped_ipv6_public_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("8.8.8.8")] |
|
||||
[InlineData("1.1.1.1")] |
|
||||
[InlineData("203.0.113.1")] |
|
||||
public void Should_allow_public_ipv4_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("2001:4860:4860::8888")] |
|
||||
[InlineData("2606:4700:4700::1111")] |
|
||||
public void Should_allow_public_ipv6_addresses(string ip) |
|
||||
{ |
|
||||
var address = IPAddress.Parse(ip); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_block_custom_blacklisted_ip() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("1.2.3.4"); |
|
||||
var blacklist = new HashSet<IPAddress> { IPAddress.Parse("1.2.3.4") }; |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, blacklist); |
|
||||
|
|
||||
Assert.True(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_allow_ip_not_in_blacklist() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("8.8.8.8"); |
|
||||
var blacklist = new HashSet<IPAddress> { IPAddress.Parse("1.2.3.4") }; |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, blacklist); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_handle_null_blacklist() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("8.8.8.8"); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, null); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_handle_empty_blacklist() |
|
||||
{ |
|
||||
var address = IPAddress.Parse("8.8.8.8"); |
|
||||
var blacklist = new HashSet<IPAddress>(); |
|
||||
|
|
||||
var result = SsrfHelper.IsPrivateOrReservedIp(address, blacklist); |
|
||||
|
|
||||
Assert.False(result); |
|
||||
} |
|
||||
} |
|
||||
@ -1,131 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Net; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Http; |
|
||||
|
|
||||
public class SsrfProtectionHandlerTests |
|
||||
{ |
|
||||
private readonly SsrfCustomHandler sut; |
|
||||
private readonly SsrfOptions options = new (); |
|
||||
|
|
||||
private sealed class SsrfCustomHandler(IOptions<SsrfOptions> options) : SsrfProtectionHandler(options) |
|
||||
{ |
|
||||
public new async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, |
|
||||
CancellationToken cancellationToken) |
|
||||
{ |
|
||||
return await base.SendAsync(request, cancellationToken); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private sealed class TestHttpMessageHandler : HttpMessageHandler |
|
||||
{ |
|
||||
protected override Task<HttpResponseMessage> SendAsync( |
|
||||
HttpRequestMessage request, |
|
||||
CancellationToken cancellationToken) |
|
||||
{ |
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public SsrfProtectionHandlerTests() |
|
||||
{ |
|
||||
sut = new SsrfCustomHandler(Options.Create(options)) |
|
||||
{ |
|
||||
InnerHandler = new TestHttpMessageHandler(), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("http://example.com")] |
|
||||
[InlineData("https://example.com")] |
|
||||
public async Task Should_allow_http_and_https_schemes(string url) |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url); |
|
||||
|
|
||||
await sut.SendAsync(request, CancellationToken.None); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("ftp://example.com")] |
|
||||
[InlineData("file:///etc/passwd")] |
|
||||
public async Task Should_block_non_http_schemes(string url) |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url); |
|
||||
|
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => |
|
||||
sut.SendAsync(request, CancellationToken.None)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_throw_exception_if_request_uri_is_null() |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, (Uri?)null); |
|
||||
|
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => |
|
||||
sut.SendAsync(request, CancellationToken.None)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_block_request_to_localhost() |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost"); |
|
||||
|
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => |
|
||||
sut.SendAsync(request, CancellationToken.None)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_block_request_to_loopback_ip() |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1"); |
|
||||
|
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => |
|
||||
sut.SendAsync(request, CancellationToken.None)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_not_block_request_to_localhost_if_whitelisted() |
|
||||
{ |
|
||||
options.WhitelistedHosts.Add("localhost"); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost"); |
|
||||
|
|
||||
await sut.SendAsync(request, CancellationToken.None); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_not_block_request_to_localhost_if_all_hosts_are_whitelisted() |
|
||||
{ |
|
||||
options.WhitelistedHosts.Add("*"); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost"); |
|
||||
|
|
||||
await sut.SendAsync(request, CancellationToken.None); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_allow_custom_scheme_when_configured() |
|
||||
{ |
|
||||
options.AllowedSchemes.Add("custom"); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "custom://example.com"); |
|
||||
|
|
||||
await sut.SendAsync(request, CancellationToken.None); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_throw_exception_on_dns_resolution_failure() |
|
||||
{ |
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://invalid.domain.that.does.not.exist.local"); |
|
||||
|
|
||||
await Assert.ThrowsAsync<HttpRequestException>(() => |
|
||||
sut.SendAsync(request, CancellationToken.None)); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue