Der Code lief vor dem Modell
Pluto Security hat 31 Hugging-Face-Repositories gefunden, die sich als winzige Qwen2-Test-Checkpoints ausgeben. Keines enthielt ein Modell. Jedes enthielt eine Python-Datei, die in dem Moment läuft, in dem das Modell mit trust_remote_code geladen wird, und einen versteckten Miner, Startup-Hooks und in manchen Builds eine Reverse Shell installiert. Das Flaggschiff kam durch die Scanner von Hugging Face und erreichte 28.412 Downloads, und eine fast identische Kopie blieb nach der Entfernung online. Coding Agents greifen beim Schreiben von Tests zu winzigen Testmodellen. In Bromure Agentic Coding läuft dieses Laden in einer VM, aus der der Miner seinen Pool nicht erreicht, die Security Timeline ordnet jede Verbindung dem Tool-Aufruf zu, der sie geöffnet hat, und ein Zurückspulen entfernt, was die Payload hinterlassen hat.
Die Gewichtsdatei war 42 Bytes groß und enthielt keine Tensoren. Die Arbeit erledigte eine Python-Datei, die lief, sobald das Modell geladen wurde, bevor irgendein Modellcode etwas tat.
Am 29. Oktober hat Pluto Security Load/Mine
veröffentlicht, eine Analyse einer Kampagne, die seit September auf
Hugging Face und auf Chinas ModelScope läuft. Pluto fand 31 bösartige
Repositories in sieben Konten. Sie sind als kleine Qwen2-Test-Checkpoints
getarnt, mit Namen wie qwen2-tiny, und kamen zusammen auf mehr als
32.000 erfasste Downloads. BleepingComputer
berichtete am selben Tag.
Pluto fasst die Kampagne in einem Satz zusammen: „The code runs before the model does“ (der Code läuft, bevor das Modell läuft).
Wie aus dem Laden eines Modells ein Miner wird
Der Angriff nutzt keine vergifteten Gewichte und keinen Pickle-Exploit. Er
nutzt ein Feature. Ein Hugging-Face-Modell kann eigenen Python-Code
mitbringen, und die Bibliothek Transformers führt diesen Code aus, wenn
der Aufrufer trust_remote_code=True übergibt. Die config.json des
Repositorys nennt in einem auto_map-Eintrag ein Modul und eine Klasse,
etwa modeling_evil2.EvilModel. Transformers lädt diese .py-Datei
herunter und importiert sie. Alles auf oberster Ebene der Datei läuft
während des Imports, noch bevor die Klasse zurückgegeben wird.
In diesen Repositories ist model.safetensors 42 Bytes groß. Darin steckt
kein Modell. Die Python-Datei erledigt die ganze Arbeit:
- Sie nistet sich ein. Sie schreibt nach
/tmp/.cch/svc.pyeinen Watchdog, der den Miner alle zwei Minuten neu startet, und hängt einen Startup-Hook an~/.bashrc,/root/.bashrcund/root/.profilean. Builds der ersten Welle legen außerdem einen SSH-Schlüssel namensqx-5090-opinauthorized_keysund einen Cron-Job unter/etc/cron.d/.sync-helperab. - Sie holt den Miner und versteckt ihn. Der Downloader wird mit einem
leeren Proxy-Handler gebaut und ignoriert so die Einstellungen
HTTP_PROXYundHTTPS_PROXY, auf die sich ein Unternehmen vielleicht verlässt. Bei der 40,6 MB großen Binärdatei sind die UPX-Marker so verändert, dassupx -dsie ablehnt. Sie läuft aus einer anonymen Datei im Speicher, unter dem Prozessnamen[kworker/u20:1], und schürft Pearl über Kryptex auf Port 8048. - Sie öffnet eine Tür. Drei Builds fügen eine Reverse Shell zu
212.32.253.3auf Port 443 oder 9443 hinzu, die sich nach jedem Abbruch neu verbindet. - Sie nimmt den Host ins Visier. Ein verwandtes Skript der zweiten Welle hängt aus einem privilegierten Container die Festplatten des Hosts ein und verschafft sich Root auf dem Server darunter.
Sauber gescannt, und wieder da
Hugging Face prüft Uploads mit einem Virenscanner und mit Partnertools von
Protect AI und JFrog. Laut Pluto suchen diese Tools nach bekannter
Malware und unsicheren Modellformaten. Was die Python-Datei tut, prüfen
sie nicht. Das Flaggschiff der zweiten Welle, MENXIANGYU/qwen2-tiny,
„scanned clean while it collected 28,412 downloads“ (blieb im Scan
unauffällig, während es 28.412 Downloads sammelte). Das Urteil eines
Partnerscanners über die Payload-Datei lautete „not a machine-learning
model“ (kein Machine-Learning-Modell).
Pluto meldete die erste Welle an Hugging Face und ModelScope, und beide
entfernten die Konten. Innerhalb weniger Tage tauchten 19 neue
Repositories unter neuen Konten auf. Stunden nach Plutos Bericht
veröffentlichte der Betreiber ein Container-Escape-Skript. Hugging Face
deaktivierte das Flaggschiff, aber BleepingComputer bestätigte, dass eine
fast identische Kopie, htt0118/qwen2-tiny, mit demselben Miner und
derselben Reverse Shell weiter online war.
Ein winziges Testmodell ist genau das, wonach Ihr Agent sucht
Die Kampagne zielte auf GPU-Server mit Inferenzplattformen wie Xinference
und GPUStack. Der Köder wirkt aber auch auf dem Rechner eines Entwicklers,
denn ein Coding Agent sucht einen winzigen Qwen2-Checkpoint, wenn er Tests
für Code schreibt, der ein Modell aufruft. Bitten Sie einen Agenten, einen
Test für Ihre Embedding-Pipeline oder Ihr Fine-Tuning-Skript zu schreiben,
und er sucht ein Modell, das klein genug ist, um in Sekunden zu laden. Die
Namen dieser Kampagne, qwen2-tiny und seine Varianten, sind genau auf
diese Suche zugeschnitten.
Der Agent muss nicht nachlässig sein, um das Flag zu setzen. Wenn ein
Repository eigenen Code mitbringt, bricht Transformers ab und fordert den
Aufrufer auf, trust_remote_code=True zu übergeben. Für einen Agenten,
der einen fehlschlagenden Test repariert, ist das ein Fehler, dessen
Lösung gleich in der Meldung steht. Er ergänzt das Argument und startet
den Test neu. Ob der Test dann besteht, spielt keine Rolle mehr: Der
Miner ist während des Imports gestartet.
Auf einem Laptop läuft diese Datei als Sie. Der Startup-Hook in
~/.bashrc läuft in jedem Terminal, das Sie danach öffnen. Die Reverse
Shell gibt dem Betreiber einen Prompt mit Ihrem ~/.ssh, Ihren
Cloud-Zugangsdaten, Ihrem gh-Login und Ihrem HF_TOKEN. Plutos Rat an
alle, die diese Modelle geladen haben: den Rechner als kompromittiert
behandeln und jede Zugangsdaten rotieren, die der Prozess erreichen
konnte.
In Plutos Härtungsempfehlungen beschreibt eine Zeile das Setup, das Sie wollen: nicht vertrauenswürdigen Modellcode „with limited privileges, restricted egress and no production secrets“ ausführen (mit eingeschränkten Rechten, beschränktem ausgehendem Verkehr und ohne Produktionsgeheimnisse).
Derselbe Test in Bromure Agentic Coding
Bromure Agentic Coding führt Claude Code, Codex, Grok, Kimi und omp in Ubuntu-VMs auf Ihrem Mac aus. Jede Maschine hat ihre eigene Festplatte, ihr eigenes Home und ihre eigene Netzwerkrichtlinie, durchgesetzt auf dem Mac, wo der Agent nicht hinkommt. Hier ist derselbe Test auf einer Bromure-Maschine, eine Stufe der Payload nach der anderen.
Der Miner erreicht seinen Pool nie
Jede Verbindung, die die VM öffnet, läuft über Bromures virtuellen Switch und Proxy auf dem Mac. Die Ports 80 und 443 werden am Switch in den Proxy umgeleitet, also wird auch ein Downloader mit leerem Proxy-Handler abgefangen. Der Trick, mit dem die Kampagne an Proxy-Einstellungen von Unternehmen vorbeikommt, hat hier nichts zu umgehen, denn das Abfangen hängt von keiner Einstellung in der VM ab.
Stellen Sie Unmatched traffic in Sandbox Policy auf Deny, oder
stellen Sie die Maschine auf die Engine OpenShell + Sentry um, bei der
alles verweigert wird, was keine Regel erlaubt. Die Startrichtlinie
erlaubt GitHub, npm und PyPI nur lesend sowie die Ubuntu-Mirrors, und Sie
ergänzen huggingface.co für die Downloads. Dann passt die Verbindung des
Miners zu prl-us.kryptex.network:8048 auf keine Regel und wird
abgelehnt. Ebenso die Reverse Shell zu 212.32.253.3, auf beiden Ports.
Mit Tell the agent when policy blocks it, bei OpenShell-Maschinen
standardmäßig aktiv, liest der Agent außerdem in seiner Unterhaltung, was
blockiert wurde und dass er anhalten und Sie fragen soll, statt die
nächsten zehn Minuten nach einem Umweg zu suchen.
Auf einer OpenShell-Maschine mit Enforce binaries wird jede
Verbindung zudem dem Programm zugeordnet, das sie geöffnet hat, und der
Hash jedes Programms wird beim ersten Start festgeschrieben. Ein Miner,
der aus einer anonymen Speicherdatei startet, ist ein Programm, das keine
Regel nennt. Ihn in [kworker/u20:1] umzubenennen, ändert daran nichts,
denn der Attestor identifiziert die Binärdatei, nicht den Namen, den sie
sich gibt.
Sie sehen, welcher Tool-Aufruf es war
Der Betreiber hat den Namen [kworker/u20:1] gewählt, um jemanden zu
täuschen, der auf einem ausgelasteten Server ps oder top liest. Auf
einer Bromure-Maschine mit dem Kernel-Sentry meldet der Kernel der VM
jeden Programmstart und jede Verbindung an den Mac, über einen privaten
Kanal, den der Agent nicht beenden kann. Jede blockierte Verbindung
landet in der Security Timeline mit der Kette dahinter, etwa so:
Tool pytest tests/test_embed.py: claude → bash → python → TCP 212.32.253.3:443.
Ein Doppelklick auf die Zeile, und network lineage zeigt die ganze Geschichte: den Prompt, den Sie gegeben haben, was der Agent vor dem Aufruf geschrieben hat, den genauen Befehl, jeden Prozess vom Agenten bis zu dem, der sich verbunden hat, und welche Schicht abgelehnt hat. Wenn ein Testlauf eine nackte IP-Adresse anwählt, bekommen Sie eine rote Zeile mit dem Befehl, der das Modell geladen hat.
Der Agent-Watchdog, in Guardrails auf Quarantine gestellt, reagiert auf dieselben Signale, ohne auf Sie zu warten. Eine Häufung von Blockaden, oder neue Ziele, nachdem sich die Sitzung eingependelt hat, über seiner Schwelle innerhalb einer Minute, und Bromure trennt das Netzwerk der VM und pausiert sie binnen Millisekunden. Die Warnung listet auf, was er gesehen hat. Keep quarantined setzt die VM fort, mit weiterhin getrenntem Netzwerk, damit Sie hineinschauen können.
Die Schlüssel in der VM sind Platzhalter
Angenommen, ein Build der Payload kommt weiter, auf einer Maschine, die
Sie auf der Standard-Firewall gelassen haben. Der Betreiber hat eine
Shell in einer Ubuntu-VM, und alle Zugangsdaten darin sind Platzhalter.
Der GH_TOKEN in der Umgebung und das Token in ~/.git-credentials sind
Fälschungen, die der Proxy auf Ihrem Mac erst auf dem Weg zu GitHub gegen
das echte tauscht. Tragen Sie Ihr Hugging-Face-Token unter Other API
keys mit dem Variablennamen HF_TOKEN und dem Host-Filter
huggingface.co ein, und die VM bekommt einen brm_…-Platzhalter, der
nur dort funktioniert. SSH-Schlüssel kommen gar nicht erst in die VM: Der
Agent kann eine Signatur anfordern, und die Schlüsselbytes bleiben auf
dem Mac.
Ein Platzhalter ist zugleich ein Stolperdraht. Schickt die Payload den GitHub-Platzhalter irgendwohin außer per HTTPS zu GitHub, blockiert der Proxy die Anfrage mit HTTP 451, pausiert die VM und öffnet eine Warnung, die die Zugangsdaten und den Zielhost nennt.
Das Aufräumen sind zwei Klicks
Plutos Aufräumliste für einen infizierten Host ist lang: den Watchdog
finden, die Shell-Hooks entfernen, den Cron-Job löschen, den
SSH-Schlüssel aus sechs authorized_keys-Pfaden streichen, nach
fremden Registrierungen suchen und im Zweifel aus einem vertrauenswürdigen
Image neu aufsetzen. Eine Bromure-Maschine hat dieses vertrauenswürdige
Image schon. Reset disk klont die Systemfestplatte neu aus dem
Basis-Image und nimmt dabei /etc/cron.d/.sync-helper und alles andere
mit, was die Payload außerhalb des Home geschrieben hat. Rewind home…
setzt /home/ubuntu auf einen Checkpoint eines früheren Starts zurück.
Bromure legt bei jedem Start der Maschine einen an, also ist die
~/.bashrc von vor dem Test darunter. Ihre mit dem Host geteilten Ordner
liegen auf dem Mac und bleiben unberührt.
Auf einer OpenShell-Maschine mit Enforce binaries wird ein Großteil dieser
Liste gar nicht erst geschrieben. Der Agent läuft ohne Root, also werden
die Hooks in /root/.bashrc und /root/.profile, der Cron-Job und die
Schreibzugriffe auf die authorized_keys von Root abgelehnt, und jede
Ablehnung erscheint als Guest sandbox-Zeile in der Timeline.
Ausgehender Verkehr, entschieden auf dem Mac
Die Ports 80 und 443 gehen an den Proxy, egal was die Einstellungen der VM sagen. Wird nicht zugeordneter Verkehr verweigert, werden Pool und Shell abgelehnt.
Jede Verbindung hat einen Namen
Der Kernel-Sentry ordnet jede Verbindung dem Tool-Aufruf und der Prozesskette dahinter zu. Ein getarnter Miner erscheint trotzdem als das Laden des Modells.
Platzhalter statt Schlüssel
GitHub-, Cloud- und Hugging-Face-Tokens in der VM sind Fälschungen, die nur auf ihren eigenen Hosts funktionieren. Anderswo lösen sie eine Warnung aus.
Eine saubere Maschine auf Abruf
Reset disk und Rewind home versetzen die VM in den Zustand vor dem Test, Persistenz eingeschlossen.
Behandeln Sie ein Modell-Repository wie ein Paket
Pluto listet seine Indikatoren im Bericht auf. Wenn Sie eines dieser
Modelle mit aktiviertem eigenem Code geladen haben, suchen Sie nach
/tmp/.cch/, dem Prozess [kworker/u20:1] mit Mining-Argumenten und den
Startup-Hooks, und rotieren Sie dann alle Zugangsdaten, die der Rechner
erreichen konnte. Bevor Sie trust_remote_code für ein Repository
einschalten, lesen Sie dessen modeling_*.py und pinnen Sie den Commit,
den Sie gelesen haben.
Plutos Bericht zeigt ein Modell-Repository, das als Code-Repository arbeitet, während die Scanner es als Dateiformat prüften. Wenn Ihr Agent Modelle für seine Tests aussucht, wird er irgendwann eine dieser Dateien laden. Auf einer Bromure-Maschine läuft diese Datei in einer VM mit Platzhalter-Schlüsseln und einer auf dem Mac durchgesetzten Egress-Richtlinie, und danach können Sie die Maschine auf einen sauberen Checkpoint zurücksetzen.
Für einen früheren Fall, in dem die Prüfung von trust_remote_code
selbst versagt hat, lesen Sie Das Flag sagte Nein.
Wie Bromure die Pakete prüft, die ein Agent installiert, lesen Sie in
Das Release war zwei Stunden alt.