Ein kritischer PhpSpreadsheet-Bug umging einfach seinen eigenen Patch

Vor einigen Wochen hat das PHPOffice-Team einen Fix für einen Phar-Deserialisierungsfehler in PhpSpreadsheet veröffentlicht, der als CVE-2026-34084 erfasst wurde. Forscher fanden dann einen Weg, diesen Fix direkt zu umgehen, und der Bypass ist nun eine eigenständige Sicherheitslücke: CVE-2026-45034, bewertet mit 9,2 kritisch auf CVSS v4. Das Advisory GHSA-87m4-826x-3crx wurde am 7. Juni öffentlich. Wenn PhpSpreadsheet irgendwo in Ihrem Stack Tabellendateien liest, ist das hier heute zehn Minuten Ihrer Zeit wert.
Der ursprüngliche Patch fügte eine Hilfsfunktion namens File::prohibitWrappers hinzu, die Stream-Wrapper wie phar:// ablehnen sollte. Dazu rief er parse_url($filename, PHP_URL_SCHEME) auf und warf eine Ausnahme, wenn das Schema als Zeichenkette mit mehr als einem Zeichen zurückgegeben wurde. Das Problem ist, dass diese Prüfung nicht dasselbe ist wie „Verwendet dieser Pfad einen Wrapper?“ Übergibt man einen Pfad der Form phar:///pfad/datei.phar/inner – mit drei Schrägstrichen nach dem Schema – gibt parse_url den booleschen Wert false statt der Schemazeichenkette zurück. Die Prüfung wird übersprungen, die Hilfsfunktion kehrt ohne Beanstandung zurück, und IOFactory::load() erreicht dennoch den Phar-Wrapper.
Warum die PHP-Version wichtiger ist als der CVSS-Score
Was als Nächstes passiert, hängt vollständig von Ihrer PHP-Version ab – und genau das ist der Teil, den viele Teams falsch lesen. Unter PHP 7.x reicht es aus, den Phar-Wrapper über is_file anzusprechen, damit PHP die Phar-Metadaten deserialisiert, was __wakeup und __destruct auf vom Angreifer kontrollierten Objekten auslöst. Das ist vollständige Remote-Codeausführung. Die Forscher wiesen dies im 1.x-Branch unter PHP 7.4 mit einer Gadget-Kette nach, die eine Marker-Datei schrieb. Unter PHP 8.x wurde die automatische Metadaten-Deserialisierung bei einfachen Dateioperationen entfernt, sodass der Fehler auf der PhpSpreadsheet-Ebene auf ein Phar-Dateilese-Primitiv reduziert wird. Codeausführung ist nur möglich, wenn etwas Nachgelagertes in Ihrer App Phar::getMetadata auf diesem Pfad aufruft.
Lesen Sie es so: Noch auf PHP 7.x? Das ist kritisch und unmittelbarer Handlungsbedarf. Auf PHP 8.x sind Sie nicht automatisch sicher – Sie sind einen Phar::getMetadata-Aufruf vom gleichen Ergebnis entfernt, und ein beliebiges Phar-Dateilesen ist kein Fehler, den Sie in einem Dokumentenimport-Endpunkt haben möchten.
Was das über das Umgehen von Patches aussagt
Bei unseren Pentesting-Einsätzen ist Phar-Deserialisierung eines der ersten Dinge, auf die wir zurückgreifen, wenn eine App einen Dateipfad, einen Dateinamen oder einen Upload entgegennimmt und an eine Bibliothek weitergibt. Der interessante Teil dieser CVE ist nicht der Wrapper-Trick, sondern die Struktur des ursprünglichen Fixes. Der Patch validierte das Schema, das parse_url zufällig zurückgab, während die eigentliche Regel lautete: „Dieser Pfad darf überhaupt keinen Wrapper referenzieren.“ Beheben Sie das Symptom, das Sie sehen, und Sie lassen die Fälle offen, die Sie nicht getestet haben. Drei Schrägstriche – mehr brauchte es nicht.
Dasselbe Muster sehen wir außerhalb von PHP. Aus unserer PHP- und Docker-Arbeit beim Aufbau cloudnativer Apps für Unternehmenskunden sind es die Bibliotheken, die nicht vertrauenswürdige Eingaben parsen, die bei jedem Dependency-Update einen zweiten Blick verdienen – nicht nur, wenn eine CVE auftaucht. In unseren nativen iOS- und Android-Projekten wiederholt sich das Muster mit Drittanbieter-SDKs, die stillschweigend eine URL oder einen Pfad akzeptieren. Das Team vertraut der Bibliothek, die Bibliothek vertraut der Eingabe, und niemand ist für die Grenze dazwischen verantwortlich. Ein neuer Patch ist nicht das Ende einer Schwachstellenklasse, sondern ein weiterer Datenpunkt darüber, wo die Grenze tatsächlich liegt.
Was diese Woche zu tun ist
Drei konkrete Schritte, in dieser Reihenfolge:
- Aktualisieren Sie PhpSpreadsheet. Der Fix ist 1.30.5 auf dem 1.x-Branch oder das gepatchte Release auf dem Branch, den Sie verfolgen: 2.1.17, 2.4.6, 3.10.6 oder 5.8.0. Der neue Code verzichtet auf den
parse_url-Trick und erkenntphar://und ähnliche Wrapper stattdessen durch explizite Muster. - Durchsuchen Sie Ihren Codebase nach
IOFactory::loadundReader::load, und verfolgen Sie dann jeden Aufruf zurück zu seinem Ursprung. Jeder, der von einem benutzerseitig angegebenen Dateinamen, Upload-Namen oder einer URL erreichbar ist, ist der relevante. Setzen Sie die gepatchte Version fest, bevor dieser Endpunkt erneut ausgeliefert wird. - Fügen Sie Defense-in-Depth auf der PHP-Ebene hinzu. Deaktivieren Sie die Stream-Wrapper
phar://,php://,data://undexpect://dort, wo Ihr Importcode läuft, und validieren oder kanonisieren Sie jeden Pfad, bevor er einen Loader erreicht. Wenn Sie noch auf PHP 7.x sind, ist diese CVE ein weiterer Grund, diese Migration abzuschließen. Die Runtime selbst hat den automatischen Deserialisierungs-Sink in PHP 8 entfernt.
Wenn Sie auf PHP aufbauen, ist es eine nützliche Gewohnheit, jede dateiverarbeitende Bibliothek standardmäßig als feindlich gegenüber nicht vertrauenswürdigen Pfaden zu behandeln – so wie wir es tun, wenn wir Backend-Dienste aufbauen und überprüfen für Kunden mit echten Compliance-Anforderungen. Die Kosten dieser Gewohnheit betragen einige Minuten pro Dependency. Die Kosten, sie zu überspringen, sind eine RCE in Ihrer Dokumenten-Pipeline.
Wenn eine Drittanbieter-Bibliothek, die stillschweigend nach phar:// in Ihrem PHP-Stack greift, wie ein Problem klingt, das Sie lieber abfangen würden, bevor ein Angreifer es tut, sprechen wir darüber.