diff --git a/agent_api/src/main/java/dev/aikido/agent_api/helpers/net/NormalizeHostname.java b/agent_api/src/main/java/dev/aikido/agent_api/helpers/net/NormalizeHostname.java new file mode 100644 index 00000000..c321e212 --- /dev/null +++ b/agent_api/src/main/java/dev/aikido/agent_api/helpers/net/NormalizeHostname.java @@ -0,0 +1,25 @@ +package dev.aikido.agent_api.helpers.net; + +import java.net.IDN; + +public final class NormalizeHostname { + private NormalizeHostname() {} + + /** + * Canonicalizes a hostname for consistent comparison: lowercases, strips a + * trailing dot (FQDN form returned by some DNS resolvers), and converts + * Punycode labels to Unicode via IDN. + */ + public static String normalize(String hostname) { + if (hostname == null || hostname.isEmpty()) { + return hostname; + } + String lower = hostname.toLowerCase(); + String noDot = lower.endsWith(".") ? lower.substring(0, lower.length() - 1) : lower; + try { + return IDN.toUnicode(noDot); + } catch (Exception e) { + return noDot; + } + } +} diff --git a/agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/imds/TrustedHosts.java b/agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/imds/TrustedHosts.java index f0e36337..c8e7c45a 100644 --- a/agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/imds/TrustedHosts.java +++ b/agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/imds/TrustedHosts.java @@ -1,8 +1,7 @@ package dev.aikido.agent_api.vulnerabilities.ssrf.imds; +import dev.aikido.agent_api.helpers.net.NormalizeHostname; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; public final class TrustedHosts { private TrustedHosts() {} @@ -13,6 +12,6 @@ private TrustedHosts() {} /** Checks if this hostname is trusted */ public static boolean isTrustedHostname(String hostname) { - return Arrays.asList(trustedHosts).contains(hostname); + return Arrays.asList(trustedHosts).contains(NormalizeHostname.normalize(hostname)); } } diff --git a/agent_api/src/test/java/helpers/net/NormalizeHostnameTest.java b/agent_api/src/test/java/helpers/net/NormalizeHostnameTest.java new file mode 100644 index 00000000..54d73d57 --- /dev/null +++ b/agent_api/src/test/java/helpers/net/NormalizeHostnameTest.java @@ -0,0 +1,40 @@ +package helpers.net; + +import dev.aikido.agent_api.helpers.net.NormalizeHostname; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class NormalizeHostnameTest { + + @Test + void testNullAndEmpty() { + assertNull(NormalizeHostname.normalize(null)); + assertEquals("", NormalizeHostname.normalize("")); + } + + @Test + void testLowercases() { + assertEquals("example.com", NormalizeHostname.normalize("EXAMPLE.COM")); + assertEquals("metadata.google.internal", NormalizeHostname.normalize("METADATA.GOOGLE.INTERNAL")); + } + + @Test + void testStripsTrailingDot() { + assertEquals("example.com", NormalizeHostname.normalize("example.com.")); + assertEquals("metadata.google.internal", NormalizeHostname.normalize("metadata.google.internal.")); + assertEquals("metadata.goog", NormalizeHostname.normalize("metadata.goog.")); + } + + @Test + void testStripsTrailingDotAndLowercases() { + assertEquals("metadata.google.internal", NormalizeHostname.normalize("METADATA.GOOGLE.INTERNAL.")); + } + + @Test + void testPlainHostnameUnchanged() { + assertEquals("example.com", NormalizeHostname.normalize("example.com")); + assertEquals("localhost", NormalizeHostname.normalize("localhost")); + } +} diff --git a/agent_api/src/test/java/vulnerabilities/ssrf/ResolverTest.java b/agent_api/src/test/java/vulnerabilities/ssrf/ResolverTest.java index 363ae06b..a9715c22 100644 --- a/agent_api/src/test/java/vulnerabilities/ssrf/ResolverTest.java +++ b/agent_api/src/test/java/vulnerabilities/ssrf/ResolverTest.java @@ -85,4 +85,13 @@ void testResolvesToImdsIp_WithMultipleResolvedIps_OnlyTrustedHostname() { assertNull(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal")); } + + @Test + void testResolvesToImdsIp_TrustedHostnameWithTrailingDot() { + Set resolvedIps = new HashSet<>(); + resolvedIps.add("169.254.169.254"); // IMDS IP + + assertNull(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal.")); + assertNull(Resolver.resolvesToImdsIp(resolvedIps, "metadata.goog.")); + } }