In the open-webui project, a parsing difference between the urlparse and requests libraries led to an SSRF bypass vulnerability.
In the current project, URL validation is performed using the function validate_url.
<img width="1323" height="1145" alt="QQ20260322-202854-22-1" src="https://github.com/user-attachments/assets/896d19f2-c7c3-499a-9052-12aea756ac47" />The current checking logic uses urlparse to parse the hostname part of the URL for verification.
<img width="1122" height="429" alt="QQ20260322-203014-22-2" src="https://github.com/user-attachments/assets/653520e9-e311-4a5e-8345-a2446e217d88" />However, there are actually differences in parsing between urlparse and the library that actually sends the request. For example, in files.py, validate_url is used first for URL validation, and then requests.get is used to send the request.
<img width="1269" height="915" alt="QQ20260322-203122-22-3" src="https://github.com/user-attachments/assets/f200aa06-9190-425e-9659-1ecaf95f806b" />The core issue: urlparse() and requests disagree on which host a URL like http://127.0.0.1:6666\@1.1.1.1 points to:
urlparse() treats \ as a regular character and @ as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public)requests treats \ as a path character, connecting to 127.0.0.1 (internal)Below is a test code I wrote following the open-webui code.
from __future__ import annotations
import ipaddress
import logging
import os
import socket
import urllib.parse
import urllib.request
from typing import Optional, Sequence, Union
import requests
log = logging.getLogger(__name__)
# Same text as open_webui.constants.ERROR_MESSAGES.INVALID_URL
INVALID_URL = (
"Oops! The URL you provided is invalid. Please double-check and try again."
)
# Same semantics as open_webui.config (ENABLE_RAG_LOCAL_WEB_FETCH / WEB_FETCH_FILTER_LIST)
ENABLE_RAG_LOCAL_WEB_FETCH = (
os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH",...
0.9.5Exploitability
AV:NAC:LPR:LUI:NScope
S:CImpact
C:HI:LA:N8.5/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N