Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions aikido_zen/vulnerabilities/ssrf/get_hostname_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ 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
# 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
21 changes: 21 additions & 0 deletions aikido_zen/vulnerabilities/ssrf/get_hostname_options_test.py
Original file line number Diff line number Diff line change
@@ -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"]