Security Patterns

Die zehn größten Sicherheitslücken im Web – Teil 2
Kommentare

Das letzte Mal haben wir uns mit SQL-Injection und Broken Authentication auseinander gesetzt. Dieses Mal geht es unter anderem um das sehr große Thema Cross-Site Scripting, und wir schauen uns die Serverkonfiguration genauer an, die nicht nur dabei helfen kann, mögliche Angriffe einzudämmen, sondern auch entsprechend vorzubeugen.

Anschließend an das Authentication-Management sollten wir auch noch einen Blick auf das damit verbundene Sessionmanagement werfen, da Sessions doch heute als Standard dazu genutzt werden, um die Authentifizierung zu speichern. So bringt die beste Absicherung der Authentifizierung leider nichts, wenn danach mit relativ geringem Aufwand die bereits authentifizierte Session übernommen werden kann.

Session Hijacking

Damit wir uns mit Session-Hijacking beschäftigen können, sollten wir zunächst verstehen, wie eine Session genau funktioniert. Zur Veranschaulichung dient Abbildung 1.

Abb. 1: Beim Session Hijacking wird die Session-ID des Opfers abgehört und dann zum Zugriff auf das eigentlich geschützte System genutzt

Abb. 1: Beim Session Hijacking wird die Session-ID des Opfers abgehört und dann zum Zugriff auf das eigentlich geschützte System genutzt

Vereinfacht gesehen ist eine Session nicht mehr als ein Array aus Daten, die zu einer eindeutigen ID zugeordnet sind. Der Zugriff auf diese Sessiondaten ist prinzipiell jedem möglich, der diese ID kennt, eine weitere Absicherung erfolgt standardmäßig nicht, bzw. muss manuell implementiert werden. Eine Möglichkeit wäre z. B. das Binden der IP-Adresse an die Session, sodass nur eine bestimmte IP Zugriff auf diese Session hat. Gerade mit der steigenden Anzahl mobiler Zugriffe ist dies aber auch ein Problem, da sich unterwegs doch sehr schnell und häufig die IP-Adressen ändern können und man sicherlich keinem User zumuten möchte, sich bei jedem Wechsel neu einloggen zu müssen. Eine Absicherung über den Useragent ist auch nicht sicher, da dieser problemlos von jedem Angreifer manipuliert werden kann. Es muss also sichergestellt werden, dass Session-IDs gar nicht erst einem Dritten bekannt werden und somit kein Angriff stattfinden kann.

Wie wird eine Session-ID überhaupt generiert? Hierfür ist in der php.ini die Einstellung session.hash_function verantwortlich. Ist dies nicht explizit gesetzt, nutzt PHP per Default md5, um die Hash-ID zu generieren, was eine neue Session-ID von einem Angreifer relativ einfach vorhersehbar macht. Durch das Setzen auf eine bessere Hash-Funktion wie sha256 ist es sehr viel schwerer, an die generierte Session-ID zu kommen.

Der beste Hash-Algorithmus hilft aber leider nicht, wenn die Session-IDs offen lesbar im Netz publiziert werden. So kommt es nicht selten vor, dass User Links inkl. Session-IDs in Mails oder Foren posten und so relativ einfach diese Session übernommen werden kann. Um dies zu verhindern, sollte der Wert session.use_trans_sid auf Off gestellt werden, damit werden die Session-IDs nur noch im Cookie gespeichert und eine Übergabe durch einen kopierten Link nicht mehr möglich.

Als weiterer Schutz dient session.cookie_secure, dies führt dazu, dass der Session-Cookie, der nun die einzige Referenz zur Session-ID enthält, nur verschlüsselt übertragen wird und somit nicht ausgespäht werden kann.

Allerdings werden Cookies direkt beim Client gespeichert und sind auch über JavaScript auslesbar, was im Falle einer XSS-Attacke auch wieder ermöglicht, die Session-ID auszulesen und einem Angreifer zu übermitteln. Hier hilft die Einstellung session.http_only, die das Auslesen der Session-Cookies via JavaScript unterbindet.

Session Fixation

Eine weitere sehr verbreitete Technik ist die der Session Fixation (Abb. 2).

Abb. 2: Bei einer Session Fixation wird eine Session-ID untergeschoben, nach der Authentifizierung steht dann auch dem Angreifer bei der Nutzung dieser ID der Zugriff zur Verfügung

