praisonaiagents has an SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)
Summary
praisonaiagents/tools/spider_tools.py contains an SSRF protection bypass. The function
_host_is_blocked() validates URLs against a list of blocked IP literals and hostname
aliases, but never performs DNS resolution. Any hostname that resolves to a private or
loopback IP address — including public wildcard DNS services like 127.0.0.1.nip.io —
bypasses the protection entirely.
This has been confirmed with a live exploit: scrape_page("http://127.0.0.1.nip.io:PORT/secret")
makes an HTTP request to 127.0.0.1:PORT and returns the internal service response.
No attacker-controlled infrastructure is required.
scrape_page, extract_links, crawl, and extract_text are all registered as
LLM-callable agent tools (see tools/__init__.py lines 51-55), so any agent instructed
to fetch a user-supplied URL will trigger this path.
This is a new bypass of prior fix commit 004dcfef (GHSA-q9pw-vmhh-384g), which only
rejected IP literal encoding tricks (hex, octal, backslash). The fix was also applied to
web_crawl_tools.py (line 231: socket.gethostbyname call), but that fix was not
ported to spider_tools.py.
Details
Root cause — spider_tools.py lines 26-65:
def _host_is_blocked(hostname: str) -> bool:
host = hostname.lower().rstrip(".")
# Checks literal aliases only — never resolves
if host in ("localhost", "0.0.0.0", "::1"):
return True
if host in ("169.254.169.254", "metadata.google.internal"):
return True
if any(host.endswith(s) for s in (".local", ".internal", ".localdomain")):
return True
# Tries to parse as IP literal only
try:
return _ip_blocked(ipaddress.ip_address(host))
except ValueError:
pass
try:
return _ip_blocked(ipaddress.ip_address(socket.inet_aton(host)))
except OSError:
pass
return False #