Zurück zu allen Beiträgen
Veröffentlicht am · von Renaud Deraison

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.js sei 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.js als 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.

Die Lieferkette ohne RegistryFront-Technical-Challenge.zipREADME.mddrei Stunden · keine KI„server.js ist fehlerfrei“server.jsZeile 1: require('colorized_terminal')die Datei, die tabu warnode_modules/[email protected]schon installiert · nicht auf npmSie starten die Appder Import forkt ein Implantatnode_modules/.cache/.320697f1/index.jsabgekoppelter HintergrundprozessNodeRabbit meldet sichplugplay.azurewebsites.netrgbteller.azurewebsites.net11 Befehle, dann 23was nie auslöst, und warumkeine Registry-AnfrageAltersgrenze — kein DatumIntegrität — kein LockfileProvenienz — nichts publiziertReputation — kein Paket zum PrüfenSkripte entfernt — keine InstallationTyposquat — kein Name zum Vergleichbleibt die Maschine, die es ausführt
Das Archiv kommt mit bereits installierten Abhängigkeiten, sodass die Lieferkette nie eine Registry berührt. Zeile eins der Datei, die Sie laut README nicht öffnen sollten, importiert ein Paket, das nirgendwo sonst existiert, und dieser Import forkt ein Implantat aus einem Punkt-Verzeichnis innerhalb von node_modules. Kein Installationsschritt, keine Lockfile-Prüfung, kein Veröffentlichungszeitstempel: Die Kontrollen, die Paket-Abrufe bewachen, haben keinen Abruf zu bewachen.

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.

auf dem Laptopdas Implantat erbt Ihr ganzes Kontopersist:projects:scanjedes Repo auf der Platte bekommt einen Hookpersist:vscodeeine Erweiterung, die in jedem Projekt lädtoutlook:emails · fs:read~/.aws · ~/.kube · ~/.git-credentialsAufräumenHooks finden, Schlüssel rotieren, hoffenin einem Workspaceeine Ubuntu-VM, ihre Platte, ihr Homeder Scan sieht nur, was Sie gemountet habennur geteilte Ordner, höchstens achtPersistenz landet auf einer Wegwerf-PlatteReset disk · Erase home · Checkpointsjede Credential-Datei enthält eine Fälschungghp_… · brm-k8s-… · sk-ant-api03-brm-…AufräumenPlatte und Home löschen, nichts rotieren
Ein Archiv, zwei Wirkungsradien. Auf einem Laptop erbt das Implantat das ganze Konto: Jedes Git-Repository, das es findet, bekommt einen Hook, der Editor bekommt eine Erweiterung, Mail und Cloud-Konfiguration sind lesbar, und die Zugangsdaten in der Shell-Umgebung sind die echten. In einem Workspace von Bromure Agentic Coding laufen dieselben Befehle gegen eine Linux-VM, die nur Platzhalter-Zugangsdaten enthält, nur die von Ihnen geteilten Ordner einbindet und das Netz nur über einen Socket auf dem Host erreicht.

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.