Ein verschlossener Versandkarton wird über einen Tresen gereicht, dazu ein Klemmbrett mit einem Formular. Auf dem Karton klebt ein Lieferschein, der die enthaltenen Artikel mit Anzahl und Preis auflistet. Ein Sinnbild für eine Stückliste, die beim Packen entsteht und zum Paket gehört, statt nachträglich erraten zu werden.

Eine Software-Stückliste, englisch Software Bill of Materials oder kurz SBOM, beantwortet drei Fragen: Was steckt in dieser Software, in welcher Version, unter welcher Lizenz? Ob die Antwort etwas taugt, entscheidet sich lange vor der Auslieferung. Ein aktueller Fall aus der Automobilbranche führt vor, was dabei schiefgehen kann, und er hat mehr mit PHP zu tun, als du zunächst vermuten würdest.

Ein Trojaner in der Lizenzliste

Die Kanzlei JUN Legal verklagt den chinesischen Automobilkonzern SAIC vor dem Landgericht München I. Der Vorwurf: Mit seiner Marke MG verletzt SAIC die Bedingungen von Open Source-Lizenzen. Unter dem Druck dieses Verfahrens hat SAIC eine Open Source-Attribution für den MG4 veröffentlicht, die die im Fahrzeug verbauten Komponenten samt Versionen, Lizenzen und Copyright-Hinweisen auflistet. Damit liefert der Konzern nach, was die Lizenzen verlangen und was seit über einem Jahr eingefordert wurde.

In dieser Liste steht ein bemerkenswerter Eintrag: Teardroid-phprat-95a4b56.

Teardroid ist ein Android-Botnet. Mit diesem Werkzeug wird Spyware für Android-Geräte gebaut, die Kontakte, SMS, Anruflisten und Standortdaten ausliest, Shell-Kommandos ausführt und ihre Beute an ein Kontrollpanel überträgt. Der Quellcode liegt offen auf GitHub. Ein Autohersteller erklärt hier also schriftlich, einen Trojaner auszuliefern.

Hat SAIC den MG4 tatsächlich mit einem Botnet-Trojaner infiziert? Mit Sicherheit nicht. Das ergäbe technisch keinen Sinn, und wer es täte, würde es kaum in die eigene Lizenzliste schreiben. Die tatsächliche Erklärung ist banaler und für uns interessanter.

SBOM-Erstellung ist keine Aufgabe für grep

Wie der Eintrag zustande kam, lässt sich an ihm selbst ablesen. Teardroid-phprat ist der Name eines GitHub-Repositories, 95a4b56 ein abgekürzter Commit-Hash. Repository plus Commit statt Paketname plus Versionsnummer: Diese Namenskonvention ist die Handschrift von Code-Scannern. Solche Werkzeuge gleichen Binärdateien oder Quellbäume per Fingerprinting gegen eine Datenbank von Repository-Snapshots ab. Irgendein Code-Schnipsel in der Fahrzeugsoftware hat auf einen Snapshot dieses Repositories gematcht, vermutlich eine gemeinsam genutzte Bibliothek oder eine kopierte Hilfsfunktion. Der Scanner hat den Treffer notiert, geprüft hat ihn niemand. So kam ein Android-Botnet in die Lizenzliste eines Automobilkonzerns.

Chan-jo Jun, der Anwalt hinter der Klage, fasst das so zusammen:

SBOM-Erstellung ist keine Aufgabe für grep; bei dieser Übung offenbaren Hersteller Abgründe.

Wer erst nach dem Build zu scannen beginnt, betreibt Forensik am eigenen Produkt. Das Verfahren produziert False Positives wie Teardroid, es übersieht statisch eingelinkten oder umbenannten Code, und es liefert eine Vermutung, wo eine belastbare Aussage verlangt ist.

