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:
1 parent
15d96b1f2a
commit
01198eaeef
1 file changed
+7
-2
@@ -191,7 +191,8 @@ def _is_text_content_type(content_type: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def get_content_from_url(request, url: str) -> str:
|
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()
|
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,
|
# re-validation would let an attacker reach private IPs (RFC1918, loopback,
|
||||||
# cloud-metadata 169.254.169.254) via a public host that redirects internally.
|
# cloud-metadata 169.254.169.254) via a public host that redirects internally.
|
||||||
try:
|
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()
|
response.raise_for_status()
|
||||||
content_type = response.headers.get('Content-Type', '')
|
content_type = response.headers.get('Content-Type', '')
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in new issue
Block a user