PHP-Sicherheit: Benutzereingaben validieren, maskieren und filtern
Veröffentlicht am 14.01.2021 von DomainFactory
Eine alte Programmierweisheit ist heute aktueller denn je: Traue nie den Eingaben deiner Benutzer! Im Web gilt das natürlich umso mehr, denn Sie als Webentwickler können ja meist nicht komplett kontrollieren, wer über Webformulare, URLs etc. Inhalte an Ihr System übergibt. In unserem Blogbeitrag erklären wir, auf welche Weise böswillige Benutzereingaben Schaden anrichten und was Sie dagegen tun können.Vertrauen ist gut ...
Geht es um PHP-Sicherheit bei der Webentwicklung, steht ein Prinzip im Mittelpunkt: Vertrauen. Wenn wir wissen, was wir tun, und sorgfältig arbeiten, können wir unserem eigenen Code durchaus vertrauen – zumindest wissen wir, dass sich darin keine böswilligen Fallen verbergen. Aber können wir auch der Integrität von Benutzereingaben in Formularen oder von URL-Anfragen vertrauen?
Die kurze Antwort lautet: Alles, was auch nur theoretisch von Benutzern bzw. Fremden manipuliert werden kann, ist nicht vertrauenswürdig und muss auch so behandelt werden. Dazu zählen nicht nur URLs oder Formulareingaben von Nutzern (egal ob angemeldet oder nicht), sondern zum Beispiel auch Cookies, die auf fremden Systemen gespeichert sind, oder Kommentare und Posts, die Fremde in unserem System hinterlassen.
Immer wenn eine Anwendung nicht vertrauenswürdige Daten in Kontexten nutzt, die nur für vertrauenswürdige Daten bestimmt sind, wird es gefährlich. Genau das passiert, wenn Benutzereingaben ungewollt auf dem Server als ausführbarer Code oder auf dem Client als HTML-JavaScript ausgewertet werden.
Eine wichtige Angriffsmethode dieser Art sind SQL Injections. Dabei zielen Eingaben speziell darauf ab, vom SQL-Interpreter des Datenbanksystems ausgeführt zu werden. Neben SQL können aber auch andere Interpreter per Injektion angegriffen werden, z. B. mit NoSQL oder Betriebssystembefehlen. Für Webanwendungen relevant ist vor allem auch die HTML-Injektion und ihre bekannteste Form, das Cross-Site-Scripting (XSS). Hier haben wir mehr zu diesem Thema geschrieben.
Eine erste Verteidigungslinie gegen solche Bedrohungen ist die Input-Validierung.
… Input-Validierung ist besser
Grundsätzlich können wir niemals sicher sein, dass unsere Anwendungslogik nicht vertrauenswürdigen Inhalt korrekt, also sicher, verarbeitet. Daten, die nicht den Erwartungen der Anwendung entsprechen, können zu unerwarteten Ergebnissen führen, von Instabilitäten und Fehlern bis hin zu den genannten Sicherheitsproblemen.
Sie sollten deshalb stets dafür sorgen, dass Daten aus nicht vertrauenswürdigen Quellen, die Ihre Anwendung als Input entgegennimmt, den Erwartungen bzw. Anforderungen zu ihrer Weiterverarbeitung entsprechen. Dieser Prozess wird Validierung genannt.
Definieren Sie diese Anforderungen so explizit wie möglich. Am sichersten ist es immer, konkret gültige Werte vorzugeben (Allowlisting). Blocklisting – das Blockieren von Eingaben, die bekannte gefährliche Werte enthalten, zum Beispiel den <script>-Tag, ist auch eine Möglichkeit, hat aber ihre Tücken. Denn die Zahl potenziell gefährlicher Eingaben ist extrem groß und praktisch schwer zu bewältigen. Manchmal ist Blocklisting aber kaum zu ersetzen, etwa bei Freitextfeldern.
PHP bietet Ihnen eine ganze Reihe von Validierungsmöglichkeiten. Sie können zum Beispiel Parameter-Typen vorgeben (mit aktiviertem Strict Typing deklarieren) oder auch per InvalidArgumentException durchsetzen. Sie können Werte auf bestimmte Eigenschaften prüfen, etwa Datentyp (z. B. is_numeric, is_string, is_bool), Datenformat (z. B. date_format) oder Länge (z. B. strlen). Zudem bietet PHP zahlreiche Filter zur Validierung. Für bestimmte Aufgaben kann auch eine Third-Party-Lösung wie Webmozart Assert hilfreich sein.
💡 Tipp: Überlegen Sie bei ungültigen Eingaben gut, ob und wie detailliert Ihre Anwendung das dem User zurückmelden soll. Denn Hacker können Ihren Fehlermeldungen auch nützliche Informationen über System oder Datenstrukturen entnehmen.
Cross-Site-Scripting (XSS) verhindern: Ausgaben maskieren
Aus Entwicklersicht ist XSS ein Output-Problem: Denn diese Angriffsmethode beruht darauf, dass die Webanwendung aus dem manipulierten Input schädlichen Output erzeugt, der im Browser des Opfers als Code ausgeführt werden kann.
Die sogenannte Maskierung des Outputs soll das verhindern. Maskiert – also unschädlich gemacht – werden müssen insbesondere Metazeichen mit spezieller Bedeutung (Steuerzeichen) in HTML oder JavaScript. Dazu gehört neben „<“, „>“, beispielsweise auch das einfache Hochkomma. Das kann in Text oder in Namen (wie in „O’Reilly“) vorkommen, ist aber auch ein Steuerzeichen in JavaScript, das Anfang und Ende von Eingabestrings bezeichnet. Werden Hochkommata bei der Ausgabe nicht maskiert und per JavaScript verarbeitet, könnte ein Browser etwa meinen, nach dem „O“ in „O’Reilly“ sei der JS-Befehl zu Ende. Dann aber könnte ein Angreifer statt „O’Reilly“ genauso gut auch „O‘;“ plus einen weiteren bösartigen JS-Befehl seiner Wahl eingeben, den der Browser dann gleich mit ausführt.
In PHP steht für die Maskierung von HTML- und Javascript-Sonderzeichen bei der Codierung die Funktion htmlspecialchars() zur Verfügung. htmlentities() funktioniert ähnlich, wandelt aber alle Zeichen mit HTML-Code-Entsprechung in diese um. Speziell für die Maskierung von URLs dient urlencode(). Für die Speicherung in die Datenbank nutzen Sie wie beim Schutz vor SQLI Prepared Statements, denn dabei werden die Eingaben automatisch maskiert.
Da es eine Fülle möglicher Kontexte für zu maskierende Zeichen (HTML, XML, JavaScript, CSS, SQL, URLs) mit ihren jeweiligen Besonderheiten gibt, können wir an dieser Stelle keine erschöpfende Anleitung geben. Für anspruchsvolle Aufgaben sollten Sie eine bestehende PHP-Bibliothek wie Laminas Escaper (vormals Zend Escaper) in Betracht ziehen.
Output filtern und bereinigen
Gelegentlich findet sich der Tipp, ungültige Eingaben nicht zurückzuweisen, sondern vor dem Speichern zu filtern bzw. zu bereinigen (Sanitization). Das ist nicht empfehlenswert, denn wie das oben erwähnte Blocklisting ist es schwer umzusetzen und leicht zu umgehen.
Es gibt allerdings einige Anwendungsfälle, bei denen Maskieren nicht sinnvoll ist, zum Beispiel wenn der User-Input gültiges HTML enthalten muss (z. B. Blogpost-Editor). PHP bietet analog zur Validierung auch eine Reihe von Filtern zur Bereinigung an; einen Überblick über Filter in PHP finden Sie hier. Alternativ können Sie ebenfalls eine bestehende Bibliothek wie z. B. HTML Purifier nutzen.
❗ Und bitte beachten Sie: Für das Filtern und Bereinigen wie auch für das Maskieren gilt die gleiche Grundregel: Tun Sie es so spät – bzw. so nahe am Output – wie möglich.