Eine Sicherheitsprüfung einer Laravel-Anwendung meldete einen kritischen Befund: entfernte Codeausführung in der Anhangverarbeitung. Der Befund war falsch. Was wir beim Widerlegen fanden, war auf seine stille Art schlimmer — jeder Upload schlug fehl, und niemand wusste davon.
Zwei Bäume, ein catch
Seit PHP 7 gibt es zwei getrennte Bäume dessen, was geworfen werden kann: Exception für erwartbare Fehler und Error für Programmierfehler — Aufruf einer nicht existierenden Methode, falscher Typ, erschöpfter Speicher. Gemeinsam ist ihnen nur Throwable. Ein Block catch (Exception $e) fängt ausschließlich den ersten Baum; ein Error geht hindurch, als wäre er nicht da.
Wie der Upload starb
Eine Hilfsklasse für Metadaten rief eine Methode der Dateibibliothek auf, die in einer neueren Version entfernt worden war. Der Aufruf einer nicht existierenden Methode wirft einen Error. Der Controller hatte um die Anhangverarbeitung ein sauberes catch (Exception) mit Meldung an den Nutzer — das den Error nie zu Gesicht bekam. Das Ergebnis: ein 500 bei jedem Upload, kein Eintrag im Anwendungslog, keine Meldung an den Nutzer. Meldungen mit Anhängen verschwanden einfach.
Der Fehlbefund, der zum echten Befund führte
Ein paar Zeilen weiter unten hatte dieselbe Klasse einen Aufruf, der einen Shell-Befehl zusammensetzte — auf dem Papier ein Lehrbuchbeispiel für entfernte Codeausführung. So bewertete es die Prüfung auch. Doch die Zeile war tot: Die Ausführung erreichte sie nie, weil genau jener Error ein paar Zeilen darüber jede Anfrage abbrach. Die Lehre, die wir festgehalten haben: Erreichbarkeit beweist man Zeile für Zeile, nicht durch Verfolgen der Aufrufe bis zur Funktion.
Was wir geändert haben
Der tote Code wurde entfernt, die Hilfsklasse ohne Befehlszusammensetzung neu geschrieben. An den Systemgrenzen — dort, wo ein Controller entscheidet, was der Nutzer sieht — fangen wir Throwable statt Exception und melden jeden solchen Fall ins Log, bevor eine Antwort zurückgeht. Alles zu fangen heilt schlechten Code nicht, garantiert aber, dass der Fehler wenigstens sichtbar wird. Leise zu sterben ist die schlechteste Art zu sterben.
Ein Fehler, den niemand sieht, ist nicht behandelt — er wurde nur an den Nutzer weitergereicht.