aws-cli (AWS): qué vio el motor en el CLI
Corrimos NP Auditor sobre el paquete público awscli/: 14 conocidos, 138 en cuarentena, 99 match. Aquí el desglose — técnicos y para mortales.
Esto no es “hackeamos AWS”. Es un scan estático (Capa1 + cuarentena Capa2)
sobre el paquete público awscli/. Candidato ≠ vulnerabilidad explotable confirmada.
Qué miramos: el CLI de AWS en Python (awscli/, ≈219 archivos),
clone shallow de develop (4b39e80…), sin ejecutar su código.
Qué salió: 14 patrones conocidos (Capa1), 138 sinks en cuarentena,
99 con match a un objetivo Capa2, 39 candidatos nuevos. Oráculo: 0/0 (esperado en código real).
Qué significa: el motor ve superficie real — shell, zips, rutas, logs —
con archivo y línea, útil para priorizar revisión.
Qué NO significa: que haya 99 bugs listos para CVE ni que “rompimos” el CLI.
Tabla de métricas
| Métrica | Valor |
|---|---|
| Repo / org | aws/aws-cli · Amazon Web Services |
| Branch / SHA | develop · 4b39e80d |
| Scan path | awscli/ · ≈219 .py · 26.4 s |
| Capa1 conocidos | 14 |
| Cuarentena / match / nuevos | 138 / 99 / 39 |
| Oráculo pass / fail | 0 / 0 |
| descartado_fp | 0 |
| Top objetivos (match) | cmd_injection 32 · rce_eval 27 · mass_assignment 16 · zip_slip 4 |
| CWE Capa1 (top) | CWE-117 ×5 · CWE-22 ×4 · CWE-1336 ×1 · CWE-915 ×1 |
Auditoría de impacto (lo que sí huele fuerte)
Aquí no contamos sinks: leemos trust boundaries. Pregunta de auditor: si llega input no confiable a este flujo, ¿qué se rompe y qué podría hacer un adversario? Sin oráculo ni taint completo, nada de esto está “confirmado explotable”.
1. Pager / help con shell=True — obj_cmd_injection
Ancla
awscli/help.py:190 (Popen + shell=True); wrapper en :106; alias con quote+shell en awscli/alias.py:293.
Qué pasa
El CLI lanza un proceso (pager / shell de alias) vía intérprete de shell del SO. Eso amplifica metacaracteres si algún componente del comando no está acotado.
Qué se puede hacer / cómo podría fallar
Impacto local al usuario del CLI (misma sesión / misma máquina): ejecución de comandos arbitrarios si un argumento o variable de entorno no confiable entra al string del shell. Precondiciones típicas: control del valor que se concatena o del pager; en alias, que el valor del alias o args no queden bien citados pese a compat_shell_quote.
Cómo podría ser explotado (abstracto)
Cadena clásica de command injection: superficie CLI → string pasado a shell → shell interpreta operadores. No es RCE remoto “por internet” salvo que alguien exponga el CLI como servicio o procese args de un tercero (CI malicioso, wrapper). Falta: traza taint de cada arg hasta el Popen y fixture propia que demuestre fuga de quoting.
2. Empaquetado CloudFormation / rutas — CWE-22 + zip
Ancla
Capa1: artifact_exporter.py:448, compat.py:487 (ruta_compuesta_sin_verificar_contencion). Capa2: zipfile.ZipFile en awslambda.py:94, artifact_exporter.py:186, codedeploy/push.py:187 → obj_zip_slip.
Qué pasa
Flujos de empaquetado leen rutas locales y arman zips / artefactos para subir a AWS. Path compuesto sin contención verificada = riesgo de leer o incluir archivos fuera del directorio esperado.
Qué se puede hacer / cómo podría fallar
Clase path traversal / zip slip: si un path o nombre de entrada en el zip viene de plantilla/artefacto no confiable, el empaquetador podría leer fuera del tree o (en extract) escribir fuera. Precondiciones: el usuario (o un recurso remoto que el CLI confía) controla paths; en zip, que exista extract sin sanitizar nombres — el match solo ve ZipFile, no prueba extract malicioso.
Cómo podría ser explotado (abstracto)
Escenario: plantilla o directorio de build preparado por un adversario → CLI empaqueta → filtración de secretos locales al bucket, o en sentido inverso unpack con entradas ../. Aún no confirmado: falta oráculo de contención y revisar si cada call site hace resolve+commonpath.
3. Plantilla CFN desde parámetro — CWE-1336
Ancla
cloudformation/package.py:166 · plantilla_construida_desde_parametro.
Qué pasa
El flujo de package construye/exporta plantilla a partir de input de usuario. En familia plantillas, el riesgo clásico es inyección de directivas o expansión no acotada (SSTI / template injection), no necesariamente eval de Python.
Qué se puede hacer / cómo podría fallar
Si el “motor” de plantilla interpreta sintaxis peligrosa y el input no está sandboxed, el efecto va de corrupción de plantilla a ejecución según el motor. Precondiciones: motor con features peligrosas + input no confiable en el slot de plantilla.
Cómo podría ser explotado (abstracto)
Threat model: contributor malicioso o archivo de plantilla de un repo ajeno → aws cloudformation package → efecto en el artefacto o en el proceso local. Sin fixture de oráculo, queda en “revisar motor y trust del archivo”.
4. XML history + urlopen — XXE-like / SSRF
Ancla
history/show.py:298 (xml.dom.minidom.parseString → obj_xxe_like); gamelift/getlog.py:44 y codedeploy/utils.py:116 (urlopen → obj_ssrf).
Qué pasa
Parseo de cuerpos XML (history) y fetches HTTP (logs / metadata). Parsers XML antiguos pueden resolver entidades externas; urlopen sigue la URL que le den.
Qué se puede hacer / cómo podría fallar
Clases: XXE (lectura local / SSRF vía entidad) si el parser resuelve DTDs; SSRF si la URL es atacante-controlada (presigned malicioso, redirect). El hit de metadata (169.254.169.254) parece chequeo de entorno, no sink de usuario — distinto perfil de riesgo.
Cómo podría ser explotado (abstracto)
Cadena: respuesta o archivo hostil → parseString / urlopen → exfiltración o hit a red interna. Bloqueo actual: no hay taint ni config del parser en el JSON; score 0.7–0.8 = hipótesis, no veredicto.
5. Logs sin sanear — CWE-117 ×5
Ancla
cloudtrail/validation.py líneas 276 / 861 / 1050; también EMR (command.py:62, emrutils.py:288).
Qué pasa / impacto
Datos crudos al log → log injection (CRLF): falsear líneas, ensuciar SIEM, a veces XSS en visores HTML de logs. No es RCE por sí solo. Precondición: el valor logueado viene de input externo y el sink de logs no escapa.
Lo que no conocemos (banco / cuarentena sin etiqueta fina)
39 candidato_nuevo sin objetivo_id ni CWE. El banco no etiquetado guarda la sospecha cruda.
Agrupo por patrón — qué podría ser, qué haría falta para explotabilidad, y qué probablemente es ruido.
Grupo A — platform.system (×7, p.ej. testutils.py)
Sospecha del scanner: token “system” cerca de lógica de proceso. Realidad probable: fingerprint de OS, no shell. Si fuera malo: solo si el string del OS se concatenara a un comando shell sin quote — no se ve en el snippet. Checklist: confirmar que no hay edge a Popen/os.system; si no, etiquetar FP / “info”.
Grupo B — *.decode (argparser, s3 paths, stdout/stderr, login)
Sospecha: boundary bytes→str (a veces familia header/email). Escenario abstracto: decode malicioso solo importa si el string resultante viaja a sink peligroso (header, shell, SQL). Solo, es higiene. Checklist: taint un salto; si muere en print/log estructurado → ruido.
Grupo C — parsers / jmespath / emrutils.call
self._parser.parse (argprocess.py:360), jmespath.search en artifact_exporter, emrutils.call en createcluster. Sospecha: “parse/call” genérico. jmespath: queries complejas pueden ser DoS/CPU, no RCE Python; --query en globalargs.py:59 ya matcheó obj_rce_eval por el nombre compile — engañoso. Checklist: documentar motor (JMESPath ≠ eval); fixtures de complejidad; no promover a RCE sin evidencia.
Grupo D — check_output / ui.run / renderer
sessionmanager.py:124 (check_output), startlivetail.py:286 (ui.run). Superficie de proceso/UI: revisar args fijos vs user-controlled. Si args son constantes de versión del plugin → bajo riesgo. Si algún path/URL de usuario entra al argv → subir a investigación cmd_injection.
Ruido vs señal
- Descartaría (ruido):
setattr→obj_mass_assignmentenalias.py:243— namespaces argparse, no mass-assign HTTP. - Descartaría / renombraría:
re.compile/jmespath.compile→obj_rce_eval— no es bytecode Python; el nombre de familia miente. - yaml.load en
ordered_yaml.py:47conSafeOrderedLoader— el sink se llama load, el loader es safe; matchobj_idores semántica floja. - Señal a seguir:
shell=Truereal, ZipFile en empaquetado, CWE-22 en artifact paths, plantilla CFN, XML parseString. - Límite de corrida:
oraculo_pass=0, scan soloawscli/, shallow, sin promoción a BANCO_FIRMAS.
Veredicto del auditor
Prioridad de investigación (no de “hack”):
- P0 Taint + review de
help.py/alias.pyconshell=True: ¿puede un arg no confiable llegar al string del shell? - P1 Contención de paths en CloudFormation package/export + política de nombres en zips Lambda/CodeDeploy/GameLift.
- P1 XML en history show: ¿el parser resuelve entidades externas sobre cuerpos no confiables?
- P2 CWE-117 logs (higiene), CWE-916 hash en ECS deploy (contexto: integrity de appspec, no password hashing), candidatos
platform.system/decode.
aws-cli huele a CLI con shell y empaquetado real; el volumen de “RCE” es inflado por compile. Investiga shell y paths antes que mass_assignment.
Capa1 vs Capa2 / cuarentena
obj_cmd_injection, etc.). Score ≠ confirmación de exploit.
Detalle técnico — hallazgos del JSON
cloudformation/artifact_exporter.py:448. Flag de path traversal — revisar contención real del path, no asumir RCE.# piping to a pager to work, we need to use shell=True. kwargs['shell'] = True return Popen(*args, **kwargs)
alias.py:293 / :302 (compat_shell_quote / invoker con shell=True). Superficie shell real en contexto de pager/alias — candidato a revisión, no proof of exploit.with closing(zipfile.ZipFile(fileobj)) as f:
f.infolist()
artifact_exporter.py:186. El objetivo zip_slip marca el sink ZipFile; no confirma traversal en extract.setattr → familia mass_assignment es heurística floja en CLIs (namespaces argparse). Match semántico ruidoso — ejemplo de lo que no debes leer como bug.re.compile o jmespath.compile — no bytecode Python. La familia obj_rce_eval se pasa de lanza con el nombre compile.Cómo se validaría bien (defensivo)
Para promover un match: (1) fixture propia que reproduzca el patrón en harness controlado; (2) oráculo Capa2 sobre esa fixture; (3) revisión humana del trust boundary (¿quién controla el input? ¿hay contención?). Store → analyze; humano decide. No hace falta “probar el hack” contra el repo ajeno.
Serie: caso agregado · Claude · aws-cli · Django · Metaflow · home