Eine PHP-Anwendung, die seit zehn oder fünfzehn Jahren zuverlässig Rechnungen schreibt, Bestellungen abwickelt oder Kundendaten verwaltet, ist kein Sanierungsfall. Sie ist der Beweis, dass die Fachlogik trägt. Das Problem sitzt fast immer nicht im Kern, sondern in der Verdrahtung drumherum – und zwei der hartnäckigsten Altlasten heißen register_globals und magic_quotes. Beide waren PHP-Sprachfeatures aus der 4er- und frühen 5er-Zeit, beide sind seit Jahren aus dem Interpreter verschwunden. Was bleibt, ist Code, der stillschweigend auf ihr Verhalten baut. Genau das müssen Sie entschärfen, bevor Sie auf PHP 8 heben.
Was waren register_globals und magic_quotes eigentlich?
register_globals hat eingehende Request-Parameter automatisch als globale Variablen registriert. Kam ein Aufruf mit ?id=42, existierte im Script direkt $id mit dem Wert 42 – ohne dass jemand auf $_GET zugegriffen hätte. Praktisch, und zugleich eine der größten Sicherheitslücken der PHP-Geschichte, weil ein Angreifer damit interne Variablen von außen setzen konnte. Das Feature wurde in PHP 5.3 als veraltet markiert und in PHP 5.4 (2012) ersatzlos entfernt.
magic_quotes_gpc hat jeden Wert aus GET, POST und Cookies automatisch mit Backslashes vor Anführungszeichen versehen, in der guten Absicht, SQL-Injections zu verhindern. In der Praxis erzeugte das mehr Probleme als es löste: doppelt escapte Daten, kaputte Umlaute, Sonderzeichen in Formularen. Auch magic_quotes flog in PHP 5.4 raus.
Beide sind also seit über einem Jahrzehnt tot. Die Arbeit beim register_globals migrieren besteht nicht darin, ein Setting umzulegen – das Setting gibt es nicht mehr. Die Arbeit besteht darin, den Code zu finden, der noch so tut, als gäbe es sie.
Woran erkenne ich, dass mein Code noch von register_globals abhängt?
Der typische Fingerabdruck: Ein Script verwendet Variablen, die nirgends definiert oder aus einem Superglobal gelesen werden. Sie tauchen einfach auf. Ein klassisches Muster sieht so aus:
if ($user_is_admin) { deleteRecord($id); }
Weder $user_is_admin noch $id werden im Script gesetzt. Unter register_globals kamen sie aus dem Request. Ohne das Feature sind sie schlicht null – im besten Fall bricht die Funktion, im schlimmsten läuft ein Admin-Zweig für jeden durch, oder eine Löschung greift ins Leere.
So spüren Sie die Stellen systematisch auf:
- Fehler sichtbar machen. Setzen Sie in einer Testumgebung
error_reporting(E_ALL). JedeUndefined variable-Warnung ist ein heißer Kandidat. In älteren PHP-Versionen war das nur eine Notice, ab PHP 8 wird der Zugriff auf undefinierte Variablen deutlich strenger behandelt. - Nach nackten Variablen greppen. Suchen Sie nach Variablen, die verwendet, aber im selben Scope nie zugewiesen werden. Statische Analyse mit
PHPStanoderPsalmauf niedrigem Level meldet genau diese undefinierten Zugriffe zuverlässig. - Alte Kompatibilitäts-Shims suchen. Viele Anwendungen haben irgendwann einen
extract($_REQUEST)-Aufruf oder eine handgeschriebeneregister_globals-Emulation in eine Bootstrap-Datei geschmuggelt, um weiterzukommen. Das ist die gefährlichste Fundstelle, weil sie die Lücke aktiv nachbaut.
Wie migriere ich register_globals-Code sauber?
Die Regel ist einfach: Jede Variable, die aus dem Request stammt, muss explizit aus dem passenden Superglobal geholt werden. Aus dem impliziten $id wird ein bewusstes, validiertes $_GET['id'].
Ersetzen Sie das obige Beispiel nicht einfach eins zu eins, sondern ziehen Sie gleich die Validierung ein, die vorher gefehlt hat:
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) { throw new InvalidArgumentException('Ungültige ID'); }
Der Punkt $user_is_admin darf natürlich niemals aus dem Request kommen – dieser Wert gehört in die Session und wird serverseitig gesetzt. Beim Migrieren decken Sie also nicht nur eine technische Abhängigkeit auf, Sie schließen oft direkt eine echte Rechte-Lücke. Das ist der Grund, warum sich diese Arbeit lohnt, selbst wenn der Code „läuft“.
Falls Sie eine extract($_REQUEST)-Zeile finden: Löschen Sie sie und arbeiten Sie die dadurch verwaisten Variablen einzeln ab. Das ist mühsam, aber es ist die einzige Variante, die die Sicherheitslücke wirklich schließt, statt sie zu verschieben.
Wie werde ich die magic_quotes-Altlasten los?
Bei magic_quotes ist die zentrale Frage: Escapt Ihr Code irgendwo doppelt? Sehr viele Legacy-Anwendungen enthalten am Anfang eine Kompensationsroutine, die genau erkennen wollte, ob magic_quotes aktiv ist, und andernfalls selbst addslashes() nachlegte. Solche Blöcke sehen typischerweise so aus:
if (get_magic_quotes_gpc()) { $_POST = array_map('stripslashes', $_POST); }
Die Funktion get_magic_quotes_gpc() gibt es ab PHP 8.0 nicht mehr – ihr Aufruf erzeugt einen fatalen Fehler. Diese Blöcke müssen weg. Gehen Sie dabei so vor:
- Alle Aufrufe von
get_magic_quotes_gpc()undget_magic_quotes_runtime()finden und die zugehörigen Kompensationszweige entfernen. - Manuelle
stripslashes()-Aufrufe auf Request-Daten prüfen. Wenn magic_quotes weg ist und niemand mehr Slashes hinzufügt, entfernt ein verbliebenesstripslashes()plötzlich echte Backslashes aus den Nutzdaten. Ein Windows-Pfad oder ein regulärer Ausdruck im Formular wird dann still verstümmelt. - Escaping an die richtige Stelle verlagern. Rohdaten bleiben roh, bis sie in einen bestimmten Kontext geschrieben werden. Für die Datenbank heißt das: Prepared Statements über PDO oder mysqli, keine manuell zusammengebauten Query-Strings. Für HTML-Ausgabe:
htmlspecialchars()beim Rendern.
Damit verschwindet die alte magic_quotes-Logik nicht nur, sie wird durch das ersetzt, was von Anfang an richtig gewesen wäre: kontextabhängiges Escaping direkt an der Ausgabestelle.
Muss ich dafür die ganze Anwendung neu schreiben?
Nein, und in aller Regel wäre das die teuerste und riskanteste Option. Eine Neuentwicklung wirft die jahrelang erprobte Fachlogik weg und ersetzt bekannte, eingegrenzte Altlasten durch neue, unbekannte Fehler. register_globals und magic_quotes sind lokal begrenzte Probleme: klar identifizierbare Muster an klar identifizierbaren Stellen. Das ist gute Nachricht, denn solche Probleme lassen sich mit Tests absichern und schrittweise beheben.
Der pragmatische Weg sieht so aus:
- Erst eine Testumgebung mit der Zielversion aufsetzen und mit
E_ALLlaufen lassen, um den Umfang ehrlich zu sehen. - Dann Charakterisierungstests für die wichtigsten Abläufe schreiben, damit Sie merken, wenn sich Verhalten ändert.
- Erst danach die Fundstellen abarbeiten – Superglobals explizit machen, Escaping in den Ausgabekontext ziehen, Kompensations-Shims löschen.
Ehrlicher Praxis-Tipp: Reparieren Sie nicht ins Blaue, sondern lassen Sie sich vom Interpreter führen. Aktivieren Sie das volle Error-Reporting in einer isolierten Umgebung und arbeiten Sie die Meldungen von oben nach unten ab. Der Interpreter zeigt Ihnen exakt jede Stelle, die noch auf ein Feature baut, das es nicht mehr gibt – zuverlässiger als jede manuelle Suche. Wenn genau diese Migration ansteht und Ihnen die Testabsicherung dafür fehlt, ist das eine der Aufgaben, bei denen wir bei LegacyWerk regelmäßig unterstützen.