Input Validierung und Output Encoding

Du kennst nun den SDL (Secure Development Lifecycle) als Rahmen für sichere Anwendungsentwicklung: Sicherheit in jeder Phase mitdenken. Aber was bedeutet das konkret beim Schreiben von Code? Welche Fehler passieren am häufigsten, und wie vermeidest du sie?

Die Antwort beginnt mit einer goldenen Regel, die sich durch die gesamte sichere Anwendungsentwicklung zieht und die du bereits kennen solltest:

Alles, was vom Nutzer kommt, ist nicht vertrauenswürdig.

Das gilt für Formulareingaben, URL-Parameter, Cookies, HTTP-Header, JSON-Felder, Datei-Uploads und Daten aus externen Schnittstellen. Es gilt sogar für Daten aus der eigenen Datenbank, wenn sie ursprünglich von Nutzern oder fremden Systemen stammen. In Modul B2 hast du Angriffe wie SQL-Injection, Cross-Site Scripting und Command Injection kennengelernt. Diese Angriffe funktionieren oft, weil eine Anwendung Eingaben als harmlos behandelt und sie später in einem gefährlichen Kontext verarbeitet.

„In der vorherigen Lektion haben wir bei der WAF gesehen, dass die eingehende Anfragen auf Angriffsmuster prüft. Wenn die WAF das schon macht, warum muss ich im Code nochmal validieren?“

„Weil das immer nur zusätzliche Schutzschichten sein sollten. Wenn dein Code selbst keine Validierung hat, hängt deine gesamte Sicherheit an einer einzelnen extra Komponente. Und was passiert, wenn jemand die Konfiguration ändert, oder das System ausfällt? Dann darf die Anwendung nicht ohne Hose dastehen…“

Input Validierung

Input Validierung bedeutet, dass Eingaben geprüft werden, bevor die Anwendung sie verarbeitet. Die Anwendung entscheidet dabei, ob ein Wert zum erwarteten Format, Typ und fachlichen Kontext passt.

OWASP beschreibt Input Validation als Maßnahme, um sicherzustellen, dass nur korrekt geformte Daten in den Verarbeitungsfluss einer Anwendung gelangen: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html

Für das NovaHealth-Patientenportal bedeutet das zum Beispiel:

  • Ein Datumsfeld akzeptiert auch nur ein gültiges Datum.
  • Eine Patienten-ID akzeptiert nur das erwartete ID-Format.
  • Ein Filter für Laborwerte akzeptiert nur bekannte Untersuchungstypen.

Dabei gibt es wiederum zwei Ebenen:

 Prüft die Form.

  • Ist der Wert eine Zahl?
  • Hat das Datum das richtige Format?
  • Hat die E-Mail-Adresse den erwarteten Aufbau?

Prüft die Bedeutung im fachlichen Kontext.

  • Liegt das Startdatum vor dem Enddatum?
  • Gehört die Patienten-ID zur angemeldeten Person?
  • Ist der Untersuchungstyp in diesem Modul erlaubt?

 

Allowlisting statt Blocklisting

Bei der Validierung gibt es ebenfalls verschiedene Strategien.

  • Allowlisting (Positivliste) bedeutet: Nur ausdrücklich erlaubte Werte werden akzeptiert. Alles andere wird abgelehnt.
  • Blocklisting (Negativliste) bedeutet: Bekannte gefährliche Werte oder Muster werden blockiert.

Ein gutes Beispiel ist ein Parameter, der nur wenige gültige Werte haben darf:

/laborergebnis?format=pdf

Die Anwendung unterstützt ausschließlich pdf und json.

Eine Blocklist wäre dann z.B.

Blockiere: xml, txt, exe, png, py, php, dll, yaml, ...

Es ist eigentlich klar, dass es wenig Sinn macht hier tausend verschiedene Dateiendungen zu blockieren. Denn dann kommt ein Angreifer und macht plötzlich: format=../../etc/passwd was eine völlig neue Schwachstelle aufreißt.

Allowlisting ist eigentlich immer die sicherere Strategie, weil nur das durchgelassen wird, was explizit bekannt ist und erwartet wird. Beim Blocklisting müssen alle möglichen Angriffsmuster bekannt sein und Angreifer finden ständig neue Wege solche Filter zu umgehen. Hier mal eine „kleine“ Liste mit möglichen Umgehungen von XSS Filtern: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html

Stell dir das einfach so vor: Du kommst nicht in den Club, weil du nicht auf der Gästeliste stehst. In einem anderen Club gibt es nur eine Liste mit Hausverboten. Da der Türsteher dich nicht kennt, kommst du rein, machst Ärger und wirst rausgeworfen… aber der Schaden ist bereits verursacht.

Validierung nur Serverseitig

„Ok, ich habe jetzt im Patientenportal eine Formularvalidierung eingebaut, die im Browser prüft, ob die E-Mail-Adresse gültig ist. Das sollte reichen, oder? Der Browser zeigt ja eine Fehlermeldung, wenn das Format nicht stimmt.“

„Das ist gut für die Benutzerfreundlichkeit, denn der Patient bekommt sofort Feedback. Aber es bietet keinen Schutz. Ein Angreifer kann den Browser komplett umgehen und Anfragen direkt an den Server senden. Zum Beispiel mit einem Tool wie Burp Suite oder einem einfachen curl-Befehl. Eine Client-Validierung existiert für Angreifer schlicht nicht.„

Die Regel lautet: Immer auf dem Server validieren. Client-seitige Validierung ist eine Komfortfunktion, keine Sicherheitsmaßnahme.

