GHSA-6h9p-93hq-q7h6: PraisonAI: SpiderTools redirect-target SSRF protection bypass
SpiderTools redirect-target SSRF protection bypassSummary`SpiderTools.scrape_page()` validates the initial URL and rejects direct
loopback, private, link-local, metadata, and internal hostnames. It then calls
`requests.Session.get()` without disabling automatic redirects or validating
redirect `Location` targets.Requests follows redirects by default for GET requests. A safe-looking public
URL can therefore pass `_validate_url()`, redirect to a blocked target such as
`127.0.0.1` or `169.254.169.254`, and have the redirected response body parsed
and returned by `scrape_page()`.The same sink is used by `extract_links()`, `crawl()`, and `extract_text()`
through their calls to `scrape_page()`.Affected componentTested affected:`v3.9.24` / `d08d98ca``v3.9.26` / `62472a23``v4.6.56` / `d3c4a2af``v4.6.57` / `e90d92231853161ad931f3498da57651a9f8b528`current main `2f9677abb2ea68eab864ee8b6a828fd0141612e1`No patched version is known at report time.Root causeCurrent main validates only the caller-supplied URL:The fetch then uses Requests defaults:Because `allow_redirects=False` is not set, Requests follows a 3xx redirect to a
new destination that has not been checked by `_validate_url()` or
`_host_is_blocked()`.Proof of vulnerabilityThe PoV below is local-only and does not contact external infrastructure. It
starts a loopback-only internal service and a local redirector. During
PraisonAI's initial host validation, `attacker.test` is made to look like a
public address. During the actual HTTP request, it routes to the local
redirector, which returns `302 Location: http://127.0.0.1:<port>/secret`.Full PoV:Run:Observed on current main:The direct control proves direct loopback is blocked. The redirect result proves
the same blocked destination is reached through a public-looking initial URL.
The no-redirect control proves that disabling automatic redirects prevents the
internal request while still receiving the external redirect response.Why this is not intended behaviorThe Spider Tools documentation says `scrape_page`, `extract_links`, `crawl`, and
`extract_text` refuse dangerous URLs before network requests. The documented
blocked classes include loopback, private/reserved IPs, link-local/cloud
metadata endpoints, internal TLDs, non-HTTP(S) schemes, and parser-smuggling
forms. The same page states the validation is always on for bundled spider tools
and does not require `enable_security()`.The current code also documents `_validate_url()` as URL validation "to prevent
SSRF attacks." A redirect to a loopback target bypasses that documented
protection.ImpactAn attacker who can influence a URL passed to `scrape_page()`,
`extract_links()`, `crawl()`, or `extract_text()` can cause the PraisonAI process
to request destinations that SpiderTools is designed to block.Potential impact includes:reading loopback-only HTTP services;probing or reading private network services reachable from the PraisonAI host;reading link-local/cloud metadata endpoints if reachable in the deployment
environment.The PoV demonstrates returned response-body disclosure from a loopback-only
service. This report does not claim arbitrary code execution or live cloud
credential theft without deployment-specific evidence.SeveritySuggested default severity: Moderate.High severity may be appropriate for deployments where untrusted users can
directly invoke SpiderTools through a network-facing agent, bot, API, or MCP
service and sensitive internal or metadata services are reachable.Suggested fixDisable automatic redirects in `scrape_page()`:If redirects should remain supported, follow them manually and validate every
`Location` target before each hop using the same SSRF guard:require `http` or `https`;resolve and validate every redirect hostname;reject loopback, private, link-local, reserved, multicast, unspecified,
internal, and metadata destinations;cap redirect count;apply the same safe fetch path to `scrape_page()`, `extract_links()`,
`crawl()`, and `extract_text()`.Regression tests should cover direct loopback rejection, public-to-loopback
redirect rejection, public-to-public redirects if supported, and all
`scrape_page()` callers.
CVSS v3.1: 6.5