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