Bei der serverseitigen Validierung solltest du folgende Aspekte prüfen:

  • Länge: Ist die Eingabe innerhalb der erwarteten Grenzen? Ein Name mit 10.000 Zeichen ist kein Name.
  • Typ: Ist es eine Zahl, ein Text, ein Datum? Ein Alter sollte keine Buchstaben enthalten.
  • Bereich: Liegt der Wert im erlaubten Bereich? Ein Alter von -5 oder 999 ist ungültig.
  • Format: Entspricht die Eingabe dem erwarteten Muster? Eine E-Mail-Adresse hat ein @, eine Postleitzahl hat fünf Ziffern (zumindest in DE (hier wirds wieder komplexer)).
  • Encoding: Verwendet die Eingabe die erwartete Zeichenkodierung (z.B. UTF-8)?
  • Erlaubte Werte: Kommt der Wert aus einer definierten Liste? siehe abschnitt zu AllowList oben. Eine gültige ID ist noch keine erlaubte ID.

Authentizität und Autorisierung, also ob ein User überhaupt auf was genau zugreifen darf, wurde in vorigen Kapiteln bereits ausführlich thematisiert, fällt aber natürlich auch unter serverseitige Validierung.

Output Encoding

Neben der Eingabevalidierung ist das Output Encoding entscheidend, um Angriffe wie XSS (Cross-Site Scripting) zu verhindern. Du hast XSS-Angriffe bereits in Modul B2 kennengelernt und in der Lektion über Webanwendungen die Abwehrseite gesehen. Output Encoding ist die Umsetzung im Code.

Output Encoding bedeutet, dass Daten vor der Ausgabe für den jeweiligen Kontext kodiert werden. OWASP beschriebt das ebenfalls in ihrem Cross-Site Scripting Prevention Cheat Sheet. Das ist wichtig, da ein und dieselbe Zeichenkette je nach Kontext unterschiedlich gefährlich sein kann:

  • HTML-Kontext: Sonderzeichen wie <, > und & werden in ihre HTML-Entitäten umgewandelt (&lt;, &gt;, &amp;). So kann ein Angreifer kein HTML einschleusen.
  • JavaScript-Kontext: Eingaben werden so kodiert, dass sie nicht als ausführbarer Code interpretiert werden.
  • URL-Kontext: Sonderzeichen werden URL-kodiert (z.B. Leerzeichen wird zu %20).
  • CSS-Kontext: Werte werden so kodiert, dass keine CSS-Injection möglich ist.

Ein konkretes Beispiel macht das deutlich:

Unsicheres Muster: die Benutzereingabe wird direkt in die HTML-Ausgabe eingefügt:

username = request.getParameter("name")
html = "<p>Willkommen, " + username + "</p>"

Wenn ein Angreifer als Namen <script>alert('XSS')</script> eingibt, wird dieses Script im Browser jedes Nutzers ausgeführt, der die Seite aufruft.

Sicheres Muster: die Ausgabe wird HTML-kodiert:

username = request.getParameter("name")
username = htmlEncode(username)
html = "<p>Willkommen, " + username + "</p>"

Jetzt wird <script> zu &lt;script&gt; und der Browser zeigt den Text an, statt ihn auszuführen.

Die htmlEncode() Funktion ist hier nur ein Beispiel. Jedes moderne Framework hat ein Äquivalent für diese Funktionalität, welches genutzt werden sollte. Bei Filtermechanismen sollte man nicht auf Eigenentwicklungen setzen, sondern auf bereits bewährte und robuste Implementierungen.

Prepared Statements gegen SQL-Injection

Einen weiteren wichtigen Schutzmechanismus kennst du schon aus der Lektion über Webanwendungen: Prepared Statements (auch parametrisierte Abfragen genannt). OWASP nennt Prepared Statements als zentrale Schutzmaßnahme gegen SQL-Injection, weil SQL-Code und Benutzerdaten getrennt bleiben: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md

Unsicher: die Benutzereingabe wird direkt in den SQL-String eingebaut:

query = "SELECT * FROM patients WHERE id = '" + userInput + "'"

Besser: die Benutzereingabe wird als Parameter übergeben:

query = "SELECT * FROM patients WHERE id = ?"
parameter = userInput

Beim Prepared Statement wird zuerst die SQL-Struktur festgelegt. Danach wird der Benutzerwert als Parameter übergeben. Die Datenbank behandelt ihn als Datenwert, nicht als SQL-Code.

Im Gegensatz zu Input Validierung, die prüft, ob eine Eingabe zulässig aussieht, sorgen Prepared Statements dafür, dass eine Eingabe die SQL-Logik nicht verändern kann. Wichtig: Beides gehört zusammen.

Im Kontext des SDL gehört Input Validierung und Output Encoding in mehrere Phasen.

In den Anforderung(en) wird festgelegt, welche Eingaben erlaubt sind und welche Daten geschützt werden müssen. Im Design wird entschieden, wo validiert wird und welche Komponenten Daten ausgeben. In der Implementation werden Validierung, Encoding und parametrisierte Abfragen umgesetzt. In der Verification wird geprüft, ob unerlaubte Eingaben abgelehnt und Ausgaben sicher dargestellt werden.

Input Validierung und Output Encoding sind die zwei Seiten derselben Medaille. Eingaben prüfen, bevor du sie verarbeitest. Ausgaben kodieren, bevor du sie anzeigst.

Lass uns schauen, ob du die Grundlagen der sicheren Datenverarbeitung drauf hast.

Nach oben scrollen