mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-10 04:20:44 +02:00
Close DNS-rebinding SSRF gap in get_content_from_url probe (#25775)
The web-ingest probe in get_content_from_url validated the URL at resolve time (validate_url) but then fetched with a bare requests.get, which re-resolves the hostname at connect time. An attacker-controlled name server can answer with a public IP during validation and an internal IP at connect, reaching cloud metadata / loopback / internal services (blind always; binary content-types are read back to the caller). The connection-layer guard (#24759) that closes this for the SafeWebBaseLoader path was never mounted on this probe. Route the probe through the same _SSRFSafeAdapter the loader uses, so the resolution that feeds the TCP connect is re-validated against the global-IP check. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -191,7 +191,8 @@ def _is_text_content_type(content_type: str) -> bool:
|
||||
|
||||
|
||||
async def get_content_from_url(request, url: str) -> str:
|
||||
from open_webui.retrieval.web.utils import validate_url
|
||||
from open_webui.retrieval.web.utils import validate_url, _SSRFSafeAdapter
|
||||
|
||||
|
||||
loader_config = await get_loader_config()
|
||||
|
||||
@@ -216,7 +217,11 @@ async def get_content_from_url(request, url: str) -> str:
|
||||
# re-validation would let an attacker reach private IPs (RFC1918, loopback,
|
||||
# cloud-metadata 169.254.169.254) via a public host that redirects internally.
|
||||
try:
|
||||
response = requests.get(url, stream=True, timeout=30, allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS)
|
||||
# Probe through the connect-time SSRF guard; bare requests.get re-resolves (DNS-rebinding gap).
|
||||
session = requests.Session()
|
||||
session.mount('http://', _SSRFSafeAdapter())
|
||||
session.mount('https://', _SSRFSafeAdapter())
|
||||
response = session.get(url, stream=True, timeout=30, allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS)
|
||||
response.raise_for_status()
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user