Eine SBOM, in der offensichtlicher Unsinn steht, ist schlimmer als gar keine. Sie belegt, dass niemand sie gelesen hat, bevor sie veröffentlicht wurde, und entwertet damit auch jeden anderen Eintrag. Wenn Teardroid fälschlich in der Liste steht, welche Komponente fehlt dann fälschlich? Wer die ungeprüfte Ausgabe eines Werkzeugs als eigene Aussage veröffentlicht, macht sich dessen Fehler zu eigen. Für Code-Scanner gilt das genauso wie für Sprachmodelle.

Die Pointe mit dem PHP RAT

Ein Detail habe ich bisher unterschlagen. Das „php“ in Teardroid-phprat steht tatsächlich für PHP. Frühere Versionen beschrieben sich selbst als „easy to use Android PHP RAT“, also als Remote Access Trojan, und das Kontrollpanel, an das die Spyware ihre Beute meldet, war ursprünglich in PHP geschrieben.

Damit ist die Zuständigkeit geklärt, und ich kann zum ernsteren Teil kommen. Die Werkzeuge, die SAIC gefehlt haben, liegen im PHP-Ökosystem seit Jahren bereit.

Deklariert schlägt gescannt

Du kannst der Teardroid-Panne mit einem besser konfigurierten Scanner begegnen. Die eigentliche Frage ist aber, warum überhaupt gescannt werden musste. Wer rückwärts ermitteln muss, was in der eigenen Software steckt, hat die Kontrolle über die eigenen Abhängigkeiten längst verloren. Eine verlässliche SBOM fällt als Nebenprodukt eines Build-Prozesses an, der jede Abhängigkeit explizit deklariert.

Im PHP-Ökosystem heißt dieses Nebenprodukt composer.lock.

Verwaltest du deine Abhängigkeiten mit Composer, hast du diese Liste bereits, versionsgenau und über Hashes abgesichert. composer info zeigt jederzeit alle im Projekt verwalteten Abhängigkeiten samt exakter Version. Der Sache nach ist das eine SBOM, nur noch nicht in einem Format, das andere Werkzeuge verarbeiten können. Das Composer-Plugin cyclonedx-php-composer beispielsweise erzeugt aus den Composer-Metadaten eine standardkonforme SBOM im CycloneDX-Format, mit SPDX-Lizenzbezeichnern und Package URLs. So erwarten es Supply Chain-Werkzeuge und, Stichwort Cyber Resilience Act, zunehmend auch die Regulierung.

Der Unterschied zur Scanner-Methode ist grundsätzlich. Eine so erzeugte SBOM vermutet nichts, sondern protokolliert, was beim Bauen tatsächlich passiert ist. Jede aufgeführte Komponente wurde von Composer aufgelöst, mit dem in composer.lock festgeschriebenen Hash verifiziert und installiert. Ein Teardroid könnte in einer solchen Liste nur auftauchen, wenn ihn jemand per composer require installiert hätte, und dann stünde er dort zu Recht.

Das funktioniert allerdings nur, wenn Composer die einzige Quelle der Wahrheit ist. Damit ist die SBOM ein weiteres Argument für eine Disziplin, die ohnehin gelten sollte: Abhängigkeiten werden ausschließlich mit Composer verwaltet, ohne PEAR Installer daneben. Falls in deinem Projekt noch Code steckt, der auf diesem Weg ins Haus kam, ordnet mein Artikel „Schatten der Vergangenheit“ das historisch ein. Vor allem aber gilt: keine Bibliothek herunterladen und an Composer vorbei im eigenen Repository ablegen. Jede so „vendored“ Abhängigkeit ist ein blinder Fleck, bei dem irgendwann niemand mehr weiß, was in welcher Version woher kam. Aus solchen blinden Flecken bestehen die Softwarestände, die später mit Scannern durchleuchtet werden müssen.

