From 32e4315c8374cbff234168bbd7d4510f6052bc19 Mon Sep 17 00:00:00 2001
From: Valera V Harseko <vharseko@3a-systems.ru>
Date: Wed, 21 Oct 2026 08:51:52 +0000
Subject: [PATCH] CVE-2026-102648 DSMLv2 gateway: incomplete internal-address check allows SSRF when anyURI dereferencing is enabled
---
opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java | 99 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 99 insertions(+), 0 deletions(-)
diff --git a/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java b/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
index d42c479..bfcda39 100644
--- a/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
+++ b/opendj-dsml-servlet/src/test/java/org/opends/dsml/protocol/ByteStringUtilityTestCase.java
@@ -46,6 +46,7 @@
ByteStringUtility.setDereferenceUri(false);
ByteStringUtility.setAllowedUriSchemes("http,https");
ByteStringUtility.setMaxUriContentLength(10L * 1024 * 1024);
+ ByteStringUtility.setNat64Prefixes("");
}
@Test
@@ -103,6 +104,13 @@
{ "http://[fe80::1]/" },
{ "http://[fc00::1]/" },
{ "http://[fd12:3456::1]/" },
+ // IPv6 transition addresses embedding an internal IPv4 (GHSA-97x5-8pm7-xfp7).
+ { "http://[64:ff9b::c0a8:0101]/" }, // NAT64 well-known -> 192.168.1.1
+ { "http://[64:ff9b::0a00:0001]/" }, // NAT64 well-known -> 10.0.0.1
+ { "http://[64:ff9b:1::7f00:1]/" }, // NAT64 local-use (RFC 8215) -> 127.0.0.1
+ { "http://[64:ff9b:1:0:c0:a801:100:0]/" }, // NAT64 local-use, /64 layout -> 192.168.1.1
+ { "http://[2002:0a00:0001::]/" }, // 6to4 -> 10.0.0.1
+ { "http://[2001:0000::3f57:fefe]/" }, // Teredo -> 192.168.1.1 (obfuscated)
};
}
@@ -143,6 +151,10 @@
// 192.0.0.0/24 boundaries
{ "192.0.0.1", true },
{ "192.0.1.1", false },
+ // 192.88.99.0/24 (deprecated 6to4 relay anycast, RFC 7526) boundaries
+ { "192.88.98.255", false },
+ { "192.88.99.1", true },
+ { "192.88.100.0", false },
// 198.18.0.0/15 boundaries
{ "198.17.255.255", false },
{ "198.18.0.0", true },
@@ -156,6 +168,33 @@
{ "fc00::1", true },
{ "fdff::1", true },
{ "fe00::1", false },
+ // IPv6 transition addresses embedding an internal IPv4 (GHSA-97x5-8pm7-xfp7).
+ { "64:ff9b::c0a8:0101", true }, // NAT64 well-known -> 192.168.1.1
+ { "64:ff9b::0a00:0001", true }, // NAT64 well-known -> 10.0.0.1
+ { "64:ff9b::7f00:0001", true }, // NAT64 well-known -> 127.0.0.1
+ { "64:ff9b::0808:0808", false }, // NAT64 well-known -> 8.8.8.8 (public)
+ // The NAT64 local-use prefix 64:ff9b:1::/48 (RFC 8215) is blocked outright,
+ // whichever RFC 6052 sub-prefix layout the operator picked inside it.
+ { "64:ff9b:1::7f00:1", true }, // /96 layout -> 127.0.0.1
+ { "64:ff9b:1::a00:1", true }, // /96 layout -> 10.0.0.1
+ { "64:ff9b:1:abcd::c0a8:101", true }, // /96 layout in a subnet of the /48 -> 192.168.1.1
+ { "64:ff9b:1:0:c0:a801:100:0", true }, // /64 layout -> 192.168.1.1
+ { "64:ff9b:1:0:a:102:300:0", true }, // /64 layout -> 10.1.2.3
+ { "64:ff9b:1:7f00:0:100::", true }, // /48 layout -> 127.0.0.1
+ { "64:ff9b:1::808:808", true }, // 8.8.8.8: public, but not reachable through this prefix
+ { "64:ff9b:1:ffff:ffff:ffff:ffff:ffff", true }, // no layout matches, still in the /48
+ // 64:ff9b:1::/48 boundaries.
+ { "64:ff9b:0:1::7f00:1", false }, // 64:ff9b:0:1, not the local-use prefix
+ { "64:ff9b:2::7f00:1", false }, // outside 64:ff9b::/96 and 64:ff9b:1::/48
+ { "64:ff9c:1::7f00:1", false }, // 64:ff9c:1, not the local-use prefix
+ { "2002:0a00:0001::", true }, // 6to4 -> 10.0.0.1
+ { "2002:c0a8:0101::", true }, // 6to4 -> 192.168.1.1
+ { "2002:c058:6301::", true }, // 6to4 -> 192.88.99.1 (blocked IPv4 via transition)
+ { "2002:0808:0808::", false }, // 6to4 -> 8.8.8.8 (public)
+ { "2001:0000::3f57:fefe", true }, // Teredo -> 192.168.1.1 (obfuscated)
+ { "2001:0000::f7f7:f7f7", false },// Teredo -> 8.8.8.8 (public, obfuscated)
+ // A regular global-unicast address under 2001::/16 must not be misread as Teredo/6to4.
+ { "2001:4860:4860::8844", false },
};
}
@@ -168,6 +207,66 @@
"isInternalAddress(" + literal + ")");
}
+ @Test
+ public void testNat64NetworkSpecificPrefixIsNotBlockedByDefault() throws Exception
+ {
+ // An NSP is ordinary operator address space: without configuration it
+ // cannot be told apart from a global address, so it must stay allowed.
+ assertFalse(isInternal("2001:db8:122:344::c0a8:101"));
+ }
+
+ @Test
+ public void testConfiguredNat64PrefixFiltersEmbeddedIPv4() throws Exception
+ {
+ ByteStringUtility.setNat64Prefixes("2001:db8:122:344::/96");
+ assertTrue(isInternal("2001:db8:122:344::c0a8:101")); // -> 192.168.1.1
+ assertTrue(isInternal("2001:db8:122:344::7f00:1")); // -> 127.0.0.1
+ assertFalse(isInternal("2001:db8:122:344::808:808")); // -> 8.8.8.8 (public)
+ assertFalse(isInternal("2001:db8:122:345::c0a8:101")); // outside the prefix
+ }
+
+ @Test
+ public void testConfiguredNat64PrefixWith64BitLength() throws Exception
+ {
+ // RFC 6052 /64 format: the IPv4 address sits in bytes 9..12, after the "u" byte.
+ ByteStringUtility.setNat64Prefixes("2001:db8:122:344::/64");
+ assertTrue(isInternal("2001:db8:122:344:a:0:100:0")); // -> 10.0.0.1
+ assertFalse(isInternal("2001:db8:122:344:8:808:800:0"));// -> 8.8.8.8 (public)
+ }
+
+ @Test
+ public void testMultipleNat64PrefixesAreSupported() throws Exception
+ {
+ ByteStringUtility.setNat64Prefixes(" 2001:db8:122:344::/96 , 2001:db8:9::/96 ");
+ assertTrue(isInternal("2001:db8:122:344::a00:1")); // -> 10.0.0.1
+ assertTrue(isInternal("2001:db8:9::a9fe:a9fe")); // -> 169.254.169.254
+ assertFalse(isInternal("2001:db8:9::101:101")); // -> 1.1.1.1 (public)
+ }
+
+ @DataProvider
+ public Object[][] invalidNat64Prefixes()
+ {
+ return new Object[][] {
+ { "2001:db8::" }, // no /length
+ { "2001:db8::/95" }, // not an RFC 6052 prefix length
+ { "2001:db8::/abc" }, // length is not a number
+ { "not-an-address/96" }, // not an IP literal (must not trigger DNS)
+ { "192.0.2.0/96" }, // IPv4, not IPv6
+ };
+ }
+
+ @Test(dataProvider = "invalidNat64Prefixes",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidNat64PrefixesAreRejected(String prefixes)
+ {
+ ByteStringUtility.setNat64Prefixes(prefixes);
+ }
+
+ private boolean isInternal(String literal) throws Exception
+ {
+ return ByteStringUtility.isInternalAddress(InetAddress.getByName(literal));
+ }
+
@DataProvider
public Object[][] redirectCodes()
{
--
Gitblit v1.10.0