GHSA-vg6p-v9vm-6fgj: SSRF
The webcrawl tool performs its SSRF check only on the initial URL: it resolves the hostname once with socket.gethostbyname and rejects private/loopback/link-local results. It then passes the URL to a fetcher that uses httpx.Client(followredirects=True) - or urllib.request.urlopen when httpx is absent, which also follows redirects - and re-resolves the hostname at connect time, with no further validation. This validate-here/fetch-there gap is bypassable two independent ways: HTTP redirects and DNS rebinding.
Affected code: src/praisonai-agents/praisonaiagents/tools/webcrawltools.py - Single-shot validation (lines 229-238): if os.environ.get("ALLOWLOCALCRAWL") != "true": ipstr = socket.gethostbyname(hostname) # resolved ONCE, at validation time ip = ipaddress.ipaddress(ipstr) if ip.isloopback or ip.isprivate or ip.islinklocal or ip.ismulticast or ip.isunspecified: continue # rejected urllist.append(u) - Vulnerable fetch (crawlwithhttpx, lines 142 / 149): follows redirects, re-resolves DNS, no re-check: with httpx.Client(followredirects=True, timeout=30.0) as client: response = client.get(url) # fallback: urllib.request.urlopen(url, timeout=30) (also follows redirects by default) - webcrawl / crawlweb are registered tools (tools/init.py:156-157); httpx is the default fallback provider (dispatch at webcrawltools.py:269).
The two bypasses: 1) Redirect: validation approves an attacker domain resolving to a public IP; attacker server replies 302 Location: http://169.254.169.254/... (or any internal host); the fetcher follows it unchecked. 2) DNS rebinding (TOCTOU): validator's gethostbyname and fetcher's connect-time resolution are independent; a low-TTL attacker domain answers public to the validator and private/loopback to fetch.
Impact: An agent with webcrawl - driven by direct input or indirect prompt injection - can be made to read internal-only HTTP services and cloud instance-metadata endpoints (e.g. IAM credentials), with the response body returned in the tool output. Scope is Changed because the request pivots into the internal network.
Proof of concept: A PoC drives the real webcrawl() (httpx absent -> genuine urllib fallback). It runs a loopback "internal metadata" service and a loopback attacker redirector, substituting DNS only to stand in for "attacker owns a public domain" / offline routing - the redirect-following and connect-time re-resolution are the repo's own behavior. Observed: CONTROL: webcrawl("http://127.0.0.1:.../meta-data/") -> blocked (validator works) PoC 1A (redirect): attacker.example approved (public); 302 -> loopback metadata -> result.content leaks {"AccessKeyId":"ASIAFAKESTOLENCREDENTIAL..."} PoC 1B (rebinding): gethostbyname(rebind.example)->public (allowed); connect->127.0.0.1 -> same secret leaked The control proves the validator blocks a direct loopback request, so the bypasses are genuine.
Remediation: Resolve the hostname once, validate that IP, and connect to that exact validated IP (pin it) rather than re-resolving. Disable redirect following (followredirects=False; for urllib use a redirect handler that re-validates), or re-validate every redirect hop's resolved IP. Apply the deny check to both the validator and the actual socket target. filetools.py:364 already uses followredirects=False and is the correct pattern to propagate.
Distinct from prior advisories: The accepted SSRF advisories concern host-string parsing in different code — alternate loopback encodings in spidertools (GHSA-5c6w-wwfq-7qqm) and the CLI @url feature (GHSA-5cxw-77wg-jrf3). This is in the webcrawl tool, which neither advisory names, and the mechanisms (redirect-following and DNS rebinding) differ categorically from host-string encoding; the spidertools hostisblocked hardening does not apply to this tool.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/praisonaiagentsto a version that resolves this vulnerability.Fixed in 1.6.58 - Configuration
Disable HTTP redirect following for the SSRF validation/fetch path in web_crawl_tools.py (set follow_redirects=False).
praisonai-agents tools (web_crawl_tools.py) follow_redirects = false - Configuration
Resolve the hostname once during validation (e.g., ip_str = socket.gethostbyname(hostname); ip = ipaddress.ip_address(ip_str)), validate that IP, then connect/fetch to that exact validated IP (pin the IP) rather than re-resolving at connect time (avoid validate-here/fetch-there TOCTOU).
praisonai-agents tools (web_crawl_tools.py) URL validation vs fetcher hostname resolution = pinned-to-validated-IP - Configuration
Enforce the existing local/internal block in web_crawl_tools.py: if ip.is_loopback or ip.is_private or ip.is_link_local or ip.is_multicast or ip.is_unspecified, block unless os.environ.get('ALLOW_LOCAL_CRAWL') == 'true'. Ensure this deny check is applied for the pinned validated IP (not only the initial URL string).
praisonai-agents tools (web_crawl_tools.py) ALLOW_LOCAL_CRAWL gate = true required - Configuration
When using httpx in web_crawl_tools.py, ensure httpx.Client is not created with follow_redirects=True for SSRF-sensitive fetching; use follow_redirects=False to prevent redirect hop re-validation bypasses (redirects can otherwise pivot to loopback/internal).
praisonai-agents tools (web_crawl_tools.py) httpx.Client follow_redirects = false
Event History
Frequently Asked Questions
Who is exposed to this issue?
Deployments that use the web_crawl tool to retrieve URLs that an attacker can influence are exposed. The attacker does not need authentication or user interaction, but must be able to cause the tool to crawl a chosen initial URL.
Does the default local-address check prevent exploitation?
No. When ALLOW_LOCAL_CRAWL is not set to "true", the tool checks the hostname only once before fetching. An attacker can bypass that check through an HTTP redirect to an internal address or through DNS rebinding after the initial validation.
What internal targets could be reached?
The initial check is intended to reject loopback, private, link-local, multicast, and unspecified IP addresses. Because redirected destinations and DNS results at connection time are not revalidated, those address categories may be reachable after a passing initial URL is accepted.
What should be done if an immediate code update is not possible?
Do not allow untrusted parties to supply URLs to the web_crawl tool. In particular, prevent crawling attacker-controlled domains, since they can serve redirects or alter DNS responses after the initial hostname lookup.