Der blinde Fleck betrifft nicht nur die Stückliste. Seit Composer 2.9 blockiert der Resolver Updates auf Versionen mit bekannten Security Advisories, und seit Composer 2.10 hält das Dependency Policy Framework als bösartig gemeldete Pakete auch bei composer install fern. Wie das im Detail funktioniert, habe ich in „Der Türsteher im Dependency-Resolver“ und „Composer und Packagist im Supply Chain-Stresstest“ beschrieben. Diese Mechanismen arbeiten auf dem Abhängigkeitsgraphen, den Composer auflöst. Eine von Hand ins Repository kopierte Bibliothek steht nicht in diesem Graphen. Für sie greift weder das Blocking bekannter Schwachstellen noch eine Malware-Policy, und composer audit meldet sie nicht. Wird Jahre später eine Schwachstelle in ihr bekannt, erfährt das im Projekt niemand.

Wie PHPUnit es hält

Bei PHPUnit stellt sich die Frage in verschärfter Form. PHPUnit wird nicht nur über Composer verteilt, sondern auch als PHP Archive (PHAR), das alle Abhängigkeiten in einer einzigen Datei bündelt. Aus Sicht der Nutzenden ist ein PHAR zunächst genauso ein Binärblob wie die Firmware eines Autos, denn du siehst ihm nicht an, was drinsteckt.

Deshalb legt das PHPUnit-PHAR seinen Inhalt selbst offen. Drei Kommandozeilenoptionen, dokumentiert auf phpunit.de/verify.html, machen ihn sichtbar, ohne dass irgendetwas gescannt werden müsste.

php phpunit.phar --manifest listet jedes gebündelte Paket mit seiner exakten Versionsnummer auf.

php phpunit.phar --sbom gibt eine vollständige SBOM im CycloneDX-XML-Format aus. Für jede Komponente enthält sie Gruppe, Name, Version, Lizenz und Package URL. Supply Chain-Werkzeuge, die bekannte Verwundbarkeiten über den Abhängigkeitsbaum hinweg verfolgen, können das direkt verarbeiten.

php phpunit.phar --composer-lock gibt das composer.lock aus, mit dem die Abhängigkeiten beim Bau des PHAR installiert wurden. Das ist nützlich für reproduzierbare Builds und für quellbasierte Distributionen.

Diese SBOM entsteht nicht erst, wenn eine Rechtsabteilung unter Druck gerät. Sie steckt im Auslieferungsartefakt, weil sie beim Bauen ohnehin anfällt: Das PHAR wird aus einem Composer-verwalteten Abhängigkeitsbaum gebaut, und dieser Baum ist vollständig bekannt. Dazu kommt die kryptographische Absicherung. Jedes PHAR-Release ist PGP-signiert, SHA256-Hashes werden mitveröffentlicht, und PHIVE automatisiert die Verifikation. Signatur und SBOM ergänzen einander. Die Signatur belegt, dass das Artefakt unverändert von der Person stammt, die es signiert hat. Die SBOM belegt, was darin steckt.

Fazit

Der MG4-Fall wird vor Gericht als urheberrechtliche Frage verhandelt, und das zu Recht. Wer von Freier Software kommerziell profitiert, hat sich an deren Bedingungen zu halten. Ich veröffentliche seit über 26 Jahren Software unter freien Lizenzen und habe ein unmittelbares Interesse daran, dass diese Bedingungen keine Folklore sind.

Für Entwicklerinnen und Entwickler steckt in der Teardroid-Panne aber noch etwas anderes. Eine Lizenzliste ist nur so gut wie das Wissen, das hinter ihr steht, und dieses Wissen entsteht beim Bauen, nicht beim nachträglichen Durchleuchten. Jede Abhängigkeit wird deklariert, mit Version und Hash festgeschrieben und über einen einzigen Mechanismus verwaltet.

Wer mit Composer arbeitet, bekommt diese Disziplin größtenteils geschenkt. Dein composer.lock weiß, was in deiner Software steckt. Wer stattdessen hinterher scannen lässt, vermutet es nur, und vermutet dann schon mal einen Trojaner, den es dort nie gab.