np_auditor ← volver
Auditoría · aws-cli · Fase E
1 de agosto, 2026 · Fase E v2 · shallow scan

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.

Léelo así

Esto no es “hackeamos AWS”. Es un scan estático (Capa1 + cuarentena Capa2) sobre el paquete público awscli/. Candidato ≠ vulnerabilidad explotable confirmada.

Para mortales

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.

14Capa1 conocidos
138Cuarentena
99Match objetivo
39Candidatos nuevos

Tabla de métricas

aws-cli · Fase E v2 · ts 2026-08-01T21:48:39Z
MétricaValor
Repo / orgaws/aws-cli · Amazon Web Services
Branch / SHAdevelop · 4b39e80d
Scan pathawscli/ · ≈219 .py · 26.4 s
Capa1 conocidos14
Cuarentena / match / nuevos138 / 99 / 39
Oráculo pass / fail0 / 0
descartado_fp0
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=Trueobj_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:187obj_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.parseStringobj_xxe_like); gamelift/getlog.py:44 y codedeploy/utils.py:116 (urlopenobj_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

Veredicto del auditor

Prioridad de investigación (no de “hack”):

En una frase

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

01
Capa1 — conocidos 14 hits contra el banco de firmas (CWE). En aws-cli: logs sin sanear, paths compuestos, plantilla CFN.
02
Cuarentena 138 sinks AST sin CWE inventado (R1). Se guardan para análisis; no se escriben al banco.
03
Match de objetivos 99 cruzaron familia (obj_cmd_injection, etc.). Score ≠ confirmación de exploit.

Detalle técnico — hallazgos del JSON

CWE-22 awscli/compat.py:487 · ruta_compuesta_sin_verificar_contencion Capa1
También cloudformation/artifact_exporter.py:448. Flag de path traversal — revisar contención real del path, no asumir RCE.
CWE-117 awscli/customizations/cloudtrail/validation.py:276 · mensaje_log_sin_sanear Capa1 ×5
Higiene de logs (también otras líneas en el mismo módulo). Útil; no implica ejecución remota.
obj_cmd_injection awscli/help.py:190 · Popen + shell=True Capa2 match 0.9
# piping to a pager to work, we need to use shell=True.
kwargs['shell'] = True
return Popen(*args, **kwargs)
También 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.
obj_zip_slip awscli/customizations/awslambda.py:94 · zipfile.ZipFile Capa2 match 0.8
with closing(zipfile.ZipFile(fileobj)) as f:
    f.infolist()
También artifact_exporter.py:186. El objetivo zip_slip marca el sink ZipFile; no confirma traversal en extract.
obj_mass_assignment awscli/alias.py:243 · setattr Ruido típico
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.
obj_rce_eval awscli/help.py / textwriter.py · re.compile / jmespath.compile Nombre engañoso
Varios matches “altos” son 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)

Sin explotación de terceros

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