| Webapplikationen mit Unterstützung von XML | |
| Navigation: | Inhalt -> 3 Webapplikationen -> 3.5 Sicherheitsrisiken bei Webapplikationen | Neues und Änderungen << Vorheriges Kapitel | Nächstes Kapitel >> |
| 3.5 | Sicherheitsrisiken bei Webapplikationen |
| 3.5.1 | Allgemeine Sicherheitsprobleme |
Eine Webapplikation ist nur so sicher, wie ihr schwächstes Glied. Mögliche Angriffspunkte gibt es viele:
| 3.5.2 | Authentifizierung und Sitzungsmanagement |
Die Sicherheitsprobleme, die bei der Benutzerauthentifizierung und dem Sitzungsmanagement auftreten können, wurden schon im Kapitel 3.4.8 Sitzungsmanagement und im Kapitel 3.4.5 Benutzerauthentifizierung diskutiert. Zusammenfassend sei das Wichtigste nochmals erwähnt: Passwörter nicht im Klartext speichern, sondern sie zuvor mit Hash-Funktionen verschlüsseln. Session-IDs müssen genügend zufällig und genügend lang sein, damit weder ein Erraten oder ein Brute-Force-Angriff möglich ist. Nähere sicherheitsrelevante Informationen zum Thema Authentifizierung und Sessionmanagement findet man unter anderem unter [Fu 2001].
| 3.5.3 | Manipulierbare Eingabedaten |
Das wichtigste Gebot beim Erstellen von sicheren Webapplikationen lautet: "Traue niemals den Eingabedaten." Damit sind alle Daten gemeint, die vom Browser gesendet werden. Diese Daten können alle - mehr oder weniger einfach - manipuliert werden.
Query-String
Am einfachsten kann natürlich der Query-String manipuliert werden. Änderungen in der Adresszeile des Browsers kann jeder durchführen. Teilweise passiert das sogar unabsichtlich, wenn beispielsweise ein Link durch einen Zeilenumbruch getrennt wird, und damit ein Teil des Query-Strings nicht mit in die Adresszeile kopiert wird.
Interessant in diesem Zusammenhang ist, dass über den Querystring beliebige Zeichen übermittelt werden können. Normalerweise sind nur alphanumerische Zeichen und einige wenige Sonderzeichen erlaubt. Aber alle anderen Zeichen können codiert (URL-codiert) übermittelt werden. Dabei wird dem hexadezimalen Wert des Zeichens das Prozentzeichen ('%') vorangestellt. [Berners-Lee 1998] Die Web-Programmiersprachen bieten üblicherweise Funktionen an, die diese Codierung durchführen. (Z.B. in PHP: rawurlencode())
Formulardaten
Es ist klar, dass Eingabefelder in Formularen beliebig im Browser geändert werden können. Weniger klar ist allerdings, dass das auch für alle anderen Formularelemente - auch für die versteckten Felder (hidden fields) - gilt. Ein Formular könnte einfach beim Benutzer lokal abgespeichert und beliebig verändert werden. Beispielsweise könnten alle Formularelemente zu Eingabefeldern umgeändert werden, und somit beliebige Inhalte vom Formular aus an den Server gesendet werden.
Beschränkungen im HTML-Code in irgendeiner Weise tragen daher nicht zu mehr Sicherheit bei. Eine clientseitige Eingabeüberprüfung kann zwar für die Benutzung von Vorteil sein, aber für die Sicherheit der übertragenen Daten kann sie nicht garantieren. Javascript kann ausgeschaltet oder beim lokal gespeicherten Formular einfach entfernt werden. Ebenfalls entfernt werden können Längenbegrenzungen bei Eingabefeldern. Das Programm am Server kann sich nicht darauf verlassen, dass eine Zeichenkette nur maximal 50 Zeichen lang ist, nur weil das Formularelement mit dem Attribut 'maxlength="50"' definiert worden ist. Auch bei Auswahlfeldern mit vordefinierten Auswahlmöglichkeiten darf nicht angenommen werden, dass nur diese Möglichkeiten zurück an den Server gesendet werden.
Man könnte sich abhelfen, indem man im Programm, das die Formulardaten verarbeitet, den Referer (HTTP_REFERER-Variable) abfragt. Damit sollte man erkennen können, ob die Daten von einem lokal abgespeicherten Formular gesendet wurden. Allerdings ist auch der Referer nicht vor Manipulationen geschützt und wird darüber hinaus von manchen Firewalls gefiltert.
Cookies und andere HTTP-Kopfbereichsdaten
Aber nicht nur der Query-String und die Formulardaten lassen sich manipulieren, sondern alle Daten, die ein Browser an den Server schickt. Man braucht dazu nur ein Programm schreiben, dass sich vor dem Server als Browser ausgibt. Und das ist je nach Programmiersprache mit wenigen Zeilen Code geschrieben. Außerdem gibt es fertige Programme, die die Manipulation dieser Daten erlauben. Manche davon arbeiten wie ein Proxy. Der Browser greift über den Proxy auf den Webserver zu, wobei dann beim Proxy eingestellt werden kann, welche (Kopf-)Daten er ändern soll.
Dadurch kann man bei Anfragen an den Webserver Cookies, den Referer, die Kennung des Browsers und des Betriebssystems und einige andere Variablen ändern. Auch manipulierte Formulardaten könnte man so an den Server senden.
Folgerung
Zusammengefasst können also alle Daten, die normalerweise vom Browser gesendet werden, manipuliert werden. Das Programm am Webserver, das die Daten verarbeitet, muss daher auf beliebig manipulierte Eingabedaten vorbereitet sein.
| 3.5.4 | Sichere Verarbeitung der Eingabedaten |
Im Folgenden seien einige Möglichkeiten angeführt, wie Eingabedaten, die nicht entsprechend verarbeitet werden, zu Sicherheitsproblemen führen können.
Cross Site Scripting
Cross Site Scripting (CSS, oder um Verwirrungen mit Cascading Style Sheets zu vermeiden: XSS) nennt man einen Angriff, bei dem versucht wird, Code in den Quelltext eines HTML-Dokuments einzuschleusen, der dann ausgeführt wird. Üblicherweise ist das Javascript-Code.
Ein Beispiel: Auf einer Seite soll eine Detailansicht eines Benutzers angezeigt werden. Die Seite wird von einer Übersichtliste, auf der alle Benutzer mit einem Link aufgelistet sind, aufgerufen. Die Links enthalten im Query-String den Benutzernamen.
Ein unbedachter Programmierer könnte annehmen, dass in der Query-String-Variable immer ein korrekter Benutzername enthalten ist, und diesen einfach in den HTML-Quelltext der zu generierenden Seite schreiben. Ein potentieller Angreifer könnte aber den Query-String einfach folgendermaßen abändern:
http://www.domain.com/user.php? name=%3Cscript%3Ealert%28document.cookie%29%3B%3C%2Fscript%3EBeispiel 23: Möglicher Aufruf der Seite eines Angreifers. (Ohne Zeilenumbruch eingeben.)
Der Wert der Variable 'name' ist hier die URL-codierte Form von:
Dies ist auch der Wert, den die Query-String-Variable 'name' im Programm erhält. Die Decodierung führt bereits der Webserver durch. Der unbedachte Programmierer schreibt, wie oben erwähnt, die Variable einfach in den Quelltext der HTML-Seite. Dadurch wird in diesem Fall der Code von Beispiel 24 ausgegeben. Dieser enthält Javascript, der vom Browser ausgeführt wird und hier einfach alle gesetzten Cookies des aktuellen Domains ausgibt. Mit ein paar Zeichen mehr kann aber auch Javascript-Code angegeben werden, der die Cookies an einen anderen Server sendet, wo sie gespeichert und für einen Cookie-Manipulations-Angriff verwendet werden können. Dadurch könnte die Sitzung eines fremden Benutzers übernommen werden.
Noch mehr Schaden könnte ein Angreifer anrichten, wenn der Programmierer den Inhalt von Eingabedaten ungeprüft in den Quelltext eines Programms schreiben würde. Angenommen Benutzer können zu Artikeln Kommentare schreiben. Diese Kommentare werden wegen der schnelleren Generierung direkt in den Quellcode einer HTML oder Script-Seite geschrieben. (Z.B. PHP, ASP, JSP, etc.) Im letzten Fall wäre es dem Angreifer möglich, Programmcode sogar am Server ausführen zu lassen. Dadurch hat er praktisch alle Möglichkeiten, die auch der Programmierer hat: Dateien löschen, Passwortdateien auslesen, etc. Üblicherweise werden aber Daten nicht in Programmdateien geschrieben, sondern in Datenbanken oder reinen Datendateien, wodurch solche Eingaben niemals als Programmcode ausgeführt werden.
In der Praxis laufen solche Angriffe meist so ab, dass den Benutzern Links wie in Beispiel Beispiel 23 untergeschoben werden. Beispielsweise in E-Mails oder Foren. Diese Links sind meist so codiert (hexadezimal URL-codiert), dass der Javascript-Code nicht ohne weiteres erkennbar ist. Klickt ein Benutzer nun auf so einen Link, wird das Cookie von der angegeben Domain mit Javascript ausgelesen und an einen anderen Server übertragen.
Schutz gegen XSS
Daten, die auf einer Webseite ausgegeben werden, müssen HTML-codiert werden. Das heißt, dass alle (zumindest alle kritischen) Zeichen, die in HTML eine besondere Bedeutung haben, quotiert werden müssen. Das sind insbesondere '<', '>' und '&'. Die Programmiersprachen stellen meist Funktionen zur Verfügung, die das durchführen. (In PHP z.B.: 'htmlspecialchars()' oder 'htmlentities()'.) Wären in der Zeichenkette von Beispiel Beispiel 24 die Zeichen '<' und '>' durch '<' bzw. '>' ersetzt worden, würde diese als einfache Zeichenkette ausgegeben und nicht als Javascript im Browser ausgeführt werden.
Vorzugsweise sollten aber bei solchen Paramtern immer nummerische IDs verwendet werden. Im Link zur Detailseite sollte also nicht der Name als Parameter angegeben werden, sondern die Identifikationsnummer des Benutzers.
Vor dem Verarbeiten der Variable muss nur mehr überprüft werden, ob der Wert von 'userid' auch tatsächlich eine Zahl ist. Dies sollte üblicherweise einfacher sein als die Analyse bzw. das HTML-Codieren von Zeichenketten.
Zu beachten ist, dass natürlich alle Eingabedaten (Query-String, Formulardaten, HTTP-Kopfdaten) davon betroffen sind, sobald sie in irgendeiner Weise Inhalt der zu generierenden HTML-Seite werden.
Nähere Informationen zum Thema Cross Site Scripting findet man mit jeder Suchmaschine und im Besonderen unter [CGI-Security 2002] und [Curphey 2002].
SQL-Injection
Das Prinzip von SQL-Injection ist ähnlich dem des Cross Site Scripting. Nur wird hier nicht der Inhalt einer Webseite manipuliert, sondern die SQL-Befehle, die an eine Datenbank gesendet werden.
Angenommen in den Variablen $_POST['password'] und $_POST['username'] stehen die im HTML-Formular eingegebenen Anmeldedaten und es wird folgender Programmcode verwendet:
$_POST['password'] = sha1($_POST['password']);
$sqlquery = "SELECT id
FROM user
WHERE name = '{$_POST['username']}'
AND password = '{$_POST['password']}';";
Beispiel 26:
Überprüfung der Anmeldedaten in der Datenbank.
Das SQL-Statement sei dabei in einer Zeile und nicht wie hier wegen der besseren Lesbarkeit aufgeteilt. Gibt der Benutzer als Benutzername 'Peter' und als Passwort 'Gt§5gRz3' ein, so ist alles in Ordnung und die generierte Datenbankabfrage sieht folgendermaßen aus:
SELECT id FROM user WHERE name = 'peter' AND password = 'b2ffad2beb036e93a0bddfff18f295613a5f7497';Beispiel 27: Generierte SQL-Datenbankabfrage bei vorgesehenen Benutzereingaben.
Wobei hier die lange, kryptische Zeichenkette das mit dem SHA-1 Hash-Algorithmus verschlüsselte Passwort ist. Ein potentieller Angreifer könnte aber nicht - wie vorgesehen - einen Username und ein Passwort eingeben, sondern statt dem Benutzernamen folgende Zeichenkette (exkl. doppelte Anführungszeichen): "Administrator'; --". Das würde zu folgender Abfrage führen:
SELECT id FROM user WHERE name = 'Administrator'; --' AND password = 'b2ffad2beb036e93a0bddfff18f295613a5f7497';Beispiel 28: Generierte SQL-Datenbankabfrage bei bösartigen Benutzereingaben.
Die beiden Bindestriche markieren einen SQL-Kommentar. Alles was dahinter steht, wird in der SQL-Abfrage ignoriert. (Allerdings nur bis zum Zeilenende. Wie oben erwähnt, wird hier angenommen, im Programmquellcode stünde alles in einer Zeile.) Der Angreifer hat es damit geschafft, dass die ID des Administrator-Benutzers ohne Überprüfung des Passwortes aus der Datenbank gelesen wird. Er ist somit als Administrator angemeldet!
Es gibt unzählige Möglichkeiten, einen SQL-Injection-Angriff durchzuführen. Beispielsweise könnten weitere SQL-Abfragen eingeschleust werden, die Daten löschen, ändern oder hinzufügen, auch wenn die kompromittierte Abfrage nur eine einfache Lese-Abfrage ist.
Für den Angreifer ist es natürlich am einfachsten, wenn er Zugriff auf den Quelltext der Anwendung hat. Dies ist bei Open-Source-Software oder auch bei vielen kommerziellen Webapplikationen der Fall, wo auch der Quelltext weitergegeben wird. Aber auch wenn dem Angreifer der Quelltext nicht zur Verfügung steht, ist es mit etwas Phantasie durchaus möglich, eine Schwachstelle zu finden. Man braucht nur das Zeichen, das die Zeichenketten in der Datenbank abgrenzt, an den Server senden (im Formularfeld, Querystring, Cookie, etc.). Das ist meist ein einfaches oder bei manchen Anwendungen ein doppeltes Anführungszeichen. Je nach Reaktion (Z.B. Fehlermeldung) kann man schnell herausfinden, ob das Programm am Webserver für den SQL-Injection-Angriff anfällig ist. Wenn das der Fall ist, kann durch Probieren herausgefunden werden, wie die SQL-Anfragen aufgebaut sind, und dies dann unter Umständen ausnützen.
Die soeben erwähnte Methode ist aber nicht nur für den potentiellen Angreifer von Vorteil, sondern eignet sich auch zum Testen der eigenen Anwendung, ob sie sicher gegen SQL-Injection-Angriffe ist.
Schutz vor SQL-Injection
Alle Variablen, die vom Browser gesendet und in eine Datenbankabfrage integriert werden, müssen untersucht werden. Sind innerhalb dieser Variable Zeichen, die normalerweise die Zeichenkette bei der Abfrage begrenzen, müssen diese quotiert werden. Im SQL-Standard ist dies das einfache Anführungszeichen ('). Ein einfaches Anführungszeichen innerhalb eines Strings muss durch zwei einfache Anführungszeichen quotiert werden (''). Manche Datenbanken haben allerdings auch andere Begrenzungszeichen (z.B. doppelte Anführungszeichen), und das Quotieren kann auch anders erfolgen. Beispielsweise durch Voranstellen eines Backslashs (\"). Manche Webprogrammiersprachen bieten hier entsprechende Funktionen an, die das Quotieren übernehmen.
Sinnvoll ist auch das Verwenden von nummerischen IDs anstatt von Name bzw. Zeichenketten, wenn immer möglich. Hier kann leicht überprüft werden, ob ein Eingabewert wirklich eine gültige Zahl ist, die unbedenklich in die Datenbankabfrage integriert werden kann.
Ebenfalls sinnvoll könnten so genannte parametrisierte Abfragen sein. Hierbei werden statt den Variablen in den Abfragen Platzhalter eingefügt. Die Variablen (als Parameter) werden dann extra an die Datenbank gesendet. Diese Möglichkeit ist auch schneller, wenn bestimmte Abfragen - mit verschiedenen Variablen - oft ausgeführt werden müssen. Die Datenbank kann die Abfragen im Vorhinein schon optimieren und kann sie, sobald die Parameter für die tatsächliche Abfrage gesendet werden, schneller ausführen.
Auch die Verwendung von Stored Procedures (Gespeicherte Funktionen) kann der Sicherheit dienen. In Stored Procedures werden meist mehrere Datanbankabfragen zusammengefasst. Die Eingabedaten werden ebenfalls als Parameter übergeben. Stored Procedures sind ebenfalls schneller als einzelne Abfragen, weil die Abfragen schon im Vorhinein optimiert werden können und die Kommunikation zwischen Web- oder Applikationsserver und dem Datenbankserver verringert wird.
Sehr wichtig ist, dass in der Webapplikation die Datenbankverbindung mit einem Datenbank-Benutzer erstellt wird, der möglichst wenig Rechte hat. Die Rechte sollten gerade ausreichen, dass mit diesem der Datenbank-Benutzer die geforderten Aktionen durchführen kann. Insbesondere sollte es nicht der Administrator (Super-User) sein. Im Allgemeinen genügen Rechte wie: Abfragen, Ändern, Hinzufügen und Löschen von Datensätzen. In vielen Fällen würde auch nur ein Abfrage-Recht genügen. Der verwendete Benutzer braucht üblicherweise keine Rechte wie: Tabellenstrukturen ändern, Tabellen löschen oder erzeugen, neuen Datenbankbenutzer anlegen, etc.
Darüber hinaus sollten alle Fehlermeldungen von der Datenbank abgefangen und protokolliert (engl.: Logging) werden. Auf diese Protokolldatei (engl.: Log file) soll nur ein eingeschränkter Personenkreis (Administratoren) Zugriff haben. Die Fehlermeldung, die der Benutzer am Browser erhält, sollte sowenig wie möglich für einen Angreifer verwertbare Informationen enthalten.
Auch über SQL-Injection-Angriffe findet man mittlerweile genügend Literatur im Internet mit vielen weiteren Beispielsangriffen. Zusätzlich zu allgemeinen Informationen dazu sollte man am besten auch Artikel, die speziell für die verwendete Datenbank zugeschnitten sind, durcharbeiten. Hier einige Informationen: [Curphey 2002], [SPI 2002].
Manipulieren von Betriebssystembefehlen
Mit den Programmiersprachen für dynamische Webseiten ist es nicht nur möglich auf Datenbanken zuzugreifen, sondern ebenso auf Betriebssystembefehle und externe Programme. Solche Befehle können beispielsweise zur Dateimanipulation (lesen, erstellen, löschen, verschieben, ...) dienen. Oder man könnte sie gebrauchen, um zu einer IP-Adresse den entsprechenden Rechnernamen (DNS) zu finden.
Wenn Teile (Parameter) von solchen Befehlen in irgendeiner Weise vom Browser bzw. vom Benutzer kommen, müssen diese äußerst sorgfältig überprüft werden. Wie bei XSS und SQL-Injection gibt es etliche Zeichen, mit Hilfe derer man die Anwendung kompromittieren kann. Dadurch wäre es möglich, beliebige Befehle am Server auszuführen, oder den Inhalt von Dateien am Browser auszugeben, die für den Benutzer nicht einsehbar sein sollten.
Die Zeichen, die kritisch sind, hängen natürlich vom Betriebssystem des Servers ab. Es ist abzuraten, nur bestimmte, kritische Zeichen herauszufiltern. Es sollten grundsätzlich nur die unbedingt nötigen, unkritischen Zeichen erlaubt werden. Üblicherweise erfolgt diese Überprüfung mit Hilfe von so genannten regulären Ausdrücken (engl.: Regular Expressions). Damit können ganz genaue Überprüfungen mit wenig Programmieraufwand durchgeführt werden. Erwartet ein Programm z.B. eine IP-Adresse als Parameter, kann mit einem regulären Ausdruck verifiziert werden, ob tatsächlich eine IP-Adresse eingegeben worden ist. D.h. Genau vier Blöcke zu je ein bis drei Ziffern. Die Blöcke getrennt durch Punkte. Jede Abweichung davon (Buchstaben, andere Zeichen, nur zwei Punkte, etc.) wird sofort erkannt.
Betroffene Funktionen, also Funktionen, die Betriebssystembefehle aufrufen, bzw. solche, die das Aufrufen dieser ermöglichen, werden unter [Curphey 2002] für verschiedene Programmiersprachen aufgezählt.
Manipulationen der Pfade
Programmcode für Webapplikationen wird meist in mehreren Quellcodedateien aufgeteilt. Außerdem werden oft auch Datendateien, Protokolldateien etc. benötigt. Es sollte dabei vermieden werden, dass Teile des Pfades oder des Namens dieser Dateien aus Eingabedaten des Browsers bestehen. Falls das trotzdem nötig ist, muss hier ebenfalls eine genaue Überprüfung stattfinden. Kritisch sind z.B. zusätzlich eingeführte '../'. Dadurch könnte auch auf Systemdateien zugegriffen werden.
| 3.5.5 | Dateien per HTML-Formular auf den Server laden |
HTML-Formulare bieten auch ein Element vom Typ "file" (<input type="file" ... >). Dieses Element erlaubt es, Dateien, die am Computer des Benutzers gespeichert sind, an den Webserver zu senden, wo sie dann gespeichert werden (Datei-Upload, engl.: File upload).
Dies wird z.B. bei einer Bildergalerie benötigt, wo die Benutzer Bilder auf den Server laden können, die dann andere Benutzer betrachten können. Oder eine Webapplikation zum Verwalten von Werbebannern, wo die Werbenden ihre Werbebanner selbst auf den Server laden können.
Als Programmierer muss man beim Datei-Upload genau darauf achten, dass nur erlaubte Dateitypen auf den Server geladen werden dürfen. Und diese gegebenenfalls nur in bestimmte Verzeichnisse. Angenommen die Dateitypen wären nicht beschränkt und die Dateien werden in ein über den Webbrowser zugreifbares Verzeichnis gespeichert. Dann könnte ein Angreifer ein Script in der Programmiersprache, die der Server verwendet (ASP, PHP, JSP, ...), auf den Server laden und danach vom Browser aus ausführen lassen. Er hat dadurch alle Rechte, die jedes Programm-Script hat: Betriebssystembefehle ausführen, externe Programme starten, Dateien manipulieren, etc.
Beim Überprüfen des Dateityps sollte die Dateiendung und der MIME-Typ (MIME: Multipurpose Internet Mail Extensions) analysiert werden. Der MIME-Typ stimmt meist auch, wenn die Datei eine falsche Dateiendung hat. (Z.B. eine Textdatei mit .jpg Endung.)
Beispiel eines Angriffs aus der Praxis
Im Folgenden ein Beispiel, wie unglücklich Fehler zusammenspielen können, dass ein Angreifer in den Server eindringen kann. In einer vom Autor erworbenen Online-Diskussionsforums-Software, die in PHP geschrieben ist, haben die Forumsmitglieder die Möglichkeit, Dateien an Forumseinträge anzuhängen. Dabei sollten nur Bild- und Textdateien auf den Server geladen werden dürfen. Das PHP-Script überprüfte allerdings nur die Dateiendung, und das inkorrekt: Es wurde geprüft, ob die Datei die erlaubten Dateiendungen (.jpg, .gif, .txt, etc.) enthält und nicht, ob die Datei damit endet! Das heißt, es war möglich, eine Datei wie "attack.gif.php" auf den Server zu laden.
Der Fehler wurde auch entdeckt und ausgebessert, allerdings wurde das betroffene Script unabsichtlich wieder mit einer alten, fehlerhafen Version überschrieben. Ein Angreifer, der von der Sicherheitslücke vermutlich auf der Herstellerseite der Software gehört hat, hat dann mit einer Suchmaschine nach Seiten mit dieser Forumssoftware gesucht.53 Dort hat er ausprobiert, ob die Sicherheitslücke besteht. Er hat ein PHP-Script auf den Server laden können, das mittels Webformularen das Ausführen von beliebigen Befehlen oder Betriebssystemkommandos wie in der Kommandozeile (engl.: Shell) erlaubt.
Dadurch konnte der Angreifer auch die Datei mit den Benutzerkonten herunterladen. Die Datei enthielt in diesem Fall keine Passwörter.54 Ansonsten hätte der Angreifer unsichere Passwörter mittels Brute-Force-Angriff knacken können und Zugriff auf den Server gehabt. Er hätte dann viele Dateien löschen können, den Rechner zum Versenden von Spam-E-Mails oder für einen DDoS-Angriff missbrauchen können.
Bestimmte Dateien hätte der Angreifer auch alleine mit dem oben erwähnten Webformular löschen können. Oder die Forums-Konfigurationsdateien, die die Datenbankpasswörter enthalten, auslesen und die gesamte Forumsdatenbank löschen.
| 3.5.6 | Manipulationen erkennen |
Es wäre natürlich ideal, wenn man Manipulationen an den Eingabedaten vor dem Weiterverarbeiten erkennen könnte. Möglich ist das allerdings nur bei Daten, die zuvor schon vom Webserver zum Webbrowser gesendet worden sind und dann vom Webbrowser wieder zurückgesendet werden. Das sind der Query-String, versteckte Formularelemente (hidden fields) und die Cookies. Diese Daten werden zuerst zum Browser gesendet: Der Query-String als Link, das versteckte Formularelement im HTML-Formular und die Cookies im HTTP-Kopfbereich. Später können sie wieder zurück an den Server gesendet werden. Es ist also möglich, diese Daten am Server vor dem Senden zu verschlüsseln, sodass eine Manipulation erkannt wird, wenn sie wieder zurück an den Server gesendet werden.
Bei Daten, die ursprünglich nicht vom Server kommen, funktioniert das natürlich nicht. Formulardaten (außer versteckte Elemente) können dadurch nicht geschützt werden.
Hash-Wert einfügen
Manipulationen können erkannt werden, indem man aus den zu übertragenden Daten einen Hash-Wert berechnet und diesen gemeinsam mit den Daten an den Browser und wieder zurück an den Server sendet (im Query-String, als verstecktes Element, oder als Cookie). Dazu muss aber eine Hash mit Schlüssel (engl.: keyed hash) verwendet werden. Alternativ können auch die zu schützenden Daten um einen geheimen Schlüsselwert ergänzt werden. Der Hash-Wert ist dann sowohl von den Eingabedaten als auch vom geheimen Schlüssel abhängig. Wäre er nur von den zu übertragenden Daten abhängig, müsste ein Angreifer nur den Hash-Algorithmus ausfindig machen und kann damit auch einen korrekten Hash-Wert finden.
| 3.5.7 | Daten verschlüsseln |
Die oben erwähnten Daten können aber nicht nur gegen Manipulation geschützt werden. Sie können auch verschlüsselt werden, sodass ein Benutzer sie nicht sehen kann. Dabei kann natürlich kein Hash-Algorithmus angewendet werden, sondern es müssen echte Verschlüsselungsalgorithmen unter Verwendung eines geheimen Schlüssels verwendet werden, die auch wieder ein Entschlüsseln erlauben. Durch die Verschlüsselung können zwar die Daten nicht direkt eingesehen werden, aber die verschlüsselten Daten könnten trotzdem manipuliert werden (z.B. im Query-String). Die entschlüsselten Daten könnten in diesem Fall extrem verändert sein. Es bietet sich daher an, zusätzlich einen Hash zu verwenden, um überprüfen zu können, ob die verschlüsselten Daten verändert wurden.
| 3.5.8 | Verwendung von IDs und automatisierter Datendiebstahl |
Um eine Manipulation von Eingabedaten leichter erkennen zu können, wurde in Kapitel 3.5.4 Sichere Verarbeitung der Eingabedaten empfohlen möglichst numerische IDs zu verwenden. Es kann relativ leicht überprüft werden, ob ein Wert auch tatsächlich eine Zahl ist.
Numerische IDs werden häufig von Datenbanken beim Einfügen von Datensätzen automatisch generiert. Meist in fortlaufender Reihenfolge. Wenn es einen Datensatz mit der ID 1025 gibt, ist es wahrscheinlich, dass es auch einen mit 1026 gibt. Soll ein Benutzer nur auf bestimmte Datensätze Zugriff haben, muss dies natürlich bereits bei der Generierung einer Seite berücksichtigt und gegebenenfalls eine Fehlermeldung ausgegeben werden.
Es gibt aber auch Anwendungen, wo ein Zugriff auf alle Datensätze erlaubt ist, es allerdings verhindert werden soll, dass jemand die Daten stiehlt. In einer konkreten Anwendung, bei der der Autor mitarbeitete, können Besucher einer Webseite über eine Volltextsuche nach allen Firmen in Österreich suchen. Als Ergebnis erhält man jeweils eine Webseite der gefundenen Firmen mit Details wie Adressen, Telefonnummern, etc.
Ein Datendieb müsste nur ein Programm schreiben, das diese Webseite mit fortlaufender ID aufruft und die Daten aus den empfangenen HTML-Seiten, die natürlich alle gleich aufgebaut sind, extrahieren.
Ein anderes Beispiel wäre ein Onlineshop-Betreiber, der die mühsam erstellten Produktfotos eines anderen Shops - vielleicht auf einem anderen Kontinent, damit es weniger auffällt - automatisiert herunterlädt. Unter Umständen könnten dabei sogar viele Fotos anhand des Produktnamens automatisch in die Datenbank eingefügt werden.
In jedem Fall ist die Verwendung einer fortlaufenden ID äußerst einladend für einen Datendieb. Es sollte daher auch die ID geeignet verschlüsselt werden, sodass es äußerst schwer fällt, eine gültige ID zu finden. Alternativ könnte zusätzlich zur ID ein Hash-Wert mit einem geheimen Schlüssel (engl. keyed hash: HMAC-SHA-1) in den Query-String integriert werden. Der Schlüssel muss allerdings genügend lang und sicher sein, da der Angreifer dann sowohl den Eingabewert (die ID) als auch den Hash-Wert besitzt und so auch auf dem eigenen Rechner einen Brute-Force-Angriff auf den geheimen Schlüssel starten kann.
Natürlich hilft das alles nichts, wenn - so wie meistens - der Weg zu den Detaildaten nicht nur über ein Suchformular führt, sondern auch über ein Verzeichnis (Produktverzeichnis). Das Programm des Datendiebs kann auch den Links im Verzeichnis folgen und so schließlich auf die einzelnen Detailseiten gelangen. Wenn die Daten wirklich begehrt bei Datendieben sein könnten, sollte daher auch eine Sperre eingebaut werden, die in einem gewissen Zeitabstand die Anzahl der abzurufenden Seiten beschränkt. Zusätzlich müssen natürlich alle Zugriffe in Protokolldateien geschrieben werden, damit ein späteres Ausforschen der Urheber solcher Aktivitäten möglich ist, oder zumindest in Zukunft verhindert werden kann. Beispielsweise durch Sperren des Zugriffs von bestimmten IP-Adressen.
| 3.5.9 | Protokollieren von Aktivitäten |
Das Protokollieren (engl.: Logging) von Webseitenaufrufen übernimmt üblicherweise der Webserver. Er schreibt dabei Datum, Uhrzeit, die IP-Adresse, die angeforderte URL (inkl. Query-String) und noch weitere Daten in eine Textdatei.
Bei Webapplikationen ist es aber ratsam, weit mehr Aktivitäten zu protokollieren. Insbesondere An- und Abmeldevorgänge (vor allem misslungene Anmeldevorgänge), Änderungen von Benutzerrechten, Fehlermeldungen und wichtige Änderungen im Administrationsbereich. Je nach Wichtigkeit der Anwendungen kann es auch notwendig sein, zu protokollieren, welcher Benutzer welche Daten zu welchem Zeitpunkt abgerufen, geändert, hinzugefügt oder gelöscht hat. Eine ausführliche Auflistung von zu protokollierenden Aktivitäten findet man in [Curphey 2002].
| 3.5.10 | Fehlermeldungen |
Es ist besonders wichtig, alle Fehlermeldungen des Systems (der Laufzeitumgebung und der Datenbank) abzufangen und eigene Fehlermeldungen zu generieren. Die Systemfehlermeldungen sollten möglich detailliert protokolliert werden, damit Fehler später genau analysiert werden können. Der Benutzer kann aber mit solchen Fehlermeldungen üblicherweise nichts anfangen. Und mögliche Angreifer sollten durch ausführliche Fehlermeldungen des Systems nicht zusätzliche Information über mögliche Schwachstellen der Webapplikation erhalten.
Die eigens generierten Fehlermeldungen sollten erklären, was der Benutzer falsch gemacht hat und was er dagegen tun kann. Handelt es sich um einen Systemfehler (z.B. wenn die Datenbankverbindung nicht hergestellt werden kann), dann sollte der Besucher aufgefordert werden es nochmals, eventuell später, zu versuchen. In beiden Fällen (Benutzerfehler, Systemfehler), sollte der Benutzer die Möglichkeit haben einen Administrator oder Webmaster zu kontaktieren. Bei einem Systemfehler wäre es sogar ratsam ihn dazu aufzufordern. Da sich aber erfahrungsgemäß viele trotzdem nicht melden, ist das oben erwähnte Protokollieren von Fehlermeldungen und das regelmäßige Überprüfen dieser Protokolle besonders wichtig.
| 3.5.11 | Kryptographie |
Beim Einsatz von Algorithmen zur Ver- und Entschlüsselung oder zum Berechnen eines Hash-Wertes darf nur auf bewährte Algorithmen zurückgegriffen werden. Diese sind seit Jahren von Wissenschaftlern (und vermutlich auch Crackern) auf Sicherheitslücken untersucht worden. Sie sind vom Prinzip aus sicher, obwohl der Algorithmus bekannt ist. [Naber 2002]
Selbst entwickelte Algorithmen werden dagegen selten so genau überprüft und können ganz einfach zu findende Sicherheitslücken enthalten. Viele dieser Eigenentwicklungen scheinen auch nur deswegen sicher zu sein, weil der verwendete Algorithmus geheim ist. Dadurch darf der Quellcode nicht weitergegeben werden. Das ist aber oft nicht vermeidbar.
| 3.5.12 | Eingebundene Quelltextdateien |
Normalerweise besteht das Programm (Script), das eine Webseite generiert, nicht nur aus einer einzigen Quelltextdatei, sondern aus einer "Haupt"-Datei, die andere Quelltextdateien einbindet. Diese Dateien werden im Englischen "include files" genannt. Meist sind diese eingebundenen Dateien ebenfalls in Verzeichnissen, die vom Web aus zugreifbar sind. Dadurch kann man vom Browser aus auf diese Dateien zugreifen. Haben sie dieselbe Endung, wie die ausführbaren Scripts (z.B. .php), dann wird der Code ausgeführt, ansonsten üblicherweise heruntergeladen. Im zweiten Fall ist dadurch der Quelltext sichtbar, was in jedem Fall vermieden werden sollte. Im ersten Fall muss unbedingt darauf geachtet werden, dass kein Schaden auftritt, wenn die eingebundene Datei alleine aufgerufen wird, also nicht als Teil eines größeren Programms. Das ist z.B. dann der Fall, wenn darin nur Funktionen stehen, die extra aufgerufen werden müssten. Geht das nicht, empfiehlt es sich, in der Hauptdatei eine Variable zu setzen. Zu Beginn der eingebundenen Datei wird diese Variable abgefragt. Existiert sie nicht, so wurde die Datei direkt aufgerufen und das Programm sollte abgebrochen werden.
Kritische Informationen in eingebundenen Dateien schützen
Alternativ bietet sich auch an, die eingebundenen Dateien in Verzeichnissen abzulegen, die vom Web aus gar nicht zugreifbar sind. Das ist insbesondere dann wichtig, wenn diese Dateien sicherheitskritische Informationen enthalten. Üblicherweise muss zum Aufbau einer Datenbankverbindung ein Benutzername und ein Passwort angegeben werden. Diese Daten müssen in Konfigurationsdateien abgelegt sein, die dann in den Scripts, die zur Seitengenerierung eine Datenbankverbindung benötigen, eingebunden werden. Üblicherweise ist diese Konfigurationsdatei selber ein Script, das als Programmcode hauptsächlich Variablenzuweisungen enthält. Wird dieses Script in einem vom Web zugreifbaren Verzeichnis abgelegt, muss es so benannt werden, wie die ausführbaren Scripts. Sonst würde ein direkter Aufruf dazu führen, dass es nicht ausgeführt, sondern der Quelltext heruntergeladen wird, wodurch unter Umständen wichtige Daten (Datenbankverbindungsdaten) einsehbar wären.
Ganz sicher ist das aber nicht, da es vorkommen kann, dass durch eine Fehlkonfiguration am Server (z.B. während oder nach einer Systemsoftwareaktualisierung) Scripts grundsätzlich nicht ausgeführt, sondern angezeigt oder heruntergeladen werden. Scripts mit kritischen Konfigurationsdaten sollten daher immer in einem vom Web aus nicht zugreifbaren Verzeichnis abgelegt werden!
| 3.5.13 | Verschiedene weitere Sicherheitsrisiken |
Alte Demo- oder Test-Benutzerkonten
Bevor eine Webapplikation für die Öffentlichkeit freigeschaltet wird, sollte immer kontrolliert werden, ob nicht noch Testkonten (typisch: Name "test", Passwort "test") vorhanden sind. Außerdem ist zu überprüfen, ob eventuell vorhandene Benutzerkonten zu Demonstrationszwecken keinen Schaden anrichten können.
Vergessene alte Dateien oder Scripts
Oft werden im Laufe der Zeit Webapplikationen aktualisiert, aber Dateien, die eigentlich nicht mehr benötigt werden, bleiben am Server liegen. Es ist daher angebracht, laufend zu kontrollieren, ob noch Dateileichen vorhanden sind, die entfernt werden sollten. Das könnten z.B. alte, fehlerhafte Versionen von Webscripts sein, auf die noch irgendein externer Link verweist, und somit noch gefunden werden kann. Oder Sicherheitskopien von Scripts, bei denen die Dateiendungen umbenannt wurden. Diese könnten dann im Quelltext heruntergeladen werden, wodurch ein Angreifer Einblick in den Aufbau der Applikation erhalten könnte.
Ein bekanntes Beispiel dafür sind auch die so genannten "Formmail" Perl-Scripte. Zu Zeiten, als es fast nur statische Webseiten gab und die bevorzugte Programmiersprache für dynamische Webseiten Perl war, war das fortschrittlichste an einer Webseite ein Kontaktformular, dessen Inhalt per E-Mail an den Webmaster gesendet werden konnte. Dafür gab es bereits ein fertiges Script, das meist "formmail.pl" oder "formmail.cgi" benannt wurde. Dieses Script war bei vielen Webspace-Providern bereits standardmäßig installiert. Das Script hat aber zumindest eine große Sicherheitslücke: Es erwartet die E-Mail-Adresse, an die die E-Mail gesendet werden soll, auch als Parameter (als verstecktes Formularelement.) Zusätzlich akzeptiert es die Parameter auch, wenn der Formularinhalt per GET-Methode (also im Query-String) versendet wird. D.h., es ist möglich, durch Aufrufen des Formmail-Scripts mit geeigneten Parametern beliebige E-Mails an beliebige E-Mail-Adressen zu versenden. Und das Ganze über den Server, auf dem das Formmail-Script installiert ist. Das ist natürlich das Paradies für Spam-Mail-Versender, die damit zigtausend unerwünschte Werbe-E-Mails versenden.
Anscheinend müssen noch immer viele solcher Scripts mit Sicherheitslücken installiert sein. Auf zwei Webseiten des Autors wurde im Februar 2003 über 150 mal getestet, ob dieses Script vorhanden ist.
Kommentare im HTML-Code
Normalerweise sollte Programmcode ausführlich kommentiert werden, damit die Wartung des Codes später einfacher ausfällt. Allerdings sieht bei üblichen Programmen der Benutzer niemals die Kommentare. Im ausführbaren Programm sind diese nicht mehr enthalten und werden schon gar nicht ausgegeben. Beim HTML-Code ist das etwas anderes. Der Benutzer kann einfach den Quelltext betrachten und dabei auch die Kommentare lesen. Kommentare sollten daher niemals Rückschlüsse auf Implementierungsdetails zulassen, die von potentiellen Angreifern ausgenützt werden könnten.
Es stellt sich auch grundsätzlich die Frage, wie sinnvoll Kommentare im HTML-Code überhaupt sind. Für jeden Programmierer für Webapplikationen sollte der HTML-Code selbsterklärend sein. Kurze Kommentare sind meist weniger aussagekräftig als der Code selbst. Lange blähen den Code auf, was sich negativ auf die Ladezeit auswirkt.
Sinnvoller ist es, die Programm-Scripte zu kommentieren, die die HTML-Seiten erzeugen. Wobei hier auch zu beachten ist, dass diese Scripte teilweise erst beim Aufruf kompiliert (Stichwort JIT) oder gar interpretiert werden. Allzu ausführliche Kommentare sollten daher (zumindest bei geschwindigkeitskritischen Anwendungsfällen) ebenfalls vermieden werden. Der Code der modernen Programmiersprachen ist im Detail meist selbsterklärend. Eine externe Dokumentation, die den groben Aufbau eines Scripts beschreibt, ist oft sinnvoller.
Allerdings geht der Trend eindeutig in Richtung Zwischencode (wie Microsofts MSIL oder Java-Bytecode), oder Zwischenspeichern des kompilierten und somit ausführbaren Codes (z.B. bei PHP), sodass auch ein längerer Quellcode mit ausführlichen Kommentaren keine Geschwindigkeitseinbußen mehr zur Folge hat.
Nullbytes
Wie im Kapitel 3.5.3 Manipulierbare Eingabedaten erklärt, können im Query-String beliebige Zeichen an den Webserver übertragen werden, indem sie hexadezimal codiert werden und ein '%' vorangestellt wird (URL-codiert). Dabei kann auch das Zeichen mit dem Zahlenwert null übertragen werden. D.h., es ist möglich eine Zeichenkette an das Script am Server zu senden, die an beliebiger Stelle ein Nullbyte (Byte mit dem Wert null) enthält.
Die zeichenkettenspezifischen Befehle des Prozessors sind so ausgelegt, dass ein Byte mit dem Wert null das Ende der Zeichenkette markiert. Das ist auch bei der sehr hardwarenahen Programmiersprache C (oder C++) so. Die meisten Script-Sprachen, die bei der Programmierung dynamischer Webseiten verwendet werden, sind in C bzw. C++ geschrieben. Die Script-Sprachen selbst verwenden aber meist andere Methoden um das Ende einer Zeichenkette zu markieren (z.B. Länge der Zeichenkette abspeichern). Dadurch kann es vorkommen, dass Zeichenketten, die Nullbytes enthalten, bei manchen Codesegmenten bzw. Funktionsaufrufen komplett abgearbeitet werden, bei anderen wiederum nur bis zum Nullbyte. Beispielsweise könnte eine Überprüfung auf illegale Zeichen beim Nullbyte abbrechen, später aber die gesamte Zeichenkette - auch nach dem Nullbyte - bearbeitet werden. [Curphey 2002]
| 3.5.14 | Verhalten beim Entdecken von Sicherheitslücken |
Als Kunde bzw. als Benutzer
Findet man eine Sicherheitslücke in einer fremden Webapplikation, wäre es denkbar schlecht, diese inklusive genauer Beschreibung und Anleitung zum Ausnützen sofort öffentlich bekannt zu geben, beispielsweise im Diskussionsforum des Herstellers. Das wäre eine Einladung für potentielle Hacker oder Cracker. Viel vernünftiger ist es den Hersteller zu kontaktieren und ihm das Problem darzulegen. Reagiert der Hersteller darauf nicht, so kann durchaus mit Veröffentlichung gedroht werden. Im Extremfall ist eine Veröffentlichung sicher sinnvoll. Im besten Fall auch mit einem Patch zum Schließen der Lücke.
Als Hersteller
Wird der Hersteller auf eine Sicherheitslücke aufmerksam, so muss dieser so schnell als möglich die Lücke beseitigen. Erst dann sollte er alle Kunden kontaktieren. Falls der Kundenkreis klein ist und Kundenkontaktadressen vorhanden sind, genügt das. Bei einem größeren Kundenkreis (z.B. Opensource-Software, wo nicht unbedingt jeder Anwender der Applikation kontaktiert werden kann) muss die Beseitigung der Sicherheitslücke natürlich auch öffentlich angekündigt werden. Es ist aber im Allgemeinen nicht ratsam zu sehr ins Detail zu gehen, wodurch Angreifer kritische Informationen erhalten könnten.
Das Problem ist allerdings, dass viele Anwender der Software keine Softwareaktualisierungen vornehmen. Bei den Meldungen über Viren und Würmer liest man ständig, dass sich diese deswegen so rasch verbreiten, weil oft seit Monaten bekannte Sicherheitslücken nicht beseitigt wurden, obwohl hier das Aktualisieren (meist Installieren von so genannten "Patches") relativ einfach ist.
Bei Webapplikationen kann das Aktualisieren der Software dagegen sehr mühsam sein, wenn die Software an die eigenen Bedürfnisse angepasst wurde. Diese Anpassungen müssten dann bei der neuen Version wieder durchgeführt werden.
Insofern wäre für manche Anwender der Webapplikation eine Beschreibung, welche Dateien wie geändert werden müssen, sinnvoller als eine komplett neue Version einzuspielen. Noch dazu, wo beim Beheben von Sicherheitslücken oft nur wenige Zeilen (teilweise nur eine) Code geändert werden müssen.
| 3.5.15 | Testen von Webapplikationen auf Sicherheitslücken |
Wie grundsätzlich beim Testen auf Fehlerfreiheit von Anwendungen sollte auch das Testen auf mögliche Sicherheitslücken von anderen Personen als den Entwicklern durchgeführt werden. Im Idealfall von eigenen Spezialisten, die sich ausführlich mit diesem Thema beschäftigen und sich ständig auf dem aktuellsten Stand halten. Es muss natürlich schon beim Entwickeln auf die Sicherheit geachtet werden. Wobei die Entwickler Feedback von den Testern der Anwendungen erhalten sollen.55
Das Testen kann durch geeignete Werkzeuge teilweise unterstützt oder automatisiert werden. Das "Open Web Application Security Project" entwickelt solche Werkzeuge als Open Source. Der Link zur Seite: www.owasp.org. Im Archiv der "Web Application Security" Mailingliste findet man ebenfalls eine Diskussion über solche Werkzeuge, wo auch einige Produkte erwähnt wurden: www.securityfocus.com/archive/107/2003-03-02/2003-03-08/0 (Thema: Web Appliction Source Vulnerability Scanners)
| 3.5.16 | Literatur zum Thema Sicherheit bei Webapplikationen |
Ein sehr guter Startpunkt zum Thema ist die Webseite vom "Open Web Application Security Project" (www.owasp.org). Insbesondere der "Guide to Building Secure Web Applications" [Curphey 2002] und die Artikel unter "ASAC". Eine Sammlung von Artikeln und Arbeiten findet man auch auf www.cgisecurity.com, insbesondere unter "Library". Ein Großteil der Literatur, die in diesen Artikeln in den jeweiligen Literaturverzeichnissen angegeben sind, ist ebenfalls online verfügbar.
Eine interessante Quelle, die über aktuelle Sicherheitslücken informiert, ist www.securityfocus.com. In der Mailingliste "Web Application Security" (www.securityfocus.com/archive/107) werden Themen rund um die Sicherheit bei Webapplikationen diskutiert.
53Das hat der Autor anhand der Protokolldatei des Webservers zurückverfolgen können.
54Ursprünglich enthielt die Datei /etc/passwd beim Linux Betriebssystem alle Benutzerkonten inkl. verschlüsselter Passwörter. Da viele Programme die Benutzerdaten brauchten, mussten sie auf diese Datei zugreifen können. Damit musste die Datei für jeden lesbar sein. Das erleichterte es Angreifern von dynamischen Webseiten bzw. Webapplikationen erheblich, die Datei herunterzuladen und zuhause durch einen Brute-Force-Angriff unter Umständen unsichere Passwörter zu knacken. Mittlerweile werden die Passwörter aber in einer eigenen, nur für den Administrator (root-Benutzer) lesbaren, Datei gespeichert.
55In der Web Application Security Mailingliste auf www.securityfocus.com fand Anfang März 2003 eine Diskussion zu diesem Thema statt: www.securityfocus.com/archive/107/2003-03-02/2003-03-08/0.
| Nächstes Kapitel >> |
| Zum Seitenanfang | diplomarbeit_o a-t schmiderer d-o-t cc | 2003-04-25 |