Im letzten Beitrag hat Sebastian Sicher eine Login-Maske für seine Schlüsseldienst-Website erstellt. Dort bin ich auf bekannte Sicherheitslücken sowie mögliche Vorkehrungen gegen diese eingegangen. Es sollte nur Nutzern der Zugriff auf das System gestattet sein, denen diese Rechte bei der Registrierung auf der Website zugeordnet wurden.
Nun kümmert sich Sebastian darum, dass die Nutzer die gewünschten Funktionalitäten der Website nutzen können. Die Internetseite ist in der Lage, Bilder, Dateien oder Texte auf den Server hochzuladen. Die hochgeladenen Dateien werden daraufhin vom System weiterverarbeitet und auf entsprechenden Server-Pfaden abgelegt. Sebastian geht davon aus, dass nur „auserkorene“ Nutzer Zugriff auf das System haben und vernachlässigt die Sicherheitsvorkehrungen nach dem Login. Es muss jedoch davon ausgegangen werden, dass diese Nutzer unbewusst Schadsoftware besitzen und diese ungewollt in das System hochladen. Aus diesem Grund muss jeder Nutzer als potenzielle Gefahr eingestuft werden. Auf Sebastians Website muss daher der Datenverkehr kontrolliert und verifiziert werden. Hier rückt der Punkt „Validate All Input“ oder zu Deutsch „Validierung aller Eingaben“ in den Fokus.
Das Ziel der Validierung ist es zu verifizieren, dass die in das System eingehenden Daten keinen Schaden anrichten oder dafür sorgen, dass Informationen hinausgeleitet werden. Die Validierung sollte so früh wie möglich geschehen, damit die Daten des externen Systems Sebastians Seite so wenig wie möglich manipulieren können. Selbst wenn es sich um Systeme handelt, denen vertraut wird, müssen die eingehenden Informationen überprüft werden. Dies gilt zum Beispiel auch für Partner, deren Daten zur Weiterverarbeitung notwendig sind. Es gibt keine hundertprozentige Versicherung, dass die Partnersysteme nicht korrumpiert sind. Die Validierung sollte zuallererst auf der semantischen und syntaktischen Ebene durchgeführt werden. Die Datei sollte den logischen Aufbau des entsprechenden Dateiformates besitzen und der Inhalt sollte mit der geforderten Eingabe übereinstimmen. Auf folgende Punkte sollte bei der Validierung geachtet werden.
1. Black- und Whitelisting
Dies ist die einfachste Methode, die von Sebastian implementiert werden kann. Für den Upload eines Bildes muss überprüft werden, ob es wirklich nur möglich ist, Bild-Formate hochzuladen. In einem Bilder-Upload-Feld sollte es nicht möglich sein, Skripte, welche im Nachhinein von innen heraus angreifen können, auf das System hochzuladen. Es wäre allerdings möglich, Dateien hochzuladen, die äußerlich das korrekte Format, im Inhalt jedoch <SCRIPT>-Tags besitzen. Solche Dateien können Skripte ausführen, obwohl das Datei-Format geprüft wurde. Ein solches Vorgehen nennt man Cross-Site-Scripting (XSS), dabei handelt es sich um eine Art Injection, mit dem Angreifer sich Zugriff auf das System verschaffen können. Daher sollte nicht nur eine kurze Sichtprüfung stattfinden, sondern auch der Inhalt der jeweiligen Datei überprüft werden.
2. Begrenzungen (Min & Max)
Es sollte auch festgelegt werden, in welchem Werte-Bereich die Eingaben und Dateien sich befinden. Es muss sich nicht immer um den Upload einer Datei handeln. Es können auch Strings verschickt werden, die im Nachhinein in der Datenbank gespeichert werden. Dementsprechend sollte bei der Eingabe eines Datums oder einer Zahl darauf geachtet werden, dass sie beispielsweise die richtige Länge besitzen. Beim Upload von Dateien sollte darauf geachtet werden, dass keine Gigabyte-großen Dateien importiert werden, wenn es sich nur um ein einfaches Profilbild handelt. Sämtliche dieser Min- und Max-Grenzen-Betrachtungen sind klassische Testszenarien, die von jedem professionellen QA-Team beobachtet werden. Ein spezielles Beispiel für solche Größenbegrenzungen ist der „Billion-Laughs-Attack“. Dabei handelt es sich um XML-Dateien, die eine Entität im Header definieren. Diese bestehen aus mehreren „LOL“-Strings, die sich selbst durch den verschachtelten Aufruf verzehnfachen.
<!--?xml version="1.0"?-->
<!--ELEMENT lolz (#PCDATA)-->
<!--ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"-->
<!--ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"-->
<!--ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"-->
<!--ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"-->
<!--ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"-->
<!--ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"-->
<!--ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"-->
<!--ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"-->
<!--ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"-->
]>
<lolz>&lol9;</lolz>
Bei diesem Beispiel wird 1.000.000.000 mal der String „LOL“ in den Speicher des Systems geladen. Je nach Hardwarestärke des Systems kann dies zum Zusammenbruch führen, wenn diese Datei gelesen wird. Die Menge und Größe des Strings können noch gesteigert werden und es könnten mehrere solcher Dateien gleichzeitig hochgeladen werden. In solchen Situationen ist es daher notwendig, dass Systeme ab einer gewissen Größe den Prozess beenden, um sich selbst zu schützen. Dies wäre keine Sicherheitslücke, die Informationen leakt, allerdings kann sie dazu verwendet werden, das System zum Absturz zu bringen. Und dieser kann wiederum dazu genutzt werden, weitere Maßnahmen zum Eindringen in unser System zu ergreifen.
Für die Tester unter euch ist der „Billions-Laugh-Attack“ vielleicht eine interessante Testmöglichkeit für eure Testsysteme.
3. Client- und Server-Verifikation
Es muss sichergestellt werden, dass die Eingabevalidierung nicht nur auf dem Client, sondern auch auf dem Server stattfindet. Auf Web-Applikationen ist es möglich, die JavaScripts mit Hilfe eines Proxys oder Direktanfragen zum Server zu umgehen. Daher ist eine beidseitige Sicherung die empfohlene Variante. Wenn vom Client überprüft wird, dass es sich um eine JPG-Datei handeln muss und erst nach dieser Überprüfung die Datei hochgeladen wird, dann könnte von der Serverseite die Überprüfung vernachlässigt werden. Doch wenn der Angreifer die genauen Adressen und den Aufbau dieser Upload-Anfrage an den Server mit Netzwerküberwachungstools ausliest, ist es ihm möglich, mit REST-Tools eine eigene Upload-Anfrage zu erstellen, die ihn die Client-seitige Überprüfung übergehen lässt. Somit wäre ein Angreifer in der Lage, direkt auf dem Server gewünschte Skripte abzulegen, die dazu genutzt werden, Zugriff auf den Server zu bekommen oder Informationen auszulesen. Darum müssen vor und nach dem Upload die Dateien überprüft werden.
4. Serverseitige Steuerung
Ein weiterer Punkt, der beachtet werden sollte, ist die Speicherort-Bestimmung. Diese sollte dem Server überlassen werden und nicht dem Client. Je nachdem wie viel der Angreifer aus den Skripten des Clients herauslesen konnte, kann er mit diesen Informationen einen Überblick vom Server-Aufbau bekommen und hat somit eine größere Angriffsfläche. Des Weiteren sollte eine Datei auch vom Server umbenannt werden, sobald diese auf dem konfigurierten Speicherpfad abgelegt wird. Dies sorgt dafür, dass Skriptinhalte, falls sie in der Datei übersehen wurden und auf den eigenen Dateinamen zugreifen würden, nicht ausgeführt werden könnten, da die gewünschte Datei in diesem Falle nicht vorhanden wäre.
\server\test\uploads\Testupload.JPG
—
\server\test\uploads\TE123ST321UPZEZELOAUIUID.JPG
Diese Punkte können QA-seitig leicht geprüft werden, indem die Server-Verzeichnisse nach einem Test-Upload überprüft und die sich dort befindenden Upload-Dateien analysiert werden.
Außerdem gibt es Software, die auf den Servern nach Malware sucht und die hochgeladenen Daten direkt untersucht. Testseitig kann hier Schadsoftware auf den Server hochgeladen werden, um solche Sicherheitslücken zu verifizieren. Solche Tests sollten allerdings vorher mit dem Systemverantwortlichen abgesprochen werden.
5. Regular-Expressions
Die Erstellung von Regular-Expressions für eine spezifische Aufgabe ist eine große Hilfe für das Schließen von Sicherheitslücken. Man erlaubt für die gewünschten Eingaben nur die Characters, die das System verarbeiten kann. Es ist nicht notwendig, den kompletten UTF-16 zu erlauben, wenn für eine Postleitzahl nur die Ziffern benötigt werden. Somit grenzt man für jedes Eingabefeld die möglichen Risiken ein. Diese sollten dann auch wieder Client- und Server-seitig überprüft werden. Auch ein wichtiger Leitsatz für die Sicherheit bei Regular-Expressions ist es, KEINE Wildcards zu verwenden.
Einfache Expression für E-Mail-Adresse:
[a-zA-Z]@[a-zA-Z].[a-zA-Z]
Hier wird eine einfache Regular-Expression für E-Mail-Adressen verwendet, die allerdings noch viel weiter verfeinert werden könnte.
Verfeinerte Expression für E-Mail-Adresse:
^[\w-\.\{\}#\+\&\-]{1,}\@([\da-zA-Z-\{\}#]{1,}\.){1,}[\da-zA-Z-]{2,3}$
Da man bei E-Mail-Adressen eine sehr große Auswahl hat, ist es schwer, eine sehr eingrenzende Regular-Expression hierfür zu schreiben, allein wegen der großen Menge an Sonderzeichen. Zum Glück existieren viele Frameworks, die solche Funktionen bereits implementiert haben und einem bei der Überprüfung helfen.
Dies waren nur ein paar der Punkte, die bei der Schließung von Sicherheitslücken beachtet werden sollten. Es sollte auch vermerkt werden, dass es meist besser ist, bekannte Frameworks zu verwenden, als sich selbst Funktionalitäten auszudenken. Einerseits weil diese Frameworks erprobt und über die Zeit gewachsen sind und andererseits weil sie sich updaten, sobald neue Sicherheitslücken auftauchen.
Bekannte Frameworks für Input Validation:
- Django Validators
- FluentValidation
- Apache Commons Validators
- Express Validator
In dem Framework „FluentValidation“ wird dem Entwickler die Validierung von Regular-Expression, String-Eingaben u.v.m. sehr vereinfacht. Das Framework ist so aufgebaut, dass man für die Variablen einer Klasse, die von einem Nutzer auf der Oberfläche der Website eingegeben werden, einfache und übersichtliche Verifikationsfunktionen nutzen kann. Sebastian hat eine kleine Klasse für seine Schlüsseldienst-Kunden erstellt, diese nennt er „Schluesseldienst“. Die registrierten Nutzer tragen über ein Anmeldeformular die nötigen Informationen für diese Klasse ein. Dabei handelt es sich um den Firmennamen, Adresse, Email-Adresse und Kreditkartennummer. Diese Informationen werden dazu genutzt, die Klasse zu erstellen. Doch bevor die Klasse dazu verwendet wird, die Informationen in der Datenbank zu speichern, in der die ungeprüften Daten Schaden anrichten könnten, werden sie über die Funktionalitäten des Frameworks überprüft.
Sollten bei der Überprüfung Fehler auftauchen, werden uns diese dokumentiert ausgegeben. Somit sichert man vor dem Speichern der Daten den korrekten Inhalt der Informationen.
Damit schließe ich den zweiten Teil meines Sicherheits-Talks ab. Ich hoffe, ich konnte euch mit diesem Blogbeitrag einen kleinen Überblick für die Validierung von Eingaben auf euren Systemen geben.