fix: prevent redirect-based SSRF and enforce collecton write access (#24524)
* fix: prevent redirect-based SSRF in get_image_base64_from_url Cohort follow-up to PR #24491. That PR patched three call sites (SafeWebBaseLoader._scrape, get_content_from_url, load_url_image) to pass allow_redirects=False on the underlying HTTP client; this fourth call site in utils/files.py was missed. get_image_base64_from_url() is invoked from convert_url_images_to_base64 in utils/middleware.py on every /api/chat/completions request whose message content includes an image_url part. validate_url() is called on the originally-submitted URL only; the aiohttp session.get() call had no allow_redirects argument and the shared session pool does not override the aiohttp default (allow_redirects=True). An authenticated user sending a chat message with image_url pointing at an attacker host that 302-redirects to 169.254.169.254 / 127.0.0.1 / RFC1918 reached the internal target. This is the most reachable variant in the redirect cluster: no special endpoint, no admin permission, no feature flag. Apply the same one-line fix as the other three call sites: pass allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS (defaults to False). Reported by nayakchinmohan in GHSA-88jq-grjp-jx6f; consolidated under GHSA-rh5x-h6pp-cjj6. Co-authored-by: nayakchinmohan <nayakchinmohan@users.noreply.github.com> * fix: enforce collection write access on process_file endpoint Cohort follow-up toba83613ff. That commit added _validate_collection_access to process_text and process_web (the user-supplied collection_name path) but missed process_file in the same router. process_file accepts a user-supplied collection_name and writes the file's embedded content into that collection via save_docs_to_vector_db. The file_id is gated by file ownership (line 1562) but collection_name was unchecked, so an authenticated user could append content from a file they own into another user's knowledge-base collection by passing the victim's KB UUID as collection_name. Identical pattern to the process_text and process_web gaps thatba83613ffclosed. Apply the same one-line gate as the sibling endpoints: when collection_name is user-supplied (not the default file-{file.id} fallback), require write access via _validate_collection_access. The shared validator delegates to filter_accessible_collections, which already correctly handles file-* prefixes (via has_access_to_file) and KB UUIDs (via Knowledges.check_access_by_user_id) — admins bypass. Reported by tenbbughunters (Tenable) in GHSA-4g37-7p2c-38r9 (the comprehensive write-path filing covering process_text / process_file / process_web / process_youtube and the _validate_collection_access UUID root cause), and independently re-identified for the missed process_file call site by kodareef5 in GHSA-4m74-3cmc-293g. Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com> Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com> * fix: enforce collection write access on process_files_batch endpoint Cohort follow-up toba83613ffand the prior process_file fix on this branch. process_files_batch (line 2604) is the third write endpoint in the same router that accepts a user-supplied collection_name; it was covered in the same Tenable filing as process_file and was missed by the same cohort fix. The endpoint validates per-file ownership at line 2642 but does not check whether the caller has write access to the target collection_name before save_docs_to_vector_db writes into it at line 2683-2690 with add=True. Apply the same one-line gate as the sibling endpoints. Validate only when collection_name is user-supplied (truthy) so the existing fall through behavior for the None case is unchanged. Same Tenable / kodareef5 cohort as the previous commit. Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com> Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com> --------- Co-authored-by: nayakchinmohan <nayakchinmohan@users.noreply.github.com> Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com> Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com>
This commit is contained in:
co-authored by
tenbbughunters
kodareef5
nayakchinmohan
parent
841c9045d7
commit
d11e06f1b7
@@ -1567,6 +1567,8 @@ async def process_file(
|
||||
|
||||
if collection_name is None:
|
||||
collection_name = f'file-{file.id}'
|
||||
else:
|
||||
await _validate_collection_access([collection_name], user, access_type='write')
|
||||
|
||||
if form_data.content:
|
||||
# Update the content in the file
|
||||
@@ -2617,6 +2619,9 @@ async def process_files_batch(
|
||||
|
||||
collection_name = form_data.collection_name
|
||||
|
||||
if collection_name:
|
||||
await _validate_collection_access([collection_name], user, access_type='write')
|
||||
|
||||
file_results: List[BatchProcessFilesResult] = []
|
||||
file_errors: List[BatchProcessFilesResult] = []
|
||||
file_updates: List[FileUpdateForm] = []
|
||||
|
||||
@@ -26,7 +26,11 @@ import base64
|
||||
import io
|
||||
import re
|
||||
|
||||
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK
|
||||
from open_webui.env import (
|
||||
AIOHTTP_CLIENT_ALLOW_REDIRECTS,
|
||||
AIOHTTP_CLIENT_SESSION_SSL,
|
||||
ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK,
|
||||
)
|
||||
from open_webui.utils.session_pool import get_session
|
||||
|
||||
BASE64_IMAGE_URL_PREFIX = re.compile(r'data:image/\w+;base64,', re.IGNORECASE)
|
||||
@@ -53,11 +57,17 @@ _IMAGE_MIME_FALLBACK = {
|
||||
async def get_image_base64_from_url(url: str) -> Optional[str]:
|
||||
try:
|
||||
if url.startswith('http'):
|
||||
# Validate URL to prevent SSRF attacks against local/private networks
|
||||
# Validate URL to prevent SSRF attacks against local/private networks.
|
||||
# allow_redirects=False prevents redirect-based SSRF: validate_url() is
|
||||
# called only on the originally-submitted URL; following 3xx redirects
|
||||
# without re-validation would let an attacker reach private IPs via a
|
||||
# public host that redirects internally (e.g. cloud-metadata exfil).
|
||||
validate_url(url)
|
||||
# Download the image from the URL
|
||||
session = await get_session()
|
||||
async with session.get(url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as response:
|
||||
async with session.get(
|
||||
url, ssl=AIOHTTP_CLIENT_SESSION_SSL, allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
image_data = await response.read()
|
||||
encoded_string = base64.b64encode(image_data).decode('utf-8')
|
||||
|
||||
Reference in New Issue
Block a user