From fe948430f5f919affea0fae0c60b3ed55b8cc934 Mon Sep 17 00:00:00 2001 From: MaiDormo Date: Fri, 21 Aug 2026 23:43:19 +0200 Subject: [PATCH 1/2] Fix SSRF scan aborting on malformed punycode hostnames get_hostname_options() decoded xn-- hostnames with IDNA without guarding the call. A malformed punycode label (e.g. xn--a.attacker.com) raised UnicodeError, which propagated through find_hostname_in_context() and was swallowed by the catch-all in run_vulnerability_scan(), so the SSRF scan failed open. Fall back to the raw hostname when decoding fails. --- .../ssrf/get_hostname_options.py | 11 ++++++++-- .../ssrf/get_hostname_options_test.py | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 aikido_zen/vulnerabilities/ssrf/get_hostname_options_test.py diff --git a/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py b/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py index e9296700d..795a485ab 100644 --- a/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py +++ b/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py @@ -10,8 +10,15 @@ def get_hostname_options(raw_hostname: str) -> List[str]: # Add a case when the hostname is in punycode (like xn--pp-oia.aikido.dev) if "xn--" in raw_hostname: - hostname_decoded = raw_hostname.encode("ascii", errors="").decode("idna") - options_urls.append(try_parse_url(f"http://{hostname_decoded}")) + try: + hostname_decoded = raw_hostname.encode("ascii", errors="").decode("idna") + except UnicodeError: + # Malformed punycode (e.g. xn--a.attacker.com): keep the raw form only, + # so the SSRF scan still runs against the requested hostname instead + # of aborting with an exception. + hostname_decoded = None + if hostname_decoded: + options_urls.append(try_parse_url(f"http://{hostname_decoded}")) # Map to url.hostname options = [] diff --git a/aikido_zen/vulnerabilities/ssrf/get_hostname_options_test.py b/aikido_zen/vulnerabilities/ssrf/get_hostname_options_test.py new file mode 100644 index 000000000..4a704b3cc --- /dev/null +++ b/aikido_zen/vulnerabilities/ssrf/get_hostname_options_test.py @@ -0,0 +1,21 @@ +from .get_hostname_options import get_hostname_options + + +def test_plain_hostname(): + assert get_hostname_options("example.com") == ["example.com"] + + +def test_valid_punycode_adds_decoded_form(): + options = get_hostname_options("xn--r8jz45g.com") + assert "xn--r8jz45g.com" in options + assert len(options) > 1 # decoded variant added + + +def test_malformed_punycode_does_not_raise(): + # Invalid punycode labels (e.g. xn--a) previously raised UnicodeError + # and aborted the whole SSRF scan. The raw hostname must still be returned. + assert get_hostname_options("xn--a.com") == ["xn--a.com"] + + +def test_malformed_punycode_subdomain_does_not_raise(): + assert get_hostname_options("xn--a.attacker.com") == ["xn--a.attacker.com"] From c98b67e85e821ff8f0164ca712e88ab084c2a682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Sat, 22 Aug 2026 14:48:45 +0200 Subject: [PATCH 2/2] Fix tests by skipping identical hostnames Only affects Python 3.8 --- aikido_zen/vulnerabilities/ssrf/get_hostname_options.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py b/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py index 795a485ab..2849484b2 100644 --- a/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py +++ b/aikido_zen/vulnerabilities/ssrf/get_hostname_options.py @@ -20,9 +20,10 @@ def get_hostname_options(raw_hostname: str) -> List[str]: if hostname_decoded: options_urls.append(try_parse_url(f"http://{hostname_decoded}")) - # Map to url.hostname + # Map to url.hostname, deduplicating (bracketed and unbracketed + # variants can resolve to the same hostname depending on Python version) options = [] for options_url in options_urls: - if options_url and options_url.hostname: + if options_url and options_url.hostname and options_url.hostname not in options: options.append(options_url.hostname) return options