mirror of
https://github.com/Athou/commafeed.git
synced 2026-09-20 19:51:09 +00:00
more SSRF protection regarding IPv6
This commit is contained in:
@@ -7,6 +7,8 @@ import com.google.common.net.HttpHeaders;
|
||||
import inet.ipaddr.IPAddress;
|
||||
import inet.ipaddr.IPAddressNetwork;
|
||||
import inet.ipaddr.IPAddressString;
|
||||
import inet.ipaddr.ipv4.IPv4Address;
|
||||
import inet.ipaddr.ipv6.IPv6Address;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -137,8 +139,38 @@ public class HttpClientFactory {
|
||||
}
|
||||
|
||||
private static boolean isLocalAddress(InetAddress address) {
|
||||
IPAddress ip = new IPAddressNetwork.IPAddressGenerator().from(address);
|
||||
return ip.isLocal() || ip.isLoopback() || ip.isMulticast() || CGNAT_RANGE.contains(ip);
|
||||
return isLocalAddress(new IPAddressNetwork.IPAddressGenerator().from(address));
|
||||
}
|
||||
|
||||
private static boolean isLocalAddress(IPAddress ip) {
|
||||
if (ip.isLocal() || ip.isLoopback() || ip.isMulticast() || CGNAT_RANGE.contains(ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ip.isIPv6()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IPv6 transition mechanisms embed an IPv4 address that must be validated too, otherwise
|
||||
// they could be used to smuggle a blocked IPv4 target past the IPv6-only checks above
|
||||
IPv6Address ipv6 = ip.toIPv6();
|
||||
if (ipv6.isIPv4Mapped() || ipv6.isIPv4Compatible() || ipv6.isWellKnownIPv4Translatable()) {
|
||||
// IPv4-mapped (::ffff:x.x.x.x), IPv4-compatible (::x.x.x.x) and NAT64
|
||||
// (64:ff9b::/96, RFC 6052) addresses all embed the IPv4 address in the lowest 32 bits
|
||||
return isLocalAddress(ipv6.getEmbeddedIPv4Address());
|
||||
}
|
||||
if (ipv6.is6To4()) {
|
||||
// 6to4 (2002::/16, RFC 3056) embeds the IPv4 address in bits 16-47
|
||||
return isLocalAddress(ipv6.get6To4IPv4Address());
|
||||
}
|
||||
if (ipv6.isTeredo()) {
|
||||
// Teredo (2001::/32, RFC 4380) embeds the IPv4 address in the lowest 32 bits,
|
||||
// obfuscated with a bitwise complement
|
||||
IPv4Address obfuscated = ipv6.getEmbeddedIPv4Address();
|
||||
return isLocalAddress(new IPv4Address(~obfuscated.intValue()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||
|
||||
@@ -547,5 +547,47 @@ class HttpGetterTest {
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class, () -> getter.get("http://[fd00:dead:beef::50]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nat64() {
|
||||
// 64:ff9b::/96 embeds the IPv4 address in the lower 32 bits (RFC 6052)
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[64:ff9b::a9fe:a9fe]")); // 169.254.169.254
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[64:ff9b::a00:1]")); // 10.0.0.1
|
||||
}
|
||||
|
||||
@Test
|
||||
void sixToFour() {
|
||||
// 2002::/16 embeds the IPv4 address in bits 16-47 (RFC 3056)
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[2002:a9fe:a9fe::]")); // 169.254.169.254
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[2002:a00:1::]")); // 10.0.0.1
|
||||
}
|
||||
|
||||
@Test
|
||||
void teredo() {
|
||||
// 2001:0000::/32 embeds the obfuscated (bitwise NOT) IPv4 address in the lower 32
|
||||
// bits (RFC 4380)
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[2001:0:4136:e378:8000:63bf:f5ff:fffe]")); // 10.0.0.1
|
||||
}
|
||||
|
||||
@Test
|
||||
void ipv4Compatible() {
|
||||
// deprecated ::/96 IPv4-compatible addresses embed the IPv4 address in the lower 32
|
||||
// bits
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class,
|
||||
() -> getter.get("http://[::a9fe:a9fe]")); // 169.254.169.254
|
||||
Assertions.assertThrows(
|
||||
UnknownHostException.class, () -> getter.get("http://[::a00:1]")); // 10.0.0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user