Abb. 2: Bei einer Session Fixation wird eine Session-ID untergeschoben, nach der Authentifizierung steht dann auch dem Angreifer bei der Nutzung dieser ID der Zugriff zur Verfügung

Das Problem liegt darin, dass PHP prinzipiell jeden String als valide Session-ID akzeptiert und keine weitere Validierung stattfindet, ob diese Session wirklich existiert. Sofern die Session mit der ID nicht existiert, wird nämlich einfach eine entsprechende Session generiert. Der Trick liegt also darin, einem Opfer einfach einen entsprechenden präparierten Link mit einer Session-ID unterzujubeln. Das nichtsahnende Opfer loggt sich dann mit dieser vordefinierten Session-ID auf der Seite ein, die Session wird also authentifiziert und auch der Angreifer kann dann problemlos mit der ihm bekannten Session-ID auf die geschlossenen Bereiche der Seite zugreifen.

Eine Möglichkeit, dies zu unterbinden, ist, den Flag session.use_only_cookies zu aktivieren. Damit akzeptiert PHP keine Session-IDs mehr, die über einen GET-Parameter übergeben wurden, sondern nur noch, wie der Name schon sagt, IDs, die über einen Cookie gesetzt sind.

Des Weiteren ist es zu empfehlen, sobald sich ein User authentifiziert hat, eine neue Session mit einer neuen zufälligen Session-ID zu generieren und nur diese neue Session zu authentifizieren, sodass mit der untergeschobenen Session-ID kein Angriff stattfinden kann (Abb. 3).

Abb. 3: Wenn die Session-ID bei der Authentifizierung neu generiert wird, ist eine Angriff via Session Fixation nicht mehr möglich

Abb. 3: Wenn die Session-ID bei der Authentifizierung neu generiert wird, ist eine Angriff via Session Fixation nicht mehr möglich

Last but not least sollte die Session-Lifetime reduziert werden, sodass die Zeit, die ein Angreifer hat, um auf eine Session zuzugreifen, zumindest verkürzt wird und somit ein Angriff wiederum unwahrscheinlicher wird.

XSS

Eines der Hauptprobleme in heutigen Webapplikationen ist und bleibt Cross-Site Scripting. Der Name ist dabei etwas verwirrend, denn die darunter verstandenen Angriffsszenarien müssen gar nicht durch eine zweite Seite stattfinden oder von dort Daten laden. Die Angriffe finden häufig auf einer einzelnen Seite statt, indem dort HTML-, CSS- oder JavaScript-Code injected wird und im Idealfall persistent vorhanden ist, indem der Angriff in der Datenbank, den Session oder Cookies gespeichert wird.

Was kann also passieren? Angefangen bei einfachem Defacing der Website, Austausch des Contents, Verfälschung von Aussagen und definitiv schlechter PR für das Unternehmen, ist es auch möglich, den kompromittierten Client dazu zu nutzen, auf sensitive, geschützte Bereiche zu leiten und dort automatisiert Aktionen auszuzuführen, ohne dass der User davon etwas mitbekommt. So wäre es möglich, gesicherte Daten auszulesen und weiterzugeben oder z. B. im Hintergrund einen Useraccount mit weiteren Rechten anzulegen, der dann dem Angreifer zur Verfügung steht.

Das Ganze wird durch XSS-Proxies zur Perfektion getrieben, so gibt es inzwischen voll ausgebaute Applikationen, um über XSS-Lücken ganze Botnetze durch die kompromittierten Browser aufzubauen und nicht nur die gehackte Website zu schädigen, sondern darüber z. B. auch sehr gut nachempfundene Logins für weitere Dienste wie Facebook einzublenden, die dann natürlich nach Hause telefonieren oder auch mal nahezu unbemerkt die Webcam des Users aktivieren.

Sehr clever ist dabei, dass diese XSS-Proxies sich persistent auf der Website einnisten, sobald diese einmal geladen sind, tauschen diese alle Link-Tags durch AJAX Requests aus. Normalerweise wäre ein XSS-Hack nutzlos, sobald die betroffene Seite verlassen wird, da der JavaScript-Code komplett geleert und das Skript gestoppt wird. Durch den Austausch der Links werden die Inhalte beim Klick auf einen der Links zwar geladen und dem Client angezeigt, sodass der User zunächst nichts bemerkt, da der Content aber im Hintergrund geladen wird und kein komplettes Neuladen der Seite erfolgt, bleibt der JavaScript-Code und somit auch der XSS-Proxy weiterhin geladen und kann somit auch weiterhin Informationen ausspionieren, obwohl diese Seiten eigentlich gar keine XSS-Lücke enthalten.

Allerdings ist nicht nur die ursprünglich gehackte Website in Gefahr, denn von dieser können u. a. auch Requests an ein internes Netz (eine interne Jira-Instanz, Forum oder Router) abgesetzt werden. Die Same-Origin Policy (Kasten: „Same-Origin Policy“) verhindert dabei zwar, dass die Response ausgelesen werden kann, der Request wird aber trotzdem abgesetzt, so kann durch das Messen der Response-Time dennoch eine Abfrage gesendet werden. In Kombination mit einer Time-based SQL Injection (siehe PHP Magazin 2.2014) ist es dann weiterhin möglich, auch Daten aus einem internen System auszulesen.

Same Origin Policy Die Same-Origin Policy (SOP) soll verhindern, dass kompromittierte oder manipulierte Websites Daten von vertrauenswürdigen Quellen auslesen können. So werden z. B. AJAX Request oder DOM-Zugriffe Host-übergreifend untersagt, hierbei werden neben dem Domainnamen auch das Protokoll (http oder https) und der Port des Servers überprüft. SOP wurde dabei erstmalig 1996 von Netscape implementiert und ist heutzutage in allen modernen Browsern zu finden.

Sicherlich ist jedem Entwickler das Grundprinzip von Escaping zur Verhinderung von XSS bekannt. Doch wieso gibt es dennoch so viele XSS-Lücken? Das Problem ist, so einfach und simpel Escaping auch klingt, so schwer ist es zugleich, dies immer richtig umzusetzen. Ein einfacher Aufruf von htmlspecialchars reicht leider schon mal nicht aus, um ein HTML-Attribut zu escapen, denn hier sollten mindestens neben dem Zeichensatz die Flags ENT_QUOTES und ENT_SUBSTITUTE übergeben werden, sodass auch einfache Anführungszeichen und ungültige Code-Unit-Sequenzen berücksichtigt werden. Wie sieht dies aber für JavaScript aus? Welche Zeichen müssen hier nochmal berücksichtigt werden? Ein guter Ansatz ist die Nutzung von json_encode, um somit zunächst jede Übergabe von PHP in ein einen JSON-String zu konvertieren. Je nachdem in welchem Kontext man sich befindet, muss das Escaping entsprechend anders aussehen. Möchte man zudem bewusst HTML-Code einfügen, ist eher ein Sanitizer wie HTMLPurifier zu empfehlen, denn ein Escaping würde nicht zu dem gewünschten Resultat führen. Zudem kann der Kontext auch verschachtelt sein. Das Attribut onmouseover ist nicht nur ein HTML-Attribut, sondern der Inhalt gleichzeitig auch JavaScript-Code, selbiges im href-Attribut des a-Tags, wenn man den einfachen String javascript: vor seinen auszuführenden Code schreibt.

All diese Ausnahmen führen dazu, dass besonders Escaping immer noch sehr häufig falsch gemacht wird und dies nicht nur aufgrund von Unwissenheit, sondern weil das Thema sehr komplex ist und auch der erfahrenste Entwickler Fehler macht.

Um diese Aufgabe zumindest etwas zu erleichtern, sollte eine Template-Engine genutzt werden, die das kontextabhängige Escapen beherrscht und sich zumindest um den Dschungel an Funktionen und Flags kümmert, ohne dass man selbst über die Details Bescheid wissen muss. Die Übergabe des richtigen Kontexts bleibt aber weiterhin Aufgabe des Entwicklers, da so genanntem Auto-Escaping definitiv nicht blind vertraut werden sollte. Die entsprechenden Anwendungsfälle (HTML in JavaScript, JavaScript in HTML-Attributen, URLs in JavaScript oder HTML) sind dafür zu umfangreich und komplex.

Etwas Hilfe schafft hier unter anderem der Content-Security-Policy-Header (CSP, Kasten: „Content Security Policy“). Dieser verhindert zum einen das Ausführen von Inline-JavaScript und -CSS, was es nicht nur viel schwerer macht, über eine XSS-Lücke größeren Schaden anzurichten und gleichzeitig dazu führt, dass der Quellcode besser strukturiert wird, denn ganz ehrlich: Verschiedene Sprachen (HTML und CSS/JS) sollten sich nicht in ein und derselben Datei wiederfinden. Zum anderen werden aber auch Aufrufe von JavaScript-Dateien außerhalb der eigenen Domain unterbunden, sodass z. B. ein bösartiger XSS-Proxy nicht von einer externen URL geladen werden kann.

Aufmacherbild: <a href=“http://www.istockphoto.com/vector/staircase-16556481″ title=“Staircase – Illustration von Shutterstock / Uhrheberrecht: gavni “ class=“elf-external elf-icon elf-external elf-icon“ rel=“nofollow nofollow“>Staircase – Illustration von Shutterstock / Urheberrecht: gavni [ header = Seite 2: Content Security Policy ]

Content Security Policy Der CSP-Header ist leider nicht in allen Browsern gleich implementiert, so müssen insgesamt drei Header gesetzt werden, um alle Browser abzufangen:

  • Content Security Policy: Ist der Standard laut W3C und wird von Chrome ab Version 25, Safari ab Version 7, Opera ab Version 15 und Firefox ab Version 23 unterstützt.
  • X Content Security Policy: Ist der Header für ältere Firefox-Versionen und den Internet Explorer ab Version 10. Ältere Internet-Explorer-Versionen unterstützen leider keinen CSP-Header.
  • X WebKit CSP: Wird von älteren Chrome-Versionen unterstützt.

Wenn man weiterhin auch JavaScript von Google laden möchte, kann man die Regeln etwas lockern und neben der eigenen Domain auch weitere Zugriffe erlauben:

Content-Security-Policy: script-src 'self' htt ps://ajax.googleapis.com

Neben script-src gibt es auch noch weitere Direktiven: default-src (als Fallback, wenn es keine genauere Definition gibt), object-src, style-src, img-sc, media-src, frame-src, font-src und form-action, deren Funktion sich jeweils durch ihren Namen ergibt. Zudem kann mit report-uri eines URLs bestimmt werden, wann die entsprechenden Verstöße gegen die Policy gemeldet werden.

Insecure Direct Object Reference

Objektreferenzen sind ein sehr weit gefächertes Thema und fangen u. a. bei der Referenz von Dateien über einen GET-Parameter an. Wer kennt nicht entsprechende Schwachstellen, in denen das Sprachkürzel via include eine Übersetzungsdatei einliest? Dieses Extrembeispiel ist zum Glück sehr selten geworden, häufig zu sehen sind aber u. a. noch Datenbankreferenzen in den value-Attributen von Drop-downs oder jedweder Art von Listen. Welche Schlüsse können wir aus folgendem Drop-down schließen?

 
    moderator
    editor

Egal in welchem Kontext dieses Eingabefeld zu sehen ist, ist doch sehr wahrscheinlich, dass die IDs 2 und 3 eine direkte Objektreferenz sind. Man kann sich dabei ziemlich sicher sein, dass es auch eine ID 1 gibt, obwohl diese aktuell nicht ausgegeben wird. Neben der Information, dass es also auch noch weitere Userarten gibt, kann im Worst Case das Formular ggf. auch direkt manipuliert werden und der entsprechenden Request dann auf die ID 1 (die möglichweise für einen Administrator steht) referenzieren. Je nachdem wo wir das Drop-down dann finden, können darüber evtl. sogar Userrechte manipuliert oder zumindest weitere Informationen ausgelesen werden.

Eine sehr interessante Information ist auch immer, wie viele Datensätze denn in einer entsprechenden Tabelle existieren. Primary Keys, die mit einem Standardautoinkrement belegt sind, geben darüber sehr gern Informationen preis. So lässt sich sehr schnell validieren, ob sich ein Angriff aufgrund der Anzahl an Usern lohnt und ob die Webseite überhaupt aktiv genutzt wird.

Neben der Überprüfung der Berechtigungen, ob der aktuell authentifizierte User überhaupt die Informationen zu einem bestimmten Datensatz auslesen oder ändern darf, sollte man also auch immer versuchen, eine indirekte Objektreferenz herzustellen. Dafür sollte zu jeder Referenz ein zufälliger String generiert und im Hintergrund ohne Übermittlung an den Client, z. B. in der Session, zu der Originalreferenz zugeordnet werden. So werden statt den originalen IDs zufällige Strings übermittelt und keine Möglichkeit geboten, Referenzen leicht zu manipulieren oder Informationen zu erhalten, die für einen weiteren Angriff hilfreich sein können.

Security Misconfiguration

Auch in der Konfiguration des Webservers kann einiges getan werden, um einen Angriff zu erschweren. Im Apache sollten typische Standardeinstellungen wie AllowOverride All definitiv deaktiviert werden. Dies erlaubt zahlreiche Einstellungen des Webservers via .htaccess-Datei zu überschreiben. Hier gilt prinzipiell: je restriktiver desto besser, auch wenn ein AllowOverride None sicherlich nicht für jedes Produktivsystem funktioniert. Zudem ist dringend zu empfehlen, Optionindexes zu setzen, was ein Auslesen des Inhalts eines Webverzeichnis verhindert und sonst auch wieder Informationen bieten würde, welche Dateien sich auf dem Server befinden und welche Größe diese haben.

TraceEnable sollte zudem deaktiviert werden, es liefert Informationen über die aktuellen Cookies und Sessions an den Client. Zunächst nicht weiter gefährlich, kann dies aber bei einer XSS-Schwachstelle wieder dazu genutzt werden, an genau diese sehr wichtigen Daten zu gelangen.

Seit Kurzem gibt es speziell zum Absichern der php.ini ein sehr gutes Projekt namens iniscan auf GitHub. Dieses scannt die Datei und macht entsprechende Verbesserungsvorschläge. Typische Fehler sind hier meist ein viel zu großes memory_limit, da irgendein Import oder ähnlich ressourcenfressendes Skript leider sehr viel Speicher verbraucht. Oft handelt es sich dabei um Legacy-Code, der natürlich nicht mehr so leicht optimiert werden kann. Es ist aber vollkommen ausreichend, nur diesem einem Skript entsprechend viel Speicher zu geben, der Rest der Applikation sollte mit einem moderaten Wert auskommen, womit u. a. auch DoS-Angriffe eingeschränkt werden.

Definitiv muss die Ausgabe von Fehlermeldungen mit display_errors auf einer Produktivumgebung deaktiviert und stattdessen log_errors aktiviert werden. Genauso wichtig wie das Setzen der Option ist aber auch das Überprüfen und Auswerten der Logdateien. Viel zu oft bleiben diese unberührt auf dem Server liegen und bieten dabei nicht nur Informationen über Fehler in der Applikation, sondern auch Hinweise auf mögliche Angriffsversuche.

Wer https einsetzt, sollte unbedingt einen Blick auf das Duraconf Repository werfen, denn das bloße Aktivieren von TLS/SSL macht die Verbindung leider nicht wirklich sicher. In dem genannten Repository ist allerdings sehr einfach und ausführlich erklärt, welche Schritte wirklich notwendig sind, um eine sichere Verbindung zu konfigurieren, die von niemandem, außer der NSA, abgehört werden kann.

Sensitive Data Exposure

Zusammenhängend mit der Serverkonfiguration ist auch die Preisgabe von Serverinformationen wie verwendeter Webserversoftware und Versionsnummer zu verhindern, da diese Daten schnell auf bekannte Sicherheitslücken schließen lassen (Abb. 4). Technisch notwendig ist auch das Übermitteln der PHP-Version nicht und wird nur zur Auswertung von Statistiken genutzt, somit sollte php_expose auf Off stehen.

Abb. 4: Eine Suche nach frei zugänglichen „phpinfo“-Dateien liefert viele Informationen über veraltete Systeme und damit Potenzial für Angriffe

Abb. 4: Eine Suche nach frei zugänglichen „phpinfo“-Dateien liefert viele Informationen über veraltete Systeme und damit Potenzial für Angriffe

Über die Absicherung der Session hatten wir bereits gesprochen, entsprechend sollte aber natürlich auch darauf geachtet werden, dass keine sensitiven Daten in einem URL zu finden sind. So ist wiederum eine indirekte Objektreferenz zu empfehlen, statt die Übergabe von Name, E-Mail oder sonstigen Informationen als Parameter im URL. Je nach Anwendungsfall kann es auch ggf. sinnvoll sein, die komplette Website und nicht nur einzelne Seiten über eine verschlüsselte HTTPS-Verbindung auszulesen. Zwar kostet dies mehr Serverressourcen, doch wenn die Daten erst mal gestohlen wurden, ist es zu spät.

Ausblick

Im nächsten und letzten Artikel geht es u. a. um die korrekte Erstellung von Passwort-Hashes, mit einem Blick auf das neue Password-API in PHP 5.5, die richtige Implementierung eines ACL und dem großen Problem von Cross-Site Request Forgery.

Weiter mit: Teil 1

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -