Описание
PraisonAI: Webhook SSRF via DNS fail-open in JobSubmitRequest.validate_webhook_url() — bypass of CVE-2026-40114
Summary
praisonai/jobs/models.py::JobSubmitRequest.validate_webhook_url() validates webhook
URLs by resolving the hostname and checking whether the IP is private. When DNS
resolution fails (socket.gaierror), the validator silently passes the URL via
except socket.gaierror: pass. Additionally, even when DNS succeeds at validation time,
the webhook is fired much later by JobExecutor._send_webhook(), which calls
httpx.AsyncClient().post(job.webhook_url) — performing a fresh, independent DNS
lookup at execution time. Together, these flaws create a TOCTOU SSRF window.
An attacker can:
- Submit a job with
webhook_urlpointing to a hostname that currently does not resolve (NXDOMAIN) → validation passes (gaierror→pass) - Update DNS to point that hostname to
127.0.0.1or another private IP - When the job completes,
_send_webhook()resolves the hostname fresh → POST sent to the internal IP
Details
Flaw 1 — Fail-open on DNS error (jobs/models.py lines 58-66):
When socket.gethostbyname(hostname) raises socket.gaierror (NXDOMAIN, timeout,
network error during validation), execution flows to pass and the URL is accepted.
Flaw 2 — Fresh DNS at execution time (jobs/executor.py lines 376-406):
httpx.AsyncClient creates a new connection per call. DNS is resolved at execution time,
completely independent of the validation-time resolution. The gap between submission
and execution can be minutes to hours (depending on job queue depth and timeout settings).
Combined TOCTOU window:
Relation to CVE-2026-40114 / GHSA-8frj-8q3m-xhgm: That CVE covered "no URL
validation at all" on the webhook_url parameter, patched in v4.5.126 by adding
validate_webhook_url() to jobs/models.py. This finding targets the validation code
itself — the except socket.gaierror: pass fail-open introduced in that patch.
CVE-2026-40114: no validation. This bypass: validation present but fail-open on DNS error.
PoC
Requirements: A domain you control with configurable DNS TTL, access to the jobs API
Step 1 — Confirm fail-open behaviour (local code verification):
Expected: Webhook accepted: http://rebind.attacker.com/callback
Step 2 — Confirm fresh DNS at execution time:
Step 3 — Full attack scenario:
Immediate variant (no DNS timing required):
If DNS resolution fails transiently (rate limit, network blip, temporary outage)
during validation, the webhook is accepted unconditionally even for a URL that would
normally resolve to a private IP. No attacker control over DNS timing is required —
the attacker simply retries submission during moments when their DNS server is unreachable
(e.g., their DNS server is down, causing gaierror).
Impact
What kind of vulnerability: Server-Side Request Forgery via TOCTOU DNS rebinding and validation fail-open.
Who is impacted: Any deployment exposing the PraisonAI Jobs API (POST /jobs) to
external or lower-trusted callers. This includes:
- Multi-tenant deployments where workspace members submit jobs
- API integrations (n8n, Zapier-style workflows) that provide
webhook_urlfields
Post-exploit capabilities:
- HTTP POST to any internal service with JSON payload (job result data)
- If an internal service interprets the POST body as commands (Jenkins webhook, Consul KV, etc.), this achieves code execution on internal infrastructure
- Exfiltration of job results (which may include agent reasoning, data retrieved during the task, discovered credentials) to an attacker-controlled endpoint
Fix 2 — Re-validate at execution time (jobs/executor.py before _send_webhook):
Пакеты
PraisonAI
< 4.6.58
4.6.58
Связанные уязвимости
PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.