Die Coding-Challenge verbot KI-Assistenten
Kaspersky veröffentlichte am 1. September die Recruiter-Kampagne von Mirage Kitten. Der Köder ist eine Programmieraufgabe für zu Hause, und ihre README stellt drei Forderungen: in drei Stunden fertig werden, server.js nicht verändern, weil es bereits fehlerfrei sei, und keinen KI-Assistenten benutzen. Zeile eins von server.js importiert ein trojanisiertes npm-Paket, das nie auf npm veröffentlicht wurde. Es kam im Zip mit, bereits in node_modules abgelegt. Jede jemals gebaute Lieferketten-Kontrolle bewacht eine Registry-Anfrage, die dieser Angriff nicht stellt. Bromure Agentic Coding setzt bei der anderen Hälfte an: der Maschine, auf der der Code läuft.
Der Angreifer bat Sie, keinen Coding-Agenten zu benutzen. Ein Werkzeug, das jede Datei im Repository öffnet, ganz gleich was die Anweisungen sagen, war das Einzige, was zwischen dieser Kampagne und einer Hintertür stand, und die README hat es Ihnen ausgeredet.
Ein Recruiter schreibt Ihnen auf LinkedIn. Die Stelle wirkt echt genug, der Firmenname sagt Ihnen etwas, und der nächste Schritt ist eine technische Aufgabe für zu Hause. Sie bekommen ein Zip. Darin eine Projektmanagement-App namens TaskFlow, gebaut mit Express, React und Vite: ein paar hundert Dateien und eine README im Wurzelverzeichnis. Beheben Sie die Fehler im Frontend. Sie haben drei Stunden.
Das ist das Gewöhnlichste, was einem arbeitenden Entwickler passiert. Es ist
auch, laut Kasperskys Analyse vom 1. September,
die Art, wie eine iranische Spionagegruppe, die der Bericht Mirage Kitten nennt
und die anderswo als UNC1549, Smoke Sandstorm oder Nimbus Manticore geführt
wird, plattformübergreifende Hintertüren bei Entwicklern in Ägypten, Äthiopien
und Afghanistan ablegt, mit weiteren Einreichungen aus Indien, der Türkei,
Israel, dem Irak, Deutschland und Irland. Die Archive kommen mit Namen wie
Front-Technical-Challenge.zip und Task-FullStack.zip, gehostet auf AWS,
damit der Download nach Infrastruktur aussieht und nicht nach Schadlast. The
Hacker News berichtete am 9. September über die Kampagne.
Drei Regeln, alle übers Lesen
Kaspersky hält drei Anweisungen in der README fest. Sehen Sie sich an, was jede dem Angreifer einbringt.
Die README setzte außerdem ein Zeitlimit von drei Stunden und untersagte den Einsatz von KI-Assistenten.
Sie behauptete zudem,
server.jssei fehlerfrei und dürfe nicht verändert werden.
Die Uhr ist die offensichtliche Regel: drei Stunden, um eine unbekannte Codebasis zu reparieren, ist ein Zeitplan, der keinen Raum für Neugier lässt. Die zweite Regel richtet Ihre Aufmerksamkeit aufs Frontend und weg von einer bestimmten Datei. Die dritte ist die interessante, denn hier sagt Ihnen der Angreifer, vor welchem Werkzeug er Angst hat.
Ein Coding-Agent, dem man dieses Repository gibt, hält sich nicht an den Rahmen,
den die README nahelegt. Richten Sie Claude Code oder Codex auf das Projekt und
bitten Sie um Korrekturen im Frontend, und er liest trotzdem server.js, denn
herauszufinden, wie die App startet, ist genau der Weg, auf dem er
herausfindet, womit das Frontend spricht. Er liest den ganzen Baum. Er hat keine
Drei-Stunden-Uhr und keinen Grund, eine Datei zu überspringen, die jemand für
sauber erklärt hat. Den Assistenten zu verbieten entfernt den einzigen Leser im
Raum, der alles liest, was eine seltsame Forderung für ein Bewerbungsverfahren
ist.
Und was er gelesen hätte, ist Zeile eins.
Die Abhängigkeit war nie auf npm
Die erste Zeile von server.js importiert ein Paket namens
colorized_terminal, in Version 2.1.0. Ein Geschwistersample nutzt
pretty-log, ebenfalls 2.1.0. Keines ist ein Typosquat von irgendetwas, und
keines ist ein gekaperter Release, denn keines liegt überhaupt in der Registry:
Die Angreifer bündelten das Paket direkt im
node_modules-Verzeichnis des Aufgabenarchivs, statt es in der npm-Registry zu veröffentlichen.
Beim Import startete das Paket still ein Implantat aus
node_modules/.cache/.320697f1/index.jsals abgekoppelten Hintergrundprozess.
Hier läuft kein npm install und kein postinstall-Skript. Es gibt keinen
Lockfile-Eintrag zu prüfen, keinen Integritäts-Hash zu vergleichen, keine
Provenienz-Attestierung zu kontrollieren, keinen Veröffentlichungszeitstempel,
der altern könnte, keinen Reputationsdienst zu befragen, und keine
Registry-Anfrage, an der irgendetwas davon hängen könnte. Die Abhängigkeit war
im Ordner, bevor Sie den Ordner geöffnet haben. Die App zu starten, die die
README zu reparieren bittet, und das ist die ganze Aufgabe, bedeutet ein
schlichtes require, und dieses require forkt eine Hintertür.
Drei Jahre Abwehr gegen bösartige Pakete hängen alle an einem Registry-Abruf. Mirage Kitten macht keinen einzigen.
Es bleibt nicht im Aufgabenordner
NodeRabbit fängt klein an. Kasperskys erste Variante, von einer Maschine in Afghanistan, trägt elf Befehle: eine Datei lesen, eine Datei schreiben, ein Verzeichnis auflisten, die Netzwerkkonfiguration auslesen, einen Prozess starten, ein Skript ausführen, schlafen. Die ägyptische Variante ergänzt Sandbox-Erkennung, Proxy-Unterstützung und dynamische Portwahl. Die dritte, aus Äthiopien, kommt auf dreiundzwanzig, und die neuen sind der Punkt, an dem die Maschine eines Entwicklers aufhört, eine einzelne Maschine zu sein.
outlook:emails erntet Mail-Artefakte. persist:projects:scan durchläuft die
Platte auf der Suche nach Git-Repositories. persist:project:inject schreibt in
die gefundenen hinein und landet in .git/hooks/post-merge und
.git/hooks/post-checkout, damit das Implantat beim nächsten Pull neu startet.
persist:vscode installiert eine bösartige Erweiterung in Ihrem Editor, der sie
dann in jedem Projekt lädt, das Sie öffnen. Kasperskys Formulierung ist präzise,
was die Absicht angeht:
Über die oben beschriebenen Persistenzmechanismen hinaus führt Variante 3 zwei weitere Mechanismen ein, die die Schadsoftware über gängige Entwickler-Arbeitsabläufe neu starten.
Ihre anderen Repositories sind das Ziel. Die schlichtere Persistenz darunter ist
je Plattform abgestimmt: eine geplante Aufgabe oder ein Run-Schlüssel, der
unter Windows täglich um 10 Uhr feuert, ein @reboot-Cron-Eintrag unter Linux,
ein LaunchAgent in ~/Library mit RunAtLoad und KeepAlive beide auf wahr
unter macOS.
Das Geschwister PollCat kommt über eine React-Aufgabe namens
RankChallenge-react, deren README die Uhr auf eine Sitzung von einer Stunde
verkürzt, abgesichert durch einen sechsstelligen Code, der angeblich alle
dreißig Sekunden wechselt. Dasselbe Druckdesign, weniger Zeit.
Führen Sie es aus, irgendwo, das nicht Ihre Maschine ist
Bromure Agentic Coding führt Coding-Agenten in einer hardware-virtualisierten
Linux-VM auf Ihrem Mac aus, mit jeder Sicherheitskontrolle auf der Host-Seite
dieser Grenze. Das Zip eines Fremden ist genau die Aufgabe, für die es gebaut
wurde, denn die Kontrollen beschreiben den Raum und nicht den Gast. Keine von
ihnen muss colorized_terminal erkennen.
Ein Workspace ist die Einheit der Isolation. Eine Ubuntu-VM, ihre eigene
Systemplatte, ihr eigenes persistentes Home, ihre eigenen Zugangsdaten und ihre
eigenen Richtlinien, und zwei Workspaces teilen sich nie ein Byte. Legen Sie
einen Wegwerf-Workspace für die Aufgabe an. Das Implantat läuft, der Import
feuert, der abgekoppelte Prozess startet, und all das geschieht in einer
Maschine, die heute Morgen entstanden ist und eine einzige Zip-Datei enthält.
Wenn Sie fertig sind, klont Reset disk die Systemplatte neu von der Basis
und Erase home… löscht das Home-Image; die Rollback-Checkpoints pro Start
hinter Restore home… tun dasselbe feiner granuliert. Der
@reboot-Cron-Eintrag, den der Linux-Zweig schreibt, geht mit ihnen.
Die Windows- und macOS-Persistenzzweige haben keinen Boden unter den Füßen.
Kein AppData, kein Run-Schlüssel, keine geplante Aufgabe, kein ~/Library,
kein LaunchAgent, kein Outlook-Speicher zum Lesen. Der Gast ist Linux, und es
ist nicht Ihr Linux.
persist:projects:scan sieht nur die Ordner, die Sie geteilt haben.
Host-Ordner werden als virtiofs-Mounts unter /home/ubuntu/<basename>
eingehängt, begrenzt auf acht pro Workspace, und sie sind der einzige Teil des
Dateisystems Ihres Macs, den die VM erreicht. Teilen Sie den Aufgabenordner und
sonst nichts, dann findet die Git-Hook-Injektion genau ein Repository: das, das
ohnehin feindselig war. Halten Sie die Liste aus einem zweiten Grund kurz. Ein
geteilter Ordner ist ein offenes Fenster in Ihren Mac, und eine
Kompromittierungs-Löschung lässt ihn bewusst unangetastet.
fs:read kommt mit Platzhaltern zurück. Bromure ersetzt jede Zugangsdatei,
die Sie konfigurieren, durch eine strukturerhaltende Fälschung, abgeleitet aus
dem echten Wert und einem installationseigenen Salt über HKDF-SHA256:
sk-ant-api03-brm-…, ein ghp_-Token der richtigen Länge, glpat-…,
brm-k8s-…, brm-docker-…. Die Fälschungen werden in die Umgebungsvariablen
geschrieben sowie in ~/.git-credentials, ~/.docker/config.json,
~/.kube/config, ~/.aws/config und die MCP-Konfigurationen, also genau in die
Liste, die ein Infostealer abklappert. Die echten Werte bleiben auf dem Mac
verschlüsselt, und der Host-Proxy tauscht sie auf der Leitung ein, jeweils
beschränkt auf den Zielhost, für den sie geprägt wurden. AWS geht weiter: Der
Gast signiert mit einem falschen Secret Key und der Host signiert neu, sodass
eine Anfrage, die den Proxy umgeht, bei AWS mit InvalidSignatureException
stirbt. Die Bytes privater SSH-Schlüssel gelangen nie in die VM, und nur
Signaturen überqueren die Grenze. Kein Schalter steuert irgendetwas davon, denn
der Proxy ist der einzige Weg der VM ins Netz.
Die C2-Hosts sind genau das, wofür eine Allow-Liste da ist. Die meisten
Command-Server von NodeRabbit liegen unter *.azurewebsites.net, und die von
PollCat auch. Kein Reputations-Feed wird diese Domain je auf eine Sperrliste
setzen, und keine Domain-Alters-Heuristik wird sie markieren. Einer Allow-Liste
ist das gleich: Stellen Sie in Guardrails Unmatched traffic auf Deny
und schreiben Sie die zwei oder drei Zeilen, die die Arbeit braucht. Die
Durchsetzung sitzt host-seitig und doppelt: Der virtuelle Switch gleicht jeden
Fluss nach Ziel-IP und per DNS mitgelesenem Hostnamen über alle Protokolle ab,
und der Proxy gleicht erneut nach TLS-Servernamen und HTTP-Methode ab.
Transparente Interception lenkt den Port-80- und Port-443-Verkehr des Gastes in
den Proxy um, ohne dass irgendetwas in der VM das abstellen könnte, was die
Antwort auf die Variante mit Proxy-Unterstützung und dynamischer Portwahl ist.
Und wenn das Implantat versucht, ein Token hinauszutragen, wird die Fälschung zum Stolperdraht. Der Proxy durchsucht jede ausgehende Anfrage, Header und Body, nach einer Fälschung, die zu einem Host unterwegs ist, für den sie nicht geprägt wurde. Bei einem Treffer weist er die Anfrage ab, ohne ein Byte weiterzuleiten, pausiert die VM auf der Stelle, schreibt eine rote Zeile Credential brokering in die Security Timeline und markiert den Workspace als kompromittiert, sodass der nächste Start zuerst Platte und Home löscht. Sie rotieren danach nichts, denn was das Implantat in der Hand hatte, war ein Platzhalter.
Sie können das ganze Gespräch sehen. Mit Tracing auf Activity only
schreibt der Proxy eine Metadatenzeile pro Anfrage und keinerlei Bodies.
bromure-cli trace hostnames gibt jeden einzelnen Host aus, den der Workspace
kontaktiert hat, mit Zählern, sodass drei azurewebsites.net-Adressen, die Sie
nie aufgelistet haben, einen Befehl entfernt sind statt eine forensische
Untersuchung.
Der Workspace, den Sie anlegen, bevor das Zip kommt
Ein Wegwerf-Workspace ohne konfigurierte Zugangsdaten außer dem Schlüssel des
Agenten selbst, ein geteilter Ordner für das Archiv und
Unmatched traffic auf Deny in Guardrails. Ergänzen Sie
allow web registry.npmjs.org und was das Projekt sonst noch abruft.
Speichern schiebt die Regeln ohne Neustart in laufende Sitzungen, sodass Sie
sie Zeile für Zeile lockern können, während sich der Build beschwert.
Die zwei Befehle, wenn Sie fertig sind
bromure-cli trace hostnames <workspace> sagt Ihnen, wohin der Code
gegangen ist, und bromure-cli trace leaks <workspace> gibt
No leaks detected. ✓ aus, wenn nichts versucht hat, ein Token
hinauszutragen. Dann Reset disk und Erase home…, und der Workspace
ist zurück auf dem Image, mit dem er begann.
Der Leser war nie die Kontrolle
Lesen Sie server.js trotzdem. Das ist ein guter Rat und eine schwache
Verteidigung, denn das nächste Archiv setzt den Import in Zeile 340 einer Datei,
die Sie aus keinem Grund öffnen würden, oder in ein minifiziertes Bundle, oder
hinter ein dynamisches require, zusammengesetzt aus zwei Zeichenketten. Die
drei Regeln von Mirage Kitten sind eine Wette gegen Ihre Aufmerksamkeit, und
Aufmerksamkeit geht an einem Freitag um 16 Uhr zur Neige, eine Stunde vor
Ablauf der Uhr.
Der Ort, an dem der Code läuft, geht nicht zur Neige. Der Import feuert so oder so und der abgekoppelte Prozess startet so oder so; was Ihnen zu wählen bleibt, ist, was dieser Prozess vorfindet, wenn er sich umsieht: eine Maschine mit Ihrem GitHub-Token, Ihrer kubeconfig, Ihrem SSH-Schlüssel und vierzig Repositories, oder eine Linux-VM mit einer Zip-Datei, einem Satz Platzhalter und einem Netzwerk-Socket, der nur dorthin geht, wohin Sie es gesagt haben.
Nehmen Sie die Aufgabe an. Nutzen Sie den Coding-Agenten, den die README Ihnen
verboten hat, und lassen Sie ihn zuerst server.js lesen.
Installieren Sie Bromure Agentic Coding und geben Sie dem Zip
eine Maschine, der das nichts ausmacht.