Niemand hat eine neue Version veröffentlicht
Wordfences Advisory vom 9. August zur BdThemes-Kompromittierung beschreibt einen Supply-Chain-Angriff ganz ohne Supply Chain. Kein Release, kein Install-Skript, kein Typosquat, keine geänderte Datei im Plugin-Repository. Angreifer verschafften sich Schreibzugriff auf den Storage-Bucket des Herstellers und änderten einen JSON-Feed, den 350.000 Sites bei jedem Laden einer Admin-Seite abriefen. Für Entwickler lautet die Frage, die das aufwirft: Was darf die Maschine, auf der dein Coding-Agent läuft, schreiben, und für wen?
Jede Supply-Chain-Kontrolle, die du hast, prüft Dinge, die veröffentlicht werden. Diese hier hat nichts veröffentlicht und trotzdem 350.000 Sites erreicht.
Am 7. August 2026 bekam Wordfence erste Meldungen über WordPress-Sites mit BdThemes-Plugins, auf denen Administratorkonten wuchsen, die niemand angelegt hatte. Die Liste umfasst sieben Plugins: Element Pack Addons for Elementor, Prime Slider, Pixel Gallery, Ultimate Post Kit, Ultimate Store Kit, Live Copy Paste und Smart Admin Assistant. Element Pack allein hat über 100.000 aktive Installationen, die sieben zusammen überschreiten 350.000.
Bis zum 8. August hatte der Hersteller seine API bereinigt und WordPress.org hatte alle sieben Plugins bis zum Abschluss der Untersuchung zurückgezogen. Wordfence veröffentlichte seine PSA zur Kompromittierung am Tag darauf, und der zentrale Befund ist eine Verneinung: Niemand hatte den Plugin-Code angerührt.
Es wurde nichts veröffentlicht
BdThemes-Plugins enthalten eine interne Komponente namens Biggopti, deren Aufgabe klein und langweilig ist: Werbebanner von der Remote-API des Herstellers holen und im WordPress-Admin-Dashboard zeichnen. Die Bannerdaten sind eine statische JSON-Datei in einem DigitalOcean-Spaces-Bucket, ausgeliefert hinter Cloudflare.
Angreifer erlangten Schreibzugriff auf diesen Bucket. Das ist der gesamte Einbruch.
Biggopti nahm das Feld display_id aus der JSON-Antwort und ließ es
unescaped in ein HTML-id-Attribut fallen, eine Cross-Site-Scripting-Lücke, die
Wordfence mit
CVSS 5.4, mittel
bewertete und bis zum 1. März 2026 zurückverfolgte, als ein Entwickler sie
einmal schrieb und von einem Plugin ins nächste kopierte. Eine display_id mit
einem onanimationstart-Handler ist deshalb JavaScript, und sie läuft im
Browser jedes eingeloggten Administrators, bei jedem Laden einer
wp-admin-Seite, auf jeder Site mit installiertem Plugin.
Die Payload nutzte diesen Platz gut. Ein Skript namens w2.js fragte bei einem
Command-and-Control-Server nach Zielvorgaben, also der Maschine, mit der ein
Angreifer einer Payload sagt, welche Sites sich zu übernehmen lohnen, und legte
dann über die WordPress-REST-API versteckte Administratorkonten an, auf dem
Rücken der Sitzung und der Nonces des echten Administrators. Eine Variante
leitete vorhersagbare Benutzernamen aus dem Hostnamen der Site ab. Ein falsches
Plugin, oft mit einem Namen wie wp-smart-thumbnails, lieferte eine Web-Shell
unter emer-run.php, also ein Skript, das ein Angreifer durch Aufruf einer ganz
gewöhnlichen URL steuern kann. Must-use-Plugins, die WordPress bei jedem Request
lädt und vor dem Plugin-Bildschirm verbirgt, trugen eine
Magic-Login-Hintertür und ein Modul, das die neuen Konten verbarg, indem es die
vom Admin-Bildschirm angezeigten Benutzersummen umschrieb.
Zeitstempel in den vergifteten Datensätzen legen den frühestmöglichen Beginn auf den 23. Juni 2026, sieben Wochen, bevor es jemandem auffiel.
Halte das gegen die Kontrollen, die ein sorgfältiges Team betreibt. Versions-Pinning: Die Versionen änderten sich nie. Lockfiles und Integritäts-Hashes: Sie passten, weil die Dateien, die sie abdeckten, unberührt waren. Prüfung der Install-Skripte: Es gab keine Installation. Typosquat-Erkennung: richtiger Name, richtiger Hersteller, richtiger Repository-Eintrag. Code-Review des Diffs zwischen Releases: kein Diff, weil kein Release. Zertifikat und CDN: beide bei bester Gesundheit. Cloudflare und TLS bewiesen, dass das JSON unverändert aus genau dem Bucket bei der Site ankam, aus dem es kommen sollte, und genau das machte es brauchbar.
Der Vorfall beginnt mit einem Zugangsdatum, das schreiben konnte
Liest man das Advisory von der Herstellerseite her, schrumpft die Angriffskette auf einen Schritt. Jemand erlangte Schreibzugriff auf einen Storage-Bucket. Alles danach ist Folge.
Upstream-Kompromittierungen kommen immer wieder in dieser Form. Der Wurm im npm-Scope von Red Hat, das Repository, das wirklich Microsoft gehörte, die Extension-Marktplätze, die Container-Registries: In jedem davon ist die Payload der Teil, über den geschrieben wird, und der Teil, auf den es ankam, war jemand, der am Ende Zugangsdaten hatte, mit denen sich ein Artefakt überschreiben lässt, das andere Leute regelmäßig abrufen.
Wo lebt so ein Zugangsdatum? In der Umgebung eines Entwicklers. Ein
DIGITALOCEAN_ACCESS_TOKEN in einer Shell, eine doctl-Konfiguration in
~/.config, ein Registry-Login in ~/.docker/config.json, ein AWS-Profil, ein
GitHub-Token in ~/.git-credentials, ein SSH-Schlüssel, der pushen kann. Auf
einem Laptop, in einem Terminal, neben dem Projekt. 2026 ist neben dem Projekt
auch der Ort, an dem dein Coding-Agent läuft.
Die meisten Beiträge in diesem Blog beschreiben einen Weg, diesen Agenten gegen dich zu wenden. Ein Bugreport, der einen Befehl ausführte. Eine Webseite, die die Konfiguration des Agenten umschrieb. Ein Kommentar, den niemand sehen konnte. Ein Skill, der mit deiner Identität lief. Drei Harness-Bypässe in einem Black-Hat-Vortrag. Nimm den Katalog als gegeben und nimm an, dass an irgendeinem Dienstag einer davon bei dir landet.
Wie schlimm dieser Dienstag wird, hängt an einer Frage, die du heute beantworten kannst, ohne zu wissen, welche Injektion gewinnt: Was darf diese Maschine schreiben, und für wen? Auf einem Laptop lautet die Antwort: alles, was du schreiben darfst, und wenn du irgendetwas ausliefert, gehört dazu das Artefakt, das mehrere hunderttausend Fremde ungeprüft abrufen.
Das Token ist nicht in der Maschine
Bromure Agentic Coding lässt den Agenten in einer wegwerfbaren Linux-VM auf Apple Silicon laufen, und jedes Byte seines Verkehrs kreuzt einen Proxy auf dem Host, außerhalb der Box, in der der Agent läuft. Bromure ordnet die Zugangsdaten um diese Grenze herum an, statt sie hineinzulegen.
Trag dein persönliches DigitalOcean-Zugangstoken im Credentials-Panel ein,
und Bromure behält den echten Wert auf dem Mac. Was in der VM landet, ist eine
Attrappe: eine Umgebungsvariable DIGITALOCEAN_ACCESS_TOKEN und eine
~/.config/doctl/config.yaml, die doctl ohne doctl auth init funktionieren
lassen und einen Platzhalter enthalten. Wenn die VM eine Anfrage an
api.digitalocean.com stellt, setzt der Host-Proxy auf der Leitung das echte
Token ein, für dieses Ziel und für nichts sonst.
Alles, was in dieser VM läuft, kann die Umgebung lesen, die Dotfiles greppen und
die ganze Platte ablaufen: der Agent, eine Abhängigkeit, ein Build-Schritt, ein
Shell-Befehl aus einem vergifteten Issue. Was dabei zusammenkommt, ist brm_….
Dieselbe Anordnung deckt den Rest ab: GitHub-, GitLab- und Bitbucket-Tokens,
Container-Registry-Logins, geschrieben als gefälschter Base64-Blob in
~/.docker/config.json, eine synthetische kubeconfig mit
Wegwerf-Client-Zertifikaten, Linear, die HTTPS-Datenbank-Endpunkte und alles
Weitere, was du unter Other API keys hinzufügst. AWS geht noch weiter: Der
Host signiert jede Anfrage mit SigV4 und dem echten Secret neu, sodass ein
Umgehen des Proxys eine InvalidSignatureException einbringt statt eines
unautorisierten Schreibvorgangs.
Guardrails entscheidet über das Verb, auf dem Mac
Dieser Vorfall brauchte eine einzige Operation: ein Objekt in einem Bucket
überschreiben. Guardrails ist eine Policy-Engine im Host-Proxy, mit einem
Modus pro Ressource: Off, Block destructive oder Read-only. Stell
DigitalOcean auf Read-only, und jede Mutation an api.digitalocean.com kommt
als hartes 403 zurück, das der Agent als ganz gewöhnlichen API-Fehler
meldet. Bromure trifft diese Entscheidung in macOS. Es gibt keine
Einstellung in der VM, die man ändern könnte, keine Umgebungsvariable zum
Löschen und keine Datei zum Bearbeiten.
Veröffentlichen fragt vorher
Jeder Zugangsdaten-Eintrag hat Require approval to use. Schalte es ein, und jeder Tausch Attrappe→echt löst auf dem Host einen Zustimmungsdialog aus, bevor der echte Wert auf die Leitung geht, mit zeitlich begrenzten Freigaben auf dem SSH-Pfad: fünf Minuten, eine Stunde, der Rest der Sitzung. Ein Release zu pushen ist etwas, das du vorhattest, und es kostet dich einen Klick. Ein Schreibvorgang, den du nicht gestartet hast, taucht als Dialog auf, den du nicht erwartet hast, in einem Moment, in dem du keinen erwartetest.
Registries und git bekommen dieselbe Behandlung
Guardrails klassifiziert Container-Registry-Verkehr nach Methode gegen den
Hostnamen der Registry selbst: GET und HEAD sind ein Pull, PUT und
POST sind ein Push, DELETE ist destruktiv. GitHub, GitLab und Bitbucket
sind sowohl für die REST-API als auch für git über HTTPS abgedeckt, wo
git-receive-pack als Schreibvorgang zählt und im Read-only-Modus
blockiert wird, während Fetches immer durchgehen. Ein Profil, das deinen
gesamten Abhängigkeitsgraphen liest und nichts pusht, sind zwei
Einstellungen.
Die Umgebung ist eine Liste, die du geschrieben hast
Ein Bromure-Profil sieht die Mac-Ordner, die du mit ihm geteilt hast, bis zu
acht, jeder unter /home/ubuntu eingehängt. Alles andere auf deinem Mac ist
nicht eingehängt, nicht erreichbar und nicht auflistbar. Der Satz an
Zugangsdaten ist der, den du diesem Profil hinzugefügt hast, sodass ein
Profil, mit dem du ein Frontend baust, keine Cluster-Zugangsdaten enthält,
die irgendetwas finden könnte, versteckt oder nicht.
Der Laufzeit-Abruf kreuzt den Proxy ebenfalls
Nimm nun die stromabwärts gelegene Seite der Geschichte, in der du einer der 350.000 bist. Was du dort fangen kannst, hängt davon ab, wo du die Prüfung platzierst.
Bromures Supply Chain-Panel prüft Paketabrufe über npm, PyPI, Cargo,
RubyGems, Maven, NuGet, Go-Module und Packagist: eine standardmäßig aktive
Altersschranke, die alles ablehnt, was in den letzten zwei Tagen veröffentlicht
wurde, OSV-Abfragen, socket.dev oder Delpi als Filteranbieter, das Entfernen von
Install-Skripten, das den Tarball umschreibt und den Hash in den
Registry-Metadaten nachzieht, damit npm weiterhin verifiziert, und eine
Rückfrage, bevor per Lockfile gepinnte Tarballs unverändert durchgehen. Die
.npmrc und pip.conf in der VM können diese Regeln verschärfen und können sie
nicht lockern.
Diese Regeln verdienen ihr Geld wegen des Ortes, an dem Bromure sie durchsetzt. Der Host-Proxy wendet sie auf eine Anfrage an statt auf ein Paket, sodass eine JSON-Datei, die zur Laufzeit von einem Build-Schritt, einer CLI, einem MCP-Server oder der Anwendung selbst aus einem Hersteller-Bucket gezogen wird, dieselbe Grenze kreuzt wie ein Tarball. Alles, was die VM zum Netz sagt, kreuzt sie.
Das verändert, wie die sieben Wochen aussehen. Steht Session trace auf Activity, protokolliert Bromure Host, Status, Latenz, den Tauschbericht und etwaige Leck-Warnungen für jede Anfrage der VM. Stell es auf Everything, und es behält auch die Bodies, für jeden Host, verschlüsselt mit demselben Keychain-Schlüssel wie deine Profil-Secrets und lesbar im Trace Inspector. Der Tag, an dem ein Feed, von dem dein Build abhängt, anders zu antworten beginnt, ist ein Diff, das du öffnen kannst, auf dem Mac, in einem Log, auf das die VM keinen Schreibzugriff hat.
Ein vergifteter Feed ist am Ende auch nur Text, der auf einer Maschine
ankommt. Wenn er den Agenten als tool_result, als Datei, als abgerufene Seite
oder als Anweisungsdatei wie CLAUDE.md oder AGENTS.md erreicht, bewertet ihn
das Prompt Injection-Panel auf dem Gerät mit einem lokalen Modell, und nichts
verlässt den Mac. Wähle Ask me what to do, und die Anfrage pausiert mit dem
markierten Abschnitt vor Augen; wähle Block unilaterally, und der Agent
bekommt ein hartes 451. Wenn die Maschine weg soll, setzt Erase home
/home/ubuntu zurück und Reset to base klont die Systemplatte des
Workspace neu aus dem Basis-Image.
BdThemes wird das Escaping reparieren, der Bucket bekommt engere Schlüssel, und die nächste Fassung dieser Geschichte kommt mit einem anderen Hersteller und einem anderen Feed. Was bleibt, ist die Anordnung darunter. Deine Entwicklungsumgebung hält eine Handvoll Zugangsdaten, von denen jedes etwas umschreiben kann, das Tausende von Menschen regelmäßig abrufen, und dieselbe Umgebung lässt jetzt einen Agenten laufen, der den ganzen Tag Text von Fremden liest.
Die meisten Beiträge hier fragen, was dein Agent lesen dürfen sollte. Beantworte auch die andere Frage, die auf einem Laptop unangenehm und in einem Profil leicht ist: Was darf er schreiben, und für wie viele Menschen?
Installiere Bromure Agentic Coding und gib dem Agenten eine Maschine, die keinen Schlüssel zu irgendetwas hat, das du ausliefert.