open-webui/backend/open_webui/utils
Classic298 02b2a391e9
fix: block private-IP webhook URLs to close SSRF on caller-controlled URL (#24587)
* fix: block private-IP webhook URLs to close SSRF on caller-controlled URL

post_webhook(url, ...) in utils/webhook.py forwards the URL straight to
aiohttp.ClientSession.post with no SSRF gate. The URL is caller-controlled
on two surfaces:

- User notification settings under ENABLE_USER_WEBHOOKS=true — any
  authenticated user can set the URL their notifications POST to.
- Automation notification triggers (calendar alerts, etc.).

Without a gate, the URL can target cloud metadata (169.254.169.254 /
fd00:ec2::254), localhost-bound services, RFC1918 internal hosts, or any
other private address reachable from the server process. Blind SSRF — no
response body returned to the caller — but enough to enumerate internal
services via response timing / status codes, and on cloud deployments
enough to issue requests against IMDSv1 if available.

Call validate_url() at the top of post_webhook. The function blocks
private/reserved IPs when ENABLE_RAG_LOCAL_WEB_FETCH is False (the
default), is the project's chosen SSRF gate, and is already applied to
the equivalent fetch surfaces (retrieval, image-load, OAuth profile
picture). Operators who legitimately need to webhook to private IPs
(internal monitoring, self-hosted Slack alternatives, etc.) can set
ENABLE_RAG_LOCAL_WEB_FETCH=True — same opt-out as the other gated
surfaces.

Scope intentionally limited to webhooks. The OAuth discovery and
external reranker paths cwanglab also flagged are admin-configured with
intentional private-IP defaults (reranker defaults to
http://localhost:8080/v1/rerank) and are out of scope per Rule 9 — the
admin owns the URL choice and the operator opt-out exists for them too.

Reported by cwanglab in GHSA-5x9f-85cg-w3hf (cluster canonical with six
closed siblings: g36v-23gj-j69x, 6j8f-h58v-xgmw, xpwv-52pm-p8hj,
v9gp-hv2c-9qv8, fw7w-jrw7-p3v9, x7xq-74rg-m8mf).

Co-authored-by: cwanglab <cwanglab@users.noreply.github.com>

* fix: also pass allow_redirects=False on webhook post_webhook session.post

Companion to the previous commit. validate_url() only validates the
initial URL; aiohttp's default allow_redirects=True would still follow
a 302 to a private-IP target. Same redirect-bypass class as the rh5x
cluster's five call sites, sixth call site to receive the same gate.

Co-authored-by: cwanglab <cwanglab@users.noreply.github.com>

---------

Co-authored-by: cwanglab <cwanglab@users.noreply.github.com>
2026-06-01 14:15:51 -07:00
..
access_control fix: respect access_type in shared-chat file authorization branch (#24755) 2026-05-19 22:09:56 +04:00
images refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
mcp chore: format 2026-06-01 13:56:55 -07:00
telemetry refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
actions.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
anthropic.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
asgi_middleware.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
audit.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
auth.py chore: format 2026-06-01 13:56:55 -07:00
automations.py chore: format 2026-06-01 14:10:40 -07:00
calendar.py feat: calendar 2026-04-19 19:15:05 +09:00
channels.py refac 2026-03-17 17:58:01 -05:00
chat.py fix: move bypass_system_prompt off query parameter onto request.state (#25156) 2026-05-31 14:53:53 -07:00
code_interpreter.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
embeddings.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
files.py chore: format 2026-06-01 13:56:55 -07:00
filter.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
groups.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
headers.py refac 2026-06-01 14:13:28 -07:00
logger.py chore: format 2026-06-01 13:56:55 -07:00
middleware.py chore: format 2026-06-01 13:56:55 -07:00
misc.py chore: format 2026-06-01 13:56:55 -07:00
models.py refac 2026-06-01 12:27:08 -07:00
oauth.py chore: format 2026-06-01 13:56:55 -07:00
payload.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
pdf_generator.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
plugin.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
rate_limit.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
redis.py chore: format 2026-06-01 13:56:55 -07:00
response.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
sanitize.py refac 2026-03-17 17:58:01 -05:00
security_headers.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
session_pool.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
task.py refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
tools.py chore: format 2026-06-01 13:56:55 -07:00
validate.py chore: format 2026-06-01 13:56:55 -07:00
webhook.py fix: block private-IP webhook URLs to close SSRF on caller-controlled URL (#24587) 2026-06-01 14:15:51 -07:00