Wichtigste Erkenntnisse
- Packagist indexiert Git-Tags, anstatt Artefakte zu hosten. Daher könnte eine veröffentlichte Version bis Juli 2026 unbemerkt auf einen anderen Commit umgeleitet werden.
- Bei zwei Vorfällen im Jahr 2026 wurde derselbe Trick angewendet: Bei einem wurde ein bösartiges Composer-Plugin registriert, beim anderen wurde der Autoloader von PHP gekapert.
- Ein engagierter
composer.lockfixiert den bereits aufgelösten Commit, sodass eine spätere Umbenennung des Tags nichts daran ändern kann, wascomposer installZüge. - Packagist lehnt nun die Neutagung veröffentlichter stabiler Versionen ab, aber alte Lockfiles und Entwicklungszweige müssen noch überprüft werden.
Alle anderen großen Registries haben schon früh die gleiche Entscheidung getroffen. Sobald eine Version veröffentlicht wurde, ist ihr Inhalt festgeschrieben. npm-Tarballs sind unveränderlich. Bei PyPI ist es nicht möglich, eine Datei unter demselben Namen erneut hochzuladen. crates.io erlaubt das „Yanking“, nicht jedoch das Löschen. Maven Central löscht veröffentlichte Artefakte überhaupt nicht.
Packagist hat sich anders entschieden. Es speichert keine Pakete. Es liest ein Git-Repository aus, indexiert alle gefundenen Tags und teilt Composer mit, woher die Pakete bezogen werden sollen. Eine Version wie 2.4.1 besteht aus einem Git-Tag sowie der von Packagist dafür gespeicherten Commit-Referenz. Bis Juli 2026 könnte dieses Tag gelöscht und mit einem anderen Commit erneut gepusht werden, wobei Packagist diesen Vorgang stillschweigend mitverfolgen würde.
Genau diese Schwachstelle wurde bei den Angriffen auf die Composer-Lieferkette im Jahr 2026 ausgenutzt, und bei PHP gab es innerhalb von drei Wochen zwei konkrete Beispiele dafür.
Warum Packagist eher ein Index als ein Register ist
Der architektonische Unterschied führt zu einem Unterschied in der Sicherheit. Wenn Composer eine Abhängigkeit auflöst, liefert Packagist Metadaten: den Paketnamen, die Versionen und den Speicherort der einzelnen Versionen, einschließlich der Verweise auf Quellcode und Distributionen, bei denen es sich in der Regel um einen Commit-Hash handelt. Der eigentliche Code stammt aus dem Quellcode-Repository oder aus einem Distributionsarchiv, das der Code-Host auf Anfrage anhand eines bestimmten Referenz-Hashs generiert. Kein Element in dieser Kette ist ein unveränderliches Artefakt, das Packagist selbst speichert.
| Registrierung | Versionsidentität | Kann sich eine veröffentlichte Version ändern? |
|---|---|---|
| npm | Ein veröffentlichter Tarball | Nein, Versionen sind unveränderlich. |
| PyPI | Eine hochgeladene Datei | Nein, hochgeladene Dateien können nicht ersetzt werden |
| crates.io | Ein veröffentlichtes Archiv | Nein, nur Yank |
| Maven Central | Ein veröffentlichtes Artefakt | Nein, Löschung nicht zulässig |
| Packagist | Ein Git-Tag sowie eine Referenz auf einen indizierten Commit | Erst seit Juli 2026 wurden Retags beachtet; davor war dies nicht der Fall. |
Diese letzte Zeile fasst das gesamte Problem der PHP-Lieferkettenrisiken zusammen, und es handelt sich dabei nicht um einen Fehler, den Packagist versehentlich eingeführt hat. Die Indizierung von Git sorgt für ungewöhnlich geringe Reibungsverluste bei der Veröffentlichung, und das ist seit langem einer der praktischen Vorteile von Packagist.
Realitätscheck: Wenn man dies als „Packagist-Schwachstelle“ bezeichnet, übersieht man, wo die Veränderbarkeit tatsächlich liegt. Git-Tags waren schon immer so konzipiert, dass sie verschoben werden können. Packagist hat diese Eigenschaft übernommen, indem es sich dafür entschieden hat, Repositorys zu indizieren, anstatt Artefakte zu hosten – und genau dieser Kompromiss ist es, der die Veröffentlichung eines Composer-Pakets vergleichsweise einfach macht.
Wie ein „Composer Supply Chain“-Angriff tatsächlich aussieht
Am 30. April 2026 hat jemand, der über ein kompromittiertes Dienstkonto verfügte, laut der GitHub-Hinweis. Die überarbeitete composer.json das Paket als Composer-Plugin deklariert und nach der Freigabe – entweder über eine interaktive Abfrage oder eine permissive allow-plugins Schauplatz – es spielte sich ab während composer install als Dropper, der die Bun-Laufzeitumgebung abrief und einen verschleierten Credential-Stealer ausführte. Der bösartige Tag war weniger als zwei Stunden lang aktiv, bevor die Betreuer ihn wieder auf sauberen Code umleiteten. Forscher nannten ihn „Mini Shai-Hulud“ und brachten ihn mit der Kampagne in Verbindung, die Der npm-Client von Intercom im selben Fenster.
Drei Wochen später wurde dieselbe Schwachstelle in viel größerem Umfang ausgenutzt. Ab dem 22. Mai um 22:32 Uhr UTC bis in den 23. Mai hinein schrieb ein Angreifer mit organisationsweitem Push-Zugriff alle vorhandenen Tags in vier Laravel-Lang-Lokalisierungspaketen um und verwies sie auf Commits in einem von ihm kontrollierten Fork, sodass niemals schädlicher Code in die offiziellen Repositorys gelangte. Socket zählte mehr als 700 umgeleitete historische Versionen, Aikido bestätigte 233, und der Hauptteil der Umschreibungen dauerte weniger als 90 Minuten. Diesmal kapern die Schadcode den Autoloader von PHP, anstatt ein Plugin zu registrieren.
Keiner der beiden Vorfälle erforderte eine neue Veröffentlichung, um irgendwo zu erscheinen. Kein Eintrag im Changelog, keine neue Versionsnummer, nichts, was ein Betreuer oder ein Abhängigkeits-Bot, der Versionsnummern überwacht, als Abweichung melden würde.
Vergleichen Sie das einmal damit, wie ein Angreifer bei npm vorgehen muss. Um dort eine legitime Version zu ersetzen, muss eine andere Versionsnummer veröffentlicht werden, da die Daten hinter einem bestehenden Paket und einer bestehenden Version nicht unbemerkt ausgetauscht werden können. Eine neue Version taucht in den Release-Feeds auf und löst Abhängigkeits-Bots aus. Das Verschieben eines Tags hatte keinerlei derartigen Effekt.
| Angriffsphase | npm | Packagist vor Juli 2026 |
|---|---|---|
| Schädlichen Code veröffentlichen | Neue Versionsnummer erforderlich | Die bestehende Version könnte neu verlinkt werden |
| Veranstaltung zur Veröffentlichung des neuen Produkts | Ja | Nein |
| Von einem Tool zur Versionsvergleichserkennung entdeckt | Ja | Was oft übersehen wird: Die Versionsnummer hat sich nie geändert. |
| Vom Upstream-Entwickler rückgängig gemacht | Das Fenster „Veröffentlichung rückgängig machen“ wird angezeigt | Das Tag könnte einfach wieder zurückverschoben werden |
Das Zurücksetzen hatte Vor- und Nachteile. Die Wiederherstellung erfolgte schnell – ein Betreuer konnte das Tag einfach zurücksetzen, was bei „intercom-php“ noch in derselben Nacht geschah –, doch aus demselben Grund wurde die Rückverfolgung schwieriger, da eine Versionsnummer nun auf einen anderen Commit verweisen konnte als zuvor. Das öffentliche Transparenzprotokoll von Packagist zeichnete diese Referenzänderungen auf, wodurch zumindest eine dauerhafte Aufzeichnung darüber entsteht, wann sich eine veröffentlichte Referenz verschoben hat.
Zwei verschiedene Nutzdaten, ein veränderbares Tag
Bei beiden Vorfällen im Jahr 2026 handelte es sich im strengsten Sinne um Composer-Supply-Chain-Angriffe, und beide begannen mit demselben Trick. Was danach geschah, war völlig unterschiedlich, und dieser Unterschied ist von großer Bedeutung, wenn es darum geht, zu entscheiden, was tatsächlich behoben werden muss.
Das kompromittierte Tag auf „intercom/intercom-php“ verwies auf einen Commit, der eine Änderung bewirkte composer.json um das Paket als Composer-Plugin zu deklarieren. Das ist die Variante dieses Angriffs, auf die die meisten Berichte zurückgreifen, da sie sich nahtlos in das Plugin-System von Composer einfügt. Ein zugelassenes Plugin wird während composer install und composer update, bevor irgendein Teil Ihres Anwendungscodes ausgeführt wird.
Der Laravel-Lang-Kompromiss betraf die Plugin-Registrierung in keiner Weise. Er fügte ein bösartiges src/helpers.php in die autoload.files Eintrag, sodass die Nutzlast ab dem Zeitpunkt der Installation des Pakets bei jeder PHP-Anfrage ausgeführt wurde. Gleicher „moved“-Tag, gleiches Vertrauen, das Composer in das setzt, worauf sich ein Tag auflöst – zwei völlig unterschiedliche Ausführungswege. Eine Korrektur für das eine hilft beim anderen überhaupt nicht weiter.
In unserem ausführlichen Leitfaden zur Laravel-Sicherheit können Sie sich außerdem mit den wichtigsten Schutzstrategien des Frameworks vertraut machen.
Die Sicherheit des Composer-Plugins weist eine echte Schwachstelle auf
Ein verschobenes Tag liefert Code. Dieser muss von etwas ausgeführt werden, und bei einem Angriff im „Intercom“-Stil ist dieses „Etwas“ das Plugin-System. Ein Paket, das Folgendes deklariert: "type": "composer-plugin" wird geladen und kann im Rahmen der Installations- und Aktualisierungsläufe von Composer selbst Code ausführen, sobald dies zugelassen ist. Das ist das beabsichtigte Verhalten, da Plugins dazu dienen, die Art und Weise zu verändern, wie Composer Komponenten installiert – was bedeutet, dass sie ausgeführt werden, bevor die Installation abgeschlossen ist.
Mit Composer 2.2.0 wurde die allow-plugins config, und die aktuelle Standardeinstellung lässt überhaupt nichts zu. In composer.json Es sieht so aus.
{
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"phpstan/extension-installer": true
}
}
}
Mit allow-plugins Ist diese Option konfiguriert, weigert sich Composer, Plugins auszuführen, die nicht in der Liste aufgeführt sind. Ein nicht aufgeführtes Plugin löst bei interaktiver Ausführung von Composer eine Fehlermeldung aus, und bei einer nicht-interaktiven Ausführung bricht Composer den Vorgang ab, anstatt stillschweigend fortzufahren – es sei denn, das Plugin ist als optional deklariert. Ein fehlgeschlagener Build ist besser als eine stillschweigende Installation.
Vorsicht: Überprüfe, ob jemand "allow-plugins": true nur um eine Eingabeaufforderung zu schließen. Diese eine Zeile fügt alle aktuellen und zukünftigen Plugins zur Whitelist hinzu und deaktiviert den Whitelist-Schutz vollständig. Ausführen von composer config allow-plugins zeigt, was in einem Projekt tatsächlich konfiguriert ist, und false ist der Wert, der alles leugnet.
Für einen Build, der überhaupt keine Plugins benötigt, sind die Flags zwar etwas plump, aber effektiv.
composer install --no-plugins --no-scripts --no-dev
Wenn Ihre Produktionsumgebung keine Plugins oder Composer-Skripte benötigt, ist es sinnvoll, diese dort zu deaktivieren, sie aber auf den Entwicklerrechnern weiterhin verfügbar zu lassen.
Autoload-Dateien sind der unauffälligere Weg
allow-plugins hilft nichts gegen Angriffe im Laravel-Lang-Stil. autoload.files ist keine optionale Funktion, die man hinter einer Berechtigungsabfrage verbergen kann. Es ist eine gängige Methode, mit der PHP-Pakete Hilfsfunktionen bereitstellen, und Composer muss der Autoload-Konfiguration jedes Pakets vertrauen, damit das Ökosystem überhaupt funktioniert.
Genau das hat diese Variante des Angriffs so schwerwiegend gemacht. Composer fragt, ob man einem Paket vertraut, es als Plugin auszuführen. Es fragt jedoch niemals, ob man einem Paket vertraut, automatisch geladene Dateien zu definieren. Vor der Einführung der Malware-Richtlinie in Composer 2.10 und der Änderung der Unveränderlichkeit bei Packagist bestand der wichtigste Schutz hier darin, das umgeschriebene Tag gar nicht erst aufzulösen – weshalb die Lockdatei eine so große Rolle spielte.
Das Einzige, was Composer richtig macht und npm nicht
Composer-Skripte, die über eine Abhängigkeit deklariert wurden, werden nicht ausgeführt. Es werden nur die Skripte des Stammpakets ausgeführt. Ein bösartiges post-install-cmd Eine Einbettung in vier Ebenen tief in deinem Verzeichnisbaum hat keinerlei Auswirkungen. Das ist eine deutlich bessere Standardeinstellung als bei npm, wo jedes Paket im Verzeichnisbaum ein Postinstall-Skript enthalten kann, das während der Installation ausgeführt wird.
| Mechanismus | Läuft für Abhängigkeiten |
|---|---|
| npm-Lebenszyklus-Skripte | Ja, jedes Paket im Baum |
Python .pth Start-Hooks |
Ja, beim Start des Interpreters |
Rost build.rs |
Ja, während der Kompilierung |
| Composer-Skripte | Nein, nur das Root-Paket |
| Composer-Plugins | Ja, sofern dies zulässig ist |
Wussten Sie schon: Das Ignorieren von Abhängigkeitsskripten ist eine bewusste Designentscheidung von Composer und keine Sicherheitsmaßnahme, die nach einem Vorfall nachträglich eingeführt wurde. Dies ist eines der Details, die beim Vergleich der beiden Ökosysteme oft übersehen werden.
Was Ihre Composer-Lockdatei aufzeichnet und was sie übersieht
composer.lock bietet mehr Schutz, als sein Ruf vermuten lässt, und dieser Schutz hat eine bestimmte Grenze, die zufällig sowohl den Plugin-Angriff als auch den Autoload-Angriff abdeckt, da er eine Ebene unterhalb dieser beiden Angriffsarten wirkt.
Für jedes Paket speichert die Sperrdatei die ermittelte Version zusammen mit den Verweisen auf Quelle und Distribution – in der Regel den Git-Commit-Hash hinter dieser Version. Führen Sie folgenden Befehl aus: composer install und Composer fordert genau diesen Commit an, anstatt das, worauf das Tag heute verweist, sodass eine spätere Neufassung des Tags niemals ins Spiel kommt. Die Garantie bezieht sich darauf, welcher Commit angefordert wird, nicht auf eine Prüfsumme der zurückgegebenen Bytes, da die Code-Hosts diese Archive auf Abruf generieren und die Sperrdatei keinen verwertbaren Hash für sie enthält. Operationen, bei denen Abhängigkeiten neu aufgelöst wurden, stellten die Sicherheitslücke dar, und dazu gehören auch Teilaktualisierungen wie composer update foo -W. Der Sicherheitsartikel von The Packagist vertritt denselben Standpunkt in Bezug auf Lock-Dateien, was man wissen sollte, da in vielen Berichten über Laravel-Lang behauptet wurde, Lock-Dateien böten überhaupt keinen Schutz.
| Betrieb | Behält die gesperrte Referenz bei | Einem verschobenen Tag ausgesetzt |
|---|---|---|
composer install mit festgeschriebener Sperrdatei |
Ja | Nein |
composer update |
Nein, es wird erneut aufgelöst | Ja |
| Teilaktualisierung eines Pakets | Nur bei Einträgen, die es unberührt lässt | Ja, bei allem, was neu aufgelöst wird |
composer require newpackage |
Teilweise | Ja, für die neue Anforderung und alle damit verbundenen Abhängigkeiten, die sich daraus ergeben |
| Neuer Klon, bei dem die Lock-Datei in der .gitignore-Datei ignoriert wird | Nein | Ja |
composer install in einem Docker-Build, ohne dass die Sperre kopiert wurde |
Nein | Ja |
Ein Dockerfile, das nur composer.json Bevor die Installation ausgeführt wird, gibt es keine Sperrdatei, auf der aufgebaut werden kann, und der Schutz verschwindet, ohne dass jemand auch nur eine Zeile des Anwendungscodes berührt hat. Kopieren composer.lock Gleichzeitig sorgt es dafür, dass der Schutz wiederhergestellt wird.
git ls-files composer.lock
Falls dies keine Ergebnisse liefert, composer.lock wird in diesem Repository nicht nachverfolgt, sodass bei jedem „clean“-Build die Abhängigkeiten neu aufgelöst werden, und zwar anhand der Metadaten, die zu diesem Zeitpunkt in der Registry vorliegen.
Malware-Prüfung und die Änderung der Unveränderlichkeit im Juli
Die Vorfälle im Mai blieben nicht unbeantwortet. Nils Adermann und Igor Benko veröffentlichten am 27. Mai eine Sicherheits-Roadmap für Composer und Packagist, und Composer 2.10 wurde am nächsten Tag veröffentlicht.
Ein Aspekt dieser Veröffentlichung ist hier von unmittelbarer Bedeutung. Packagist importierte bereits seit März die Ergebnisse der Malware-Erkennung von Aikido, und erst mit Version 2.10 wurden diese in der Praxis nutzbar. Als verdächtig markierte Versionen werden aus dem Auflösungspool entfernt, sodass composer update, composer require und composer create-project kann sie nicht erreichen, und composer install Es kommt zu einem lautstarken Fehler, wenn sich bereits eine markierte Version in Ihrer Lockdatei befindet, anstatt sie stillschweigend zu übernehmen. composer audit gibt dieselben Versionen an und schlägt standardmäßig fehl.
Im Juni erhielten auch Private-Packagist-Organisationen Kontrollmöglichkeiten zur Einschränkung von Plugins, sodass eine Organisation im Voraus festlegen kann, welche Pakete als Composer-Plugins fungieren dürfen. Ist diese Funktion aktiviert, wird eine manipulierte Version, die sich plötzlich als Plugin deklariert, aus den Update-Metadaten entfernt und gelangt niemals zum Composer-Resolver. Installationen aus einer bestehenden Lockdatei erfordern jedoch zusätzlich die separate Einstellung zur Download-Sperre von Private Packagist.
Die umfassendere strukturelle Änderung erfolgte am 7. Juli, als Packagist unveränderliche Versionen einführte. Sobald eine stabile Version veröffentlicht ist, ändert sich der dahinterstehende Git-Verweis nicht mehr. Unabhängig davon, ob das Tag gelöscht, neu erstellt oder stromaufwärts verschoben wird, behält Packagist den ursprünglichen Verweis bei, fügt auf der Paketseite einen Hinweis „Retag blocked“ hinzu und benachrichtigt alle Betreuer per E-Mail.
Dev-Versionen, die Zweige nachverfolgen, werden weiterhin verschoben, da dies der Sinn eines Zweigs ist, und die Regel bezieht sich auf die Zuordnung zwischen Version und Commit und nicht auf die Archivdaten, da die meisten ZIP-Dateien nach wie vor bei Bedarf vom Code-Host generiert werden. Gesperrte Referenzänderungen, weiche Löschungen und Wiederherstellungen werden alle im öffentlichen Transparenzprotokoll erfasst.
Nichts davon bereinigt rückwirkend eine Sperrdatei, in der ein bösartiger Commit vor der Einführung der Schutzmaßnahmen erfasst wurde – und genau deshalb composer.lock und allow-plugins Sie müssen Ihre eigenen Projekte dennoch überprüfen, unabhängig davon, was ein Scanner anzeigt.
Pakete überprüfen, bevor ein Tag überhaupt aufgelöst wird
Bei beiden Angriffen gelangte die gefährliche Referenz zum Zeitpunkt der Auflösung in den Abhängigkeitsgraphen, lange bevor ein Plugin geladen wurde oder ein Autoloader-Hijack ausgelöst wurde. Composer 2.10 führt nun auch eine Malware-Prüfung zum Zeitpunkt der Installation ein, wobei diese Prüfung jedoch weiterhin davon abhängt, dass jemand die betreffende Version zuvor als bedenklich gekennzeichnet hat.
SecureChain für PHP bietet Teams eine aus dem Quellcode neu kompilierte und auf Malware geprüfte Paketquelle, anstatt direkt über Upstream-VCS-Referenzen zu verweisen. Jedes Paket wird in einer isolierten Umgebung neu kompiliert und geprüft, bevor es zur Installation bereitgestellt wird – Sie erhalten also genau das, was verifiziert wurde. Sicherheitslücken werden im Rahmen des SLA kontinuierlich behoben, und der „Endless Lifecycle Support“ gewährleistet die weitere Abdeckung, selbst wenn ein Betreuer keine Updates mehr bereitstellt. Dies ist besonders wichtig bei aufgegebenen Paketen, bei denen die Aufsicht durch den Betreuer, die unerwartete Änderungen im Upstream-Bereich aufdecken würde, bereits weggefallen ist.
Nichts davon ersetzt composer.lock, allow-plugins oder composer audit. Damit wird der Teil des Problems gelöst, der sich oberhalb dessen befindet, was Ihre eigenen Werkzeuge erkennen können. Sehen Sie selbst, wie SecureChain für PHP funktioniert.
FAQ
Was ist ein „Composer“-Supply-Chain-Angriff?
Ein Angriff, bei dem ein PHP-Paket kompromittiert wird, bevor es überhaupt in Ihr Projekt gelangt – in der Regel über ein kompromittiertes Repository, ein verschobenes Git-Tag oder ein bösartiges Composer-Plugin und nicht über eine Schwachstelle in Ihrem eigenen Code. Es handelt sich um eine spezifische Ausprägung des allgemeineren Musters von Lieferkettenangriffen, von denen Open-Source-Register in allen Programmiersprachen betroffen sind.
Kann sich die Versionsnummer eines Packagist-Pakets nach der Veröffentlichung ändern?
Historisch gesehen war dies möglich. Packagist indizierte Git-Tags, anstatt unveränderliche Artefakte zu speichern, sodass ein Tag gelöscht und erneut an einen anderen Commit angehängt werden konnte, ohne dass sich die Versionsnummer änderte. Seit Juli 2026 weist Packagist jeden Versuch zurück, die Quellreferenz einer bereits veröffentlichten stabilen Version zu ändern – eine serverseitige Schutzmaßnahme, die unabhängig davon gilt, welche Composer-Version ein Entwickler verwendet. Entwicklungsversionen, die Zweige nachverfolgen, werden weiterhin aktualisiert, und die Änderung kann historische Installationen nicht sicher machen, wenn diese zuvor einen bösartigen Commit aufgelöst haben.
Werden die Abhängigkeitsskripte von Composer während der Installation ausgeführt?
Nein. Es werden nur die Skripte des Root-Pakets ausgeführt. Ein bösartiges post-install-cmd Eine von einer Abhängigkeit angegebene Version wird ignoriert, was eine strengere Standardeinstellung ist als bei npm.
Wie führen Composer-Plugins Code aus?
Ein Paket mit "type": "composer-plugin" kann im Rahmen der Installations- und Aktualisierungsvorgänge von Composer selbst Code ausführen, sobald er installiert und freigegeben wurde. Ab Composer 2.2.0 müssen Plugins explizit über die allow-plugins config.
Schützt die Datei „composer.lock“ vor einem verschobenen Git-Tag?
Ja, wenn die Sperrdatei bereits den berechtigten Commit enthält, denn composer install verwendet diese gesperrte Referenz, anstatt das Tag erneut aufzulösen. Dies bietet keinen Schutz composer update, neu composer require Aufrufe oder Builds, bei denen die Sperrdatei fehlt.
Was bewirkt die Einstellung „allow-plugins“ auf „true“?
Dadurch werden alle aktuellen und zukünftigen Composer-Plugins zugelassen, was den Schutz vollständig aufhebt. Ersetze diese Einstellung durch eine explizite Liste der spezifischen Plugins, die dein Projekt benötigt.



