»Wenn der Affe zuschaut, ptlanze ich keine Erdnüsse.«
Sprichwort der Tiv
Dieses Kapitel wendet sich in erster Linie an Leser, die Webapplikationen schreiben wollen. Sicherheitslücken gibt es überall, sie werden aber vor allem unter den folgenden drei Bedingungen ausgenutzt: Es ist viel Geld im Spiel, man hat die Chance, mit fremden Daten Unfug zu treiben, oder man kann einen fremden Server zu bösen Dingen wie Spamversand missbrauchen. Ein in einer Arbeitsgruppe genutztes Datenanalysemol oder ein privates Haushaltsverwaltungsprogramm reizt kaum jemanden dazu, die vielleicht enthaltenen Sicherheitslöcher ausfindig zu machen. Das Hacken von Desktopanwendun-gen wird in der Hacker-Vorschule vielleicht noch als Übungsaufgabe praktiziert, aber niemand, der laufen und sprechen kann, interessiert sich wirklich dafür.
Zwar machen auch Sicherheitslücken in Desktopsoftware wie Microsoft Office oder Adobe Reader immer wieder Schlagzeilen, aber dabei handelt es sich um Software, die bei Millionen von Nutzern zum Einsatz kommt - und damit ein attraktives Angriffsziel bietet. Ihre selbstgestrickte Software für wissenschaftliche Reports, Ihr selbstgeschriebenes Sudoku oder ein kleines Helferskript werden sehr wahrscheinlich über zwei Handvoll Nutzer nie hinauswachsen. Und Software für Aktienhandel oder Homebanking werden Sie hoffentlich so bald nicht entwickeln wollen.
Anders sieht es aus, wenn Sie ein kleines Webprojekt umsetzen. Der überwiegende Anteil aller ausgenutzten Sicherheitslücken befindet sich in Software, die mit dem Internet interagiert (zum Beispiel Browsern und deren Plugins) oder als Webapplikation Dienste im Internet bereitstellt (Webforen oder Content-Management-Systeme). Auch wenn die Benutzerzahl Ihrer Webapplikation klein bleibt, kann praktisch jeder Internetnutzer auf sie zugreifen. Dabei ist die Applikation selbst für Hacker möglicherweise ganz uninteressant, sie ist aber das Einfallstor, durch das andere Menschen Zugriff auf Ihren Server erhalten. Der wird dann als Spamschleuder oder als Kommandozentrale für den dritten Weltkrieg missbraucht.
Reden Sie sich nicht ein, Ihre Webapplikation wäre so unbedeutend oder unbekannt, dass nichts Böses passieren könnte. Es gibt viele Menschen mit technischen Fähigkeiten und ausgeprägtem Gewinnstreben oder einer boshaften Natur, und früher oder später wird sich einer davon Ihre Anwendung vornehmen und nach Lücken suchen.
Selbst firmeninterne Anwendungen, die nur im LAN erreichbar sind, sollten mit einem wachen Auge daraufhin überprüft werden, was schlimmstenfalls passieren kann. Haben Mitarbeiter unkontrollierten Zugriff auf beliebige Datenmengen? Was geschieht, wenn ein besonders motivierter Mitarbeiter die Anwendung knackt? Dass derjenige damit einen Grund für eine fristlose Entlassung liefert, ist nur dann tröstlich, wenn er nicht Ihre Kundenliste oder sonstige interne Daten mitgenommen hat. Derartige Vorfälle werden normalerweise nicht bekannt. In die Medien haben es aber einige spektakuläre Fälle geschafft, darunter der 2008 bekannt gewordene Datendiebstahl bei der Liechtensteiner Bank LGT Treuhand. Hier war der Täter ein mit der Überprüfung eingescannter Unterlagen betrauter externer Mitarbeiter. Und der Geheimdienstanalyst Edward Snowden, der im Jahre 2013 große Teile der NSA-lnternetüberwachung öffentlich machte, konnte das nur tun, weil er Zugang zu vielen internen Powerpoint-Präsentationen über die Überwachungsprogramme hatte.
Eigentlich ist der Begriff »Sicherheit« irreführend. Wo ein Austausch mit der Außenwelt stattfindet, da gibt es keine hundertprozentige Sicherheit. Der menschliche Körper arbeitet schon seit einigen Millionen Jahren daran, einerseits Luft und Nahrungsmittel aus der Umwelt aufzunehmen und andererseits Krankheitserreger abzuwehren, und doch stirbt ab und zu jemand an einer Infektion. Egal, ob Sie nur die elementaren Standards umsetzen, von denen in diesem Kapitel die Rede sein wird, oder ob Sie ein paar Wochen Lebenszeit investieren und sich gründlicher fortbilden - das Ergebnis ist in keinem Fall Sicherheit, sondern lediglich eine Verminderung der Risiken. Aus den zahlreichen veröffentlichten Sicherheitslücken auch bei großen Unternehmen sollte man nicht schließen, dass in deren IT-Abteilungen alle doof wären, sondern dass die Lücken immer zahlreicher sein werden als die Patches.
Es geht also nicht darum, Sicherheit herzustellen, denn die ist fiktiv. Ihr Ziel sollte es sein, kein ganz so leichtes Angriffsziel zu bieten. Kein Fahrradschloss der Welt ist unknackbar, aber ein gutes Schloss kann potenzielle Fahrraddiebe dazu bewegen, statt Ihres Rades ein anderes zu stehlen.
Dass Sicherheit zu oft zu kurz kommt, hat nicht nur mit Unkenntnis und Zeitmangel zu tun. Selbst weniger erfahrene Programmierer wissen, dass es eigentlich ganz gut wäre, sich ein bisschen mehr mit Sicherheitsfragen zu befassen. Wahrscheinlich wissen sie auch, an welchen Stellen ihr Code Anlass dazu gibt, sorgenvoll die Luft durch die Zähne zu ziehen. Aber es ist allgemein unbeliebt, Vorsorge für unangenehme Ereignisse zu treffen. Die gedankliche Beschäftigung mit den möglichen Unannehmlichkeiten schmerzt zwar weniger als das tatsächliche Eintreten des Unglücksfalls, aber mehr als die naheliegende Alternative, das Nichtstun.
Das Problem zieht sich durchs ganze Leben, vom Vernachlässigen regelmäßiger Backups und Zahnarzt-Kontrollbesuchen bis hin zum Unwillen, sich mit Altersvorsorge zu befassen. In einer Studie aus dem Jahr 1974 heißt es im Zusammenhang mit der Einführung der Gurtpflicht in Deutschland, »dass der Sicherheitsgurt primär mit den Gefahren eines Unfalls und seinen Folgen assoziiert wird und erst sekundär mit seiner eigentlichen technischen Funktion, nämlich vor diesen Gefahren zu schützen«. Die Betroffenen gerieten daher beim Stichwort Anschnallen »psychologisch in die Klemme. Einerseits sehen sie ein, dass sie mit Gurten sicherer fahren, andererseits aktualisiert der Sicherheitsgurt bei ihnen Angst, die sie vermeiden wollen. Sie kommen aus einer Angstvermeidung nicht zu einer effektiven Gefahrenvermeidung.« 1
Für den Fall, dass Sie dieses Kapitel nicht zu Ende lesen, weil Sie gern so wenig wie möglich über die fußballtorgroßen Sicherheitslücken Ihres Codes nachdenken möchten, kommen wir deshalb gleich zum zentralen Punkt dieses Kapitels: Sicherheit ist ein notorisch komplexes Thema, bei dem selbst die Besten immer wieder fatale Fehler machen. Als Neuling und selbst als fortgeschrittener Programmierer werden Sie ganz sicher ständig welche machen. Für alle sicherheitsrelevanten Aspekte der Softwareentwicklung existieren vorgefertigte Lösungen. Verwenden Sie sie (siehe auch Kapitel 19).
Wenn Sie sich entschließen, sicherheitsrelevanten Code zu schreiben, folgen daraus Verpflichtungen: Sie müssen nicht nur ein Mal, sondern immer wieder Zeit investieren, um auf dem aktuellen Stand zu bleiben. Was heute eine durchschnittlich sichere Lösung war, kann es schon morgen nicht mehr sein. Machen Sie sich mit dem Material vertraut, das das »Open Web Application Security Project« unter www.owasp.org für Sie zusammengetragen hat. Werfen Sie insbesondere einen Blick auf die Cheat Sheets (www.owasp.org/ index.php/Cheat_Sheets), um eine Vorstellung vom Umfang der Aufgabe zu bekommen, auf die Sie sich einlassen.
Aber auch, wenn Sie sich gegen das Selberschreiben entscheiden, heißt das nicht, dass Sie gar nichts über Sicherheit zu wissen bräuchten. Sie sollten wissen, wo sich häufige Sicherheitsprobleme verbergen - an welche Stellen die »Mach es nicht selbst<<-Regel also überhaupt zum Einsatz kommen soll. Es ist sinnvoll, dass Sie den eingesetzten Fremdcode und seine »Readme<<-Dateien wenigstens in den Grundzügen verstehen. Und wenn der Einsatz fertiger Lösungen einmal wirklich nicht infrage kommt, dann sollte dieses Kapitel Sie in die Lage dazu versetzen, wenigstens die allerhäufigsten Fehler zu vermeiden. Aber auch nach der Lektüre gilt: Sobald Sie die Versuchung verspüren, ein eigenes Verschlüsselungsverfahren zu schreiben, ein Loginsystem, einen eigenen Webshop oder ein Kredit-karten-Bezahlungssystem, halten Sie bitte inne, nehmen Sie die Hände von der Tastatur
! Zitiert nach »Sicherheitsgurte: Furchtvorder Fessel«, Spiegel 50/1975, www.spiegel.de/spiegel/print/d-41389557. html.
und sticken Sie sich einen Kreuzstich-Wandbehang mit der Aufschrift »Mach es nicht selbst!«.
Es kann sinnvoll sein, schon die Wahl der Programmiersprache von Sicherheitsaspekten abhängig zu machen. Wenn Sie Software schreiben, die von anderen genutzt werden wird und potenziell eine größere Verbreitung erfahren kann und/oder direkt per Webinterface zu erreichen sein wird, dann müssen Sie über böswillige und fahrlässige User nachdenken. Für solche Projekte verbietet sich C als Sprache gleich von Anfang an, C++ für schlechte Programmierer eigentlich auch. Das Problem ist die Nähe zum Metall - in diesen Sprachen gibt es keinen doppelten Boden. Es ist sehr einfach, mit simplen Logikfehlern in der Verwaltung von Speicher komplett unvorhersehbare Effekte zu erzielen: Man greift auf ein Array-Element zu, das es nicht gibt, schreibt etwas hinein, und hat in dem Moment die Rücksprungadresse der Funktion verändert, in der man sich gerade befindet. Beim Herausspringen gerät man an eine unvorhersehbare Stelle im Code - und wenn man Pech hat, hat ein böswilliger Nutzer dort Code deponiert, der Ihren Server zur Spamschleuder macht. Das kann bei Sprachen mit automatischer Speicherverwaltung nicht passieren.
Wenn Sie SQL verwenden, bleibt es Ihnen nicht erspart, sich über SQL-Injection (siehe unten) schlau zu machen, bei JavaScript über XSS-Probleme (siehe unten).
Shared Hosting ist für Sie als Zielgruppe dieses Buchs sicherer als ein selbst administrierter Server. Bei Profis ist es umgekehrt, sie werden auf dem eigenen Server wahrscheinlich restriktivere Sicherheitsstandards einsetzen, als das ein Hoster tun würde. Aber wenn Sie nicht einen wesentlichen Teil Ihrer Arbeits- oder Freizeit mit Fragen der Serveradministration zubringen, werden die Default-Einstellungen und die Häufigkeit, mit der Sicherheitsupdates eingespielt werden, selbst bei einem mittelmäßigen Webhoster über dem liegen, was Sie selbst zustande bringen würden.
Der erste Reflex zum Thema Sicherheit ist bei vielen Anfängern die Aussage »Ich halte meinen Quellcode geheim, dann kann niemand Sicherheitslücken finden.« Dieser Ansatz, im Englischen auch als »Security by Obscurity<< (Sicherheit durch Undurchsichtigkeit) bekannt, ist unter professionellen Softwareentwicklern verpönt, weil sich gezeigt hat, dass sich viele Sicherheitslücken auch ausnutzen lassen, wenn man den Programmcode nicht kennt. Gleichzeitig verführt die Vorstellung, dass niemand die Schwächen des eigenen Codes kennt, einen dazu, sich um Sicherheitsbelange nicht zu kümmern. Legt man seinen Code offen, dann zwingt man sich dazu, Sicherheitslücken auszuräumen, und kann durch Codekritik von besseren Programmierern dazulernen.
Betrachten Sie daher Ihren Code von Anfang an so, als wäre er Gott und der Welt (dem Teufel sowieso) bekannt. Das hat nebenbei den Vorteil, dass Sie später noch jemanden in das Projekt aufnehmen können, ohne sich allzu sehr für die Codequalität zu schämen.
Nicht verlinkte URLs sind keine Gewähr dafür, dass niemand auf die Seite findet. Um nur eine Möglichkeit von vielen zu nennen: Auf der geheimen Seite gibt es Links zu anderen Seiten. Klicken Sie eine dieser URLs, dann taucht die Adresse Ihrer unverlinkten Seite in den fremden Serverlogfiles auf und wird von neugierigen Menschen inspiziert. Weil auch die Betreiber dieser anderen Seiten nicht so genau wissen, was sie tun, stehen diese Logfiles womöglich außerdem offen im Netz und sind von Google durchsuchbar. Und wenn auch nur einer Ihrer Nutzer eine Google- oder Alexa-Toolbar im Browser installiert hat und auf die geheime URL zugreift, dann kommt recht bald der freundliche Googlebot vorbei und fügt Ihre vermeintlich unbekannte Seite dem Suchindex hinzu. Davor können Sie sich mit einem Eintrag in einer besonderen Datei auf Ihrem Server noch schützen, der robots.txt, aber nicht davor, dass jede URL, die von fremden Personen aufgerufen wird, nicht mehr geheim ist.
Viele erfolgreiche Angriffe basieren darauf, dass der Angreifer gezielt Fehler provoziert und aus den Antworten des Systems Informationen für einen erfolgreichen Angriff entnehmen kann. Fehlerseiten, die die Betriebssystemversion, die PHP-Revision oder den Pfad der Codedatei auf dem Server enthalten, sind schädlich. Für den legitimen Benutzer ärgerlich, aber unter dem Sicherheitsaspekt sinnvoll sind Fehlerseiten, die nur >>Error 500« zurückgeben und sonst nichts. Wenn Sie während der Codeentwicklung zu Debuggingzwecken eine Option aktiviert haben, die Ihnen alle Fehlermeldungen detailliert anzeigt, sollten Sie sie vor dem produktiven Einsatz deaktivieren. Ersetzen Sie eine der Datenbank-Queries in Ihrer Applikation durch eine fehlerhafte und rufen Sie die entsprechende Seite auf, um zu prüfen, ob Ihr Skript die Fehlermeldung der Datenbank einfach an den Browser durchreicht. Werden Username oder Passwort falsch eingegeben, dann sollte die Applikation nicht verraten, welcher Teil falsch war. Auf diese Weise ist es nicht möglich, mit einem Skript die vergebenen Benutzernamen zu erraten.
Beim Verlinken von URLs ist es üblich, statt example.com/some_directory/index.html einfach nur example.com/some_directory/ zu schreiben. Das funktioniert, weil der Browser selbstständig in diesem Verzeichnis nach einer Datei namens index.html sucht. Gibt es in dem Verzeichnis aber keine index.html, dann kann es sein, dass dem neugierigen Besucher stattdessen alle Dateien in diesem Verzeichnis aufgelistet werden. Das geschieht, wenn auf einem Apache-Server in der httpd. conf- oder der für dieses Verzeichnis zuständigen . htaccess-Datei die Zeile Options +Indexes eingetragen ist. Options -Indexes verhindert das Anzeigen des Verzeichnisinhalts. Verlassen Sie sich nicht darauf, dass die sicherere Option schon fürsorglich voreingestellt sein wird. Sie ist es oft, aber nicht immer, wie das folgende Beispiel zeigt.
Heise Security berichtete 2008 über eine Sicherheitslücke beim Erotikkonzern Beate Uhse (heise.de/-202226), durch die 20 Dateien, die E-Mail-Adressen von Kunden enthielten, für alle sichtbar wurden: »Die Datenpanne ist Folge einer ganzen Reihe von Fehlern. Offenbar hatten die Administratoren die Daten zum Zugriff auf einen Online-Adventska-lender mit Videoclips direkt auf dem Webserver gespeichert und zudem das Directory-Listing auf dem Server aktiviert. So konnte sich jeder Internetnutzer einfach per Webbrowser auf dem Ser'er umsehen. Doch damit nicht genug: Der Google-Bot fand wahrscheinlich einen Link auf die nichtöffentlichen Verzeichnisse und indizierte kurzerhand alle Daten, die er dort vorfand. So waren die Daten nicht nur für Zufallsfunde offen, sondern auch ein einfaches und lukratives Ziel für Google-Hacks.«2
Wenn diese Option in der httpd.conf- oder einer .htaccess-Datei aktiviert ist, bekommen Besucher einer nicht existierenden Seite oder eines nicht existierenden Verzeichnisses Alternativen mit ähnlichen Namen angezeigt. Darunter sind womöglich Dateien, die Sie geheim halten wollten.
Während Benutzernamen üblicherweise kein Geheimnis darstellen, müssen Passwörter nicht nur vom Inhaber, sondern auch von Ihnen als Betreiber geheim gehalten werden. Gelangt ein Unbefugter an Ihre Passwortdatei, kann das das Ende Ihres Webprojekts bedeuten, weil der Täter viel Unfug anstellen kann, alle legitimen Nutzer neue Passwörter brauchen und generell das Vertrauen der Nutzer in Sie zerstört ist.
Passwörter dürfen daher auf keinen Fall im Klartext auf dem Server gespeichert werden. Nicht in einer Datenbank, nicht als Datei. Falls irgendjemand unbefugten Zugriff auf den Speicherort der Passwörter erhält, hat er plötzlich alle Benutzerkonten auf einmal in der Hand. Schon dass Sie als Betreiber eines Angebots Zugriff auf die Passwörter im Klartext haben, ist bei den Benutzern unerwünscht.
Bilden Sie daher bei der ersten Anmeldung eines Benutzers einen Hashwert (siehe den Abschnitt >>Hashes, Digests, Fingerprints« in Kapitel 26) des Passworts und speichern Sie nur diesen. Damit kann ein Hacker schon nicht mehr an das Klartextpasswort gelangen, selbst wenn er Ihre Passwortdatenbank auslesen kann. Dieser Hashwert sollte aus dem Passwort und einem angehängten String Ihrer Wahl gebildet werden, denn es gibt inzwischen im Netz Verzeichnisse, in denen die Hashwerte für gängige Passwörter zu finden sind. Kann ein Einbrecher die Passwort-Hashes in Ihrer Datenbank einfach im Web nachschlagen, dann haben Sie durch das Hashing keine Sicherheit gewonnen. Wenn Sie hingegen das eingegebene Passwort durch einen selbst definierten String verlängern,
2
Durch Suchanweisungen wie »inurl« kann man Google dazu verwenden, nach bestimmten verwundbaren Systemen zu suchen.
dann unterlaufen Sie diese Verzeichnisse. Man nennt dieses Vorgehen Hash salting; der zusätzliche String ist das salt. Dieser Salzstring ist kein geheimer Teil eines Verschlüsselungsverfahrens, sondern dient nur dazu, die Suche in Passworthash-Verzeichnissen auszuhebeln. Sie können ihn also unbesorgt in derselben Datenbank wie die Passwörter aufbewahren oder in eine Config-Datei auslagern. Fällt er in die Hand eines Hackers, passiert nichts Schlimmes.
Jedes Mal, wenn sich der Benutzer einloggen will, bilden Sie aus dem angegebenen Passwort und dem Salzstring den Hash-Wert und vergleichen diesen mit dem gespeicherten. Sind die Hashes gleich, ist nach menschlichem Ermessen auch das richtige Passwort eingegeben worden. Als Algorithmus für das Hashing kommt derzeit nur bcrypt in Frage, alle anderen sind zu unsicher.
Selbst in großen Unternehmen, in denen erfahrene Programmierer arbeiten, kommt es immer wieder zu peinlichen Debakeln rund um die Speicherung von Passwörtern. Nicht immer ist es so schlimm wie bei reddit.com, wo im Jahr 2007 ein Backup der Datenbank abhanden kam, in dem Usernamen, Passwörter und E-Mail-Adressen aller Nutzer im Klartext gespeichert waren. Diese Geschichten sind so zahlreich, dass man ihnen eines zuverlässig entnehmen kann: Wer Passwörter speichert, macht dabei mit hoher Wahrscheinlichkeit Fehler.
Wenn Sie nicht nur eine ganz einfache statische Website mithilfe eines SFTP-Clients und eines Texteditors entwickeln, müssen Sie sich Gedanken darüber machen, wer welche Teile der Site sehen und wer die Seiten verändern darf - und das zieht gleich die Frage nach sich, mit welchem Authentifizierungsverfahren Sie den Zugriff beschränken werden. Im Wesentlichen haben Sie die Wahl zwischen folgenden Authentifizierungsstrate-gien:
Alle Nutzer dürfen dasselbe.
Vorteile:
• Die einfachste aller Lösungen.
• Kein Ärger mit vergessenen Passwörtern.
• Betreiber und Nutzer wissen genau, woran sie sind - es wird keine Sicherheit vor-gegaukelt, wo keine ist.
Nachteile:
• Sie brauchen schnelle Reaktionszeiten, um den vorherigen Zustand wiederherstellen zu können, wenn es zu Vandalismus kommt.
• Sie als Betreiber sind Ihren Nutzern gleichgestellt. Sie haben keinen privilegierten Zugriff, zum Beispiel auf die eigenen Daten, eigene Beiträge und Kommentare - oder Sie müssen eben doch eine Privatauthentifizierung für sich selbst einbauen.
° Nutzer, die bereits unangenehm aufgefallen sind, lassen sich nicht sperren. (Das ist allerdings auch mit Authentifizierung nicht zuverlässig möglich. Ein neuer Account ist schnell eingerichtet.)
• Manchmal wird die IP-Adresse erhoben, um (z.B. in der Bearbeitungsgeschichte einer Wikipedia-Seite) die anonymen und nicht eingeloggten Nutzer trotzdem irgendwie zu kennzeichnen. Diese Identifikation über die IP-Adresse funktioniert nur als ungefährer Anhaltspunkt, weil dieselbe Nutzerin mehr als eine lP-Adresse haben kann und umgekehrt mehrere Nutzerinnen vom selben Proxyserver kommen und damit eine IP-Adresse teilen können.
Sie tragen in eine . htaccess-Datei oderdie Serverkonfigurationsdatei httpd. conf ein, welche Benutzer auf eine Datei oder ein Verzeichnis zugreifen dürfen.
Vorteile:
• Basic Authentication ist die einfachste Form der Authentifizierung: keine Cookies, keine Sessions, keine eigenen Loginseiten.
• Basic Authentication ist relativ leicht zu konfigurieren. Eventuell bietet Ihr Hostinganbieter Ihnen in seinem Webinterface dafür Optionen wie »geschützte Verzeichnisse anlegen«, so dass Sie die . htaccess- oder httpd. conf-Dateien nicht einmal zu sehen bekommen.
• HTTP-Authentifizierung ist ein standardisierter Bestandteil des HTTP-Protokolls. Jeder HTTP-Client kann damit umgehen.
• HTTP-Authentifizierung ist »stateless« (siehe den Abschnitt »State und Stateless-ness« in Kapitel 26), das heißt, jede Anfrage wird unabhängig von allem behandelt, was bisher geschehen ist. Wenn der Rest der Anwendung stateless stattfindet, ist es praktisch, es bei der Authentifizierung genauso zu handhaben.
Nachteile:
• Die Eingabefelder für Benutzername und Passwort sehen in der Regel unansehnlich aus und lassen sich nicht verschönern. Es gibt keine Möglichkeit, aus der Passwortabfrage direkt zur Neuregistrierung oder zu einer »Passwort vergessen«-Hilfe zu verlinken.
• Das Einrichten zusätzlicher Nutzer ist eher kompliziert.
• Bei Basic Authentication wird das Passwort, wenn kein HTTPS zum Einsatz kommt, jedes Mal im Klartext übertragen - das ist noch schlimmer, als es im Klartext zu speichern, und ein Feature, das nie hätte erfunden werden dürfen. Digest Authentication überträgt nur eine Checksumme des Passworts.
372 | Kapitel 2S: Sicherheit
• Viele der bei Digest Authentication vorgesehenen Sicherheitsoptionen müssen explizit eingeschaltet werden
• Es gibt keine Möglichkeit, Nutzer automatisch auszuloggen. Der Browser merkt sich das Passwort mindestens, bis der Tab geschlossen wird, in vielen Fällen sogar, bis der Browser geschlossen wird oder der Nutzer von Hand die Browserhistory löscht. Wenn Ihre Nutzer auch mal an fremden Rechnern sitzen oder ihren eigenen Rechner kurz verlassen, kann das zum Problem werden: Jeder nicht autorisierte Nutzer kann dann die geschützte Seite aufrufen und zwar nicht ohne Umstände das Passwort einsehen, aber doch mit der geöffneten Seite Unfug anstellen.
Die Nutzer werden einmal auf einer handelsüblichen Loginseite nach Username und Passwort gefragt. Sind sie eingeloggt, wird dieser Status in Sessionvariablen auf dem Server abgelegt. Eine in einem Cookie untergebrachte oder in der URL durchgereichte ID sagt dem Ser'er, welcher Nutzer zu welcher Session gehört.
Vorteile:
• Die Loginseite ist frei gestaltbar.
• Nutzer können sich selbstständig neue Accounts anlegen und vergessene Passwörter wiederherstellen lassen.
• Der Server kann ein Session-Timeout machen und Nutzer damit automatisch z. B. nach Ablauf einer bestimmten Zeit ausloggen. Bekannt sind diese Timeouts vor allem aus dem Onlinebanking, wo die Dauer der Session sehr knapp bemessen ist, üblicherweise um die zehn Minuten.
Nachteile:
• Die Menge der möglichen Fehler, die man als weniger erfahrener Programmierer machen kann, ist viel größer als bei den bisher beschriebenen Methoden. Die Technik erzwingt weder Verschlüsselung noch sonstige Sicherheitsvorkehrungen. Es ist dem Entwickler freigestellt, die löchrigste Loginlösung der Welt zu basteln.
• Wenn das Cookie, das die Session-Daten enthält, nicht per HTTPS übertragen wird, kann es zum Session-Hijacking kommen, was vor allem in offenen WLANs ein Thema ist. Der Eindringling hat dann zwar nicht die Zugangsdaten, bleibt aber bis zum Ende der Session eingeloggt und kann Unheil anrichten.
Wenn Sie sich für diesen Ansatz entscheiden, setzen Sie bitte unbedingt auf schlüsselfertige Lösungen in Form separater Bibliotheken oder Tools. Das Angebot verändert sich hier im Moment so schnell, dass wir an dieser Stelle keine konkreten Tipps geben können. Eine Suche nach authentication »best solution« in Kombination mit dem Namen der jeweiligen Sprache sollte einige Ratschläge erbringen. Achten Sie auf das Datum der Antwort. Was vor einem Jahr die optimale Lösung war, kann längst schon wieder überholt sein.
Hier lagern Sie die Authentifizierung vollständig an Drittanbieter aus. Für alle, die nicht so genau wissen, was sie tun, ist das eine der sichersten Lösungen. Überall dort, wo Sie im Netz die Optionen »Login with Facebook«, »Login with Google« oder »Login with Twitter« sehen, hat der Anbieter sich für diesen Weg entschieden.
Vorteile:
• Die Nutzer haben vermutlich schon 100 über das Netz verstreute Profile und brauchen sich für Ihre Anwendung nicht noch ein weiteres anzulegen, sondern können einfach ein vorhandenes nutzen.
• Viele Ihrer Nutzer werden mehr Vertrauen in den OAuth-Anbieter haben - also etwa Twitter, Google oder Facebook - als in Ihre selbstentwickelte GetRichQuick-Anwendung.
• Den Support bei vergessenen Passwörtern muss jemand anders leisten. Sie sparen Zeit und verlieren nicht ganz so schnell den Glauben an die Zurechnungsfähigkeit Ihrer Mitmenschen.
• Sicherheitskritische Daten werden nicht auf Ihrem Server gespeichert, sondern auf dem des OAuth-Anbieters. Wenn Hacker alle Zugangsdaten stehlen, trifft Sie nur ein Teil der Schuld (nämlich, dass Sie sich für diesen unzuverlässigen Drittanbieter entschieden haben). Das ist besser, als ganz allein schuld zu sein, außerdem können Sie Ihr Leid dann mit vielen anderen teilen, die auf denselben Anbieter gesetzt haben.
Nachteile:
• Manche Nutzer werden weniger Vertrauen in Twitter, Google oder Facebook setzen als in Ihre Website, insbesondere seitdem bekannt ist, dass alle großen US-amerikanischen Internetunternehmen intensiv Daten an die NSA weitergeben. Wenn Sie diese Gruppe für relevant halten, dann könnten Sie Mozilla Persona anbieten. Auch hier wird Ihnen die meiste Arbeit abgenommen, aber Persona ist so aufgebaut, dass Mozilla keine Informationen über Ihre Nutzer erhält.
• Nutzer, die diese Bedenken zwar nicht teilen, aber einfach keinen Account bei einem OAuth-Anbieter haben, müssen Sie mühsam durch den fremden Registrierungsprozess lotsen - es sei denn, Sie sehen für solche Fälle parallel eine eigene Authentifizie-rungsoption vor. Damit sind dann aber wieder viele Vorteile der OAuth-Lösung dahin.
• Einige Ihrer Nutzer werden Ihrer GetRichQuick-Anwendung keinen verantwortungsvollen Umgang mit den doch oft sehr persönlichen Google- oder Facebook-Daten zutrauen.
• Wenn der OAuth-Anbieter gerade nicht erreichbar ist, gibt es keine Loginmöglichkeit.
• Wenn Sie sich nicht ganz so fest an einen bestimmten Anbieter binden wollen, können Sie mehrere OAuth-Loginoptionen vorsehen. Es wird dann aber auch schnell sehr viel komplizierter.
• Für Hacker ist es zwar viel schwieriger, aber auch viel attraktiver, ein großangelegtes, weltweit eingesetztes Sicherheitssystem zu knacken als Ihre kleine Privatlösung.
Eins der derzeit häufigsten Sicherheitsprobleme ist das Cross-Site-Scripting (auch als XSS abgekürzt). Dabei wird einer Website JavaScript aus nicht vertrauenswürdiger Quelle untergeschoben und auf dem Browser des Benutzers ausgeführt. Hierdurch können beliebige Inhalte nachgeladen und auf eine Weise in die Seite eingebaut werden, dass der Benutzer den Angriff nicht erkennt. Schlimmstenfalls werden so Kreditkarteninformationen gestohlen, Spamlinks auf fremden Webseiten veröffentlicht oder in sozialen Netzwerken beliebige Nachrichten unter dem Profil des Nutzers versendet.
XSS-Probleme sind allgegenwärtig - auch bei großen und professionellen Anbietern. Anfang 2013 wurde eine XSS-Sicherheitslücke bei Amazon bekannt, durch die Sitzungscookies und damit Klarnamen, Mailadressen, Einkaufskörbe und Zugangsdaten für Unbefugte zugänglich waren. »Das Ausnutzen der Lücke war trivial«, berichtete heise Security, »man musste im Kundenforum lediglich einen Beitrag mit einem speziell formatierten Titel anlegen, etwa nach dem Muster "><script>alert ( 'XSS')<script>. Da Amazon den angegebenen Beitragstitel nicht ausreichend überprüft hat, wurde der darin enthaltene javaScript-Code in bestimmte Unterseiten des Forums eingebettet und dann bei deren Aufruf vom Browser ausgeführt.« Auch der Onlinebezahldienst Paypal war wenige Monate vorher durch eine XSS-Sicherheitslücke »an sehr zentraler Stelle« aufgefallen. 2011 berichtete heise Security über XSS-Lücken auf den Websites von 17 Banken -entdeckt wurden die Probleme von Schülern.
Das zweite Hauptproblem mit Nutzereingaben sind Angriffe, die durch sogenannte SQL-Injection Ihre Datenbank lesen und verändern. Wenn Ihnen das widerfährt, haben Sie nicht nur einen roten Kopf, sondern auch das Vertrauen Ihrer Nutzer verspielt. Die Daten können Sie mit einem guten Backup wiederherstellen, das Vertrauen eher nicht. Wenn Sie keine Vorsicht in der Programmierung walten lassen, genügt es für diesen Angriff schon, dass Ihre Applikation ein Suchformular zur Verfügung stellt und die Daten in einer Datenbank verwaltet.
Beide Probleme haben eine gemeinsame Wurzel: Es ist dem Anwender möglich, aktive Inhalte durch einfache Textfelder, URL-Parameter und Cookies in Ihr System einzuschleusen, wo sie dann ausgeführt (SQL-Injektion) oder ungeprüft an den Browser herausgegeben und dort ausgeführt werden (XSS-Attacke). Um das zu unterbinden, müssen alle Daten, die von außerhalb der eigenen Anwendung kommen, als zunächst gefährlich betrachtet und von möglicherweise enthaltenen Steuerzeichen befreit werden. Das betrifft nicht nur die Daten, die Benutzer in die dafür vorgesehenen Felder eintragen können, sondern auch URL-Parameter, Cookiedaten, das User-Agent-Feld des Browsers, Daten aus Datenbanken, Konfigurationsdateien und Dateisystemen, RSS-Feeds und sogar Barcodes. Alles, was aus Software stammt, die Sie nicht selbst geschrieben haben, ist verdächtig. »Vor der Verwendung« heißt hier auch: bevor Sie die Daten in einer Datenbank oder auf der Festplatte speichern, um sie erst später zu lesen.
Zum Reinigen von Inputdaten gibt es Bibliotheksfunktionen, die je nach Sprache und verwendeter Bibliothek unterschiedlich sind. Um Text in eine Datenbank zu schreiben, muss er von möglichen SQL-lnjektionen befreit werden - dafür bieten sich parametri-sierte Queries (siehe unten) oder ein SQL-Framework an. JavaScript-Injections werden dadurch verhindert, dass vom Benutzer generierte Inhalte in dem Moment, in dem sie ins System kommen, gegen Whitelists (siehe ebenfalls unten) geprüft und gefährliche Zeichen entfernt werden.
SQL-lnjektionen kann man durch Filtern auf der Inputseite unterbinden, denn sie sollen direkt auf dem Server ausgeführt werden, aber angesichts von XSS-Angriffen sollten Sie auf jeden Fall dafür sorgen, dass auch der Output gefiltert wird, den Ihr System an den Browser zurückgibt. Hierbei liegt der Schwerpunkt darauf, per Escaping Zeichensequenzen, die HTML-Tags und JavaScript enthalten könnten, in harmlose zu verwandeln.
Wenn man Nutzereingaben nur mit JavaScript auf dem Rechner des Nutzers überprüft, kann man es auch gleich bleiben lassen. Als Programmierer hat man ausschließlich den Server und die auf ihm laufenden Programme voll unter Kontrolle. Der Browser läuft auf dem Nutzerrechner und könnte durch ein Skript ersetzt sein. Daten werden vom Benutzerrechner zum Server über viele Zwischenstationen weitergereicht. Jeder dieser Router könnte die Daten mitlesen und verändern. Jeder, der sich die Mühe macht, ein paar Minuten nach einer Anleitung zu suchen, kann die Ergebnisse einer clientseitigen Prüfung nach Belieben manipulieren. Es kann sinnvoll sein, clientseitig zu testen, um dann den Nutzern mitzuteilen, dass mit ihren eingegebenen Daten oder hochzuladenden Bildern etwas nicht stimmt. Das dient aber wirklich nur der Benutzerfreundlichkeit, sicherheitstechnisch ist es irrelevant. Sie müssen diese Tests auf jeden Fall serverseitig noch einmal durchführen.
Nutzereingaben sollten Sie nicht einfach wieder ausgeben, ohne vorher problematische Zeichen zu maskieren. Es gibt in jeder Sprache fertige Befehle zu diesem Zweck, in PHP würde man für SQL mysql_real_escape_string verwenden; für Eingaben, die auf einer Website wieder ausgegeben werden und möglicherweise JavaScript enthalten, htmlspecialchars. Für Shell-Kommandos gibt es escapeshellcmd und escapeshellarg. Alle diese Funktionen versehen bestimmte Zeichen mit einem Backslash (\) und verhindern so die Ausführung eventuell eingeschmuggelten Codes.
Schreiben Sie also nicht
echo 'Ihre Eingabe war ' . $_GET["UserInput"];
Besser ist es, die Nutzereingabe in eine Variable zu stecken, einige problematische Zeichen in HTML-Code umzuwandeln und sie erst dann wieder auszugeben. In unserem PHP-Beispiel würde das so aussehen:
$rawUserInput = $_GET["UserInput"];
$userInput = htmlspecialchars($rawUserInput); echo 'Ihre Eingabe war ’ . $userInput);
htmlspecialchars wandelt die Zeichen &, <, >, " und ' in ihre HTML-Äquivalente um. Wenn der eingegebene Text HTML-Markup enthalten hat, steht im Ergebnis danach nicht mehr <script>, sondern das harmlose <script>. Für harmlose Nutzer, die in ihrem Beitrag einfach nur ein HTML-Beispiel geben wollten, genügt das, aber potenziell schädliches JavaScript kann jetzt nicht mehr ausgeführt werden.
Allerdings schützt htmlspecialchars nicht vor allen Gefahren. Wenn Sie wegen Ihrer überdurchschnittlich hackfreudigen Benutzer oder Ihrer besonders kritischen Daten höhere Sicherheitsstandards anlegen müssen, genügt diese Strategie nicht. Wir werden im Abschnitt »Weiße Listen sind besser als schwarze« weiter unten noch auf die Details zu sprechen kommen.
Konstruieren Sie keine Queries oder Kommandos mit Nutzereingaben, ohne vorher problematische Zeichen zu maskieren.
Starten Sie also nicht mithilfe von system(), eval() oder vergleichbaren Aufrufen lokale Programme, wenn Name oder Pfad des Programms von außen beeinflusst werden können. Streichen Sie eval() am besten aus Ihrem Gedächtnis und verwenden Sie system() sehr vorsichtig.
Hochzuladende Dateien sollten das envartete Format haben und nicht irgendein beliebiges. Dasselbe gilt für Variablen, die Sie weiterverarbeiten möchten. In typsicheren Sprachen (siehe Kapitel 26) fällt von selbst auf, dass die vom Nutzer hereingereichte Variable id in Wirklichkeit keine Zahl, sondern einen halben Roman enthält. Arbeiten Sie aber mit einer schwach typisierten Sprache wie JavaScript oder PHP, dann müssen Sie selbst dafür sorgen, dass die Variable vom erwarteten Typ ist. In PHP finden Sie mithilfe von is_int(), is_numeric() etc. heraus, was Sie vor sich haben. Mit intval(), strval(), bool-val(), floatval() oder settype() können Sie den Variablentyp erzwingen. In JavaScript greift man zu parselnt(), parseFloatQ und isNaN() (>>is not a number<<). PHP bietet außerdem mit filter-var diverse Möglichkeiten, um herauszufinden, ob eine Variable in ein bestimmtes Schema passt, also beispielsweise wie eine korrekte Mailadresse oder URL geformt ist. Die zahlreichen vorgefertigten Filter decken vermutlich etwa 99% von dem ab, was Sie selbst zu schreiben versucht sein könnten. (Siehe auch Kapitel l9.)
Wenn Sie ein Content-Management-System haben, das URLs der Art /mypage?id=53 verwendet und diese lDs Primärschlüssel in der Datenbank sind, sollten Sie sich mit para-metrisierten Queries befassen.
Schreiben Sie auf keinen Fall SQL-Strings in Ihr Programm, indem Sie von außen kommende Inhalte einfach einsetzen. Also niemals so:
var query = "select * from mytable where id = "+$pageld; var result = query.execute();
Parametrisierte Queries sehen je nach Sprache unterschiedlich aus, funktionieren aber ungefähr so:
var query = "select * from mytable where id = ?"; query.setParameter(l, $pageld); var result = query.execute();
Beim Ersetzen des Fragezeichens wird der Inhalt von $pageld daraufhin geprüft, ob er eine SQL-Injection enthält, und gegebenenfalls entschärft.
Verlassen Sie sich nicht darauf, dass niemand auf seltsame Ideen kommt und der vorgesehene Platz schon reichen wird, sonst öffnen Sie Ihr System für die »file space denial of service attack«, bei der Nutzer eine boshaft konstruierte Zip-Datei hochladen und damit die Festplatte Ihres Servers überfordern. Diese sogenannten Archivbomben sind sehr kleine (ungefähr 40 KByte) große Dateien, die beim Auspacken zu tera- oder sogar petabytegro-ßen Ergebnisdateien werden. Sie nutzen aus, dass Kompressionsalgorithmen absolut gleichmäßige Dateien, die beispielsweise nur 0 enthalten, extrem hoch komprimieren können. Als Gegenmaßnahmen sollten Sie hochgeladene Archive nicht einfach blind auspacken, sondern durch Ubzip zunächst die Größe prüfen lassen
Eine Suche (hier in der Google-Version) nach inurl:& inurl:select inurl:from inurl: where fördert bedrückende Mengen an Ergebnissen ans Licht. Selbst Menschen ohne Hackerneigungen und -fähigkeiten werden hier kaum widerstehen können, durch simples Ändern der URL eine Kaffeefahrt durch die fremde Datenbank zu unternehmen.
Entwickler von Webshop-Systemen müssen das Problem lösen, dass jeder Kunde einen Warenkorb mit Waren füllen kann, dann weiter durch den Webshop navigiert und zum Schluss an die Kasse geht und die Waren in seinem Warenkorb bestellen will. Da HTTP ein stateless Protokoll ist (siehe den Abschnitt »State und Statelessness« in Kapitel 26), muss sich der Entwickler Gedanken darüber machen, wie der Webshop sich den Zustand eines Warenkorbs merken kann und wie er einem Kunden zugeordnet wird.
Die empfohlene Lösung ist, auf ein Webframework zu setzen, das mithilfe von Cookies oder Parametern in der URL eine Session für jeden Benutzer öffnet, und in dieser Session den Zustand des Warenkorbs mitzuführen. Hierfür gibt es für die meisten Sprachen kompetente Lösungen.
Einige weniger erfahrene Entwickler kamen einmal auf die Idee, die Artikelnummern der Posten des Warenkorbs zusammen mit deren Preis in der URL oder im Cookie in die Session zu codieren. Wechselt der Benutzer auf eine andere Seite, werden die Informationen zum Warenkorb vom Browser des Kunden an den Server übergeben und von diesem mit der neuen Page wieder an den Browser. Gewitzte Kunden manipulierten dann die Anzahl der Artikel und den Preis und konnten an der Kasse statt drei Bügeleisen zu je 29,90 Euro fünf Bügeleisen zu je 2,99 Euro kaufen.
Der Fehler war hier, den Informationen, die vom Browser des Kunden zurückgeliefert wurden, zu vertrauen. Der Server hätte den Preis nur herausgeben, niemals aber wieder akzeptieren dürfen. Die Endabrechnung hätte komplett auf dem Server passieren müssen; die einzigen Angaben, die vom Browser hätten übernommen werden dürfen, sind die Zahl und Artikelnummern.
Sie dürfen daher niemals Passwörter, Kreditkartennummern, Preise und dergleichen an den Client übertragen und dann auf der Serverseite wieder vom Client übernehmen. Und bitte: Wenn Sie darüber nachdenken, mit Kreditkartendaten Ihrer Kunden zu hantieren, dann treiben Sie genug Geld auf, um einen Sicherheitsexperten zu bezahlen.
Wie Sie Ihre Daten aufbewahren, sollte nach außen möglichst nicht sichtbar sein, damit Sie keine unnötige Angriffsfläche bieten.
2009 berichtete der Spiegel. »Beim Online-Buchhändler libri.de hat es eine schwere Datenpanne gegeben. 563.640 Rechnungen Hunderttausender Kunden waren über einen Umweg für jeden Internetnutzer einsehbar.« Wer eine Rechnung als PDF-Dokument herunterlud, bekam dafür eine fortlaufende Nummer. Durch Eingabe zufällig gewählter anderer Nummern ließen sich sämtliche Rechnungen anderer Kunden aus den vergangenen 16 Monaten abrufen. Der Spiegel spottete: »Besonders peinlich: Auf Libri prangt ein Gütesiegel des TÜV Süd für sicheres Einkaufen im Internet.«
Hier kamen zwei Sicherheitsprobleme zusammen: Die gravierendere Sicherheitslücke war, dass nicht geprüft wurde, ob ein Benutzer auch Zugriff auf eine bestimmte Rechnung haben durfte. Bei jedem Zugriff auf eine Rechnung muss geprüft werden, ob der Benutzer eingeloggt ist und für diese Rechnung Zugriffsrechte besitzt. Auch wenn das schon ein grober Fehler war, wäre ohne die zweite Lücke wahrscheinlich nichts Schlimmes passiert: In der Webapplikation wurden interne IDs verwendet, wahrscheinlich aus einer Datenbanktabelle. Es war Angreifern ein leichtes, fortlaufende Nummern zu erraten. Ein kleiner zusätzlicher Schritt hätte diesen Angriff unmöglich gemacht, etwa indem die fortlaufende ID mit einem salt (siehe oben) verkettet und dann durch einen Hash-Algorithmus in einen Hashwert verwandelt worden wäre. Auf Serverseite wäre es durch einen Eintrag in der Datenbank ganz leicht gewesen, Usernamen, Rechnungsnummer und Hashwert zu verknüpfen, und auf Clientseite wäre es unmöglich, die URL für eine fremde Rechnungsnummer zu erraten.
ln Ruby und Perl gehört Taint zur Standardausstattung, in vielen anderen Sprachen lässt es sich über Bibliotheken oder Erweiterungen nachrüsten (eine Suche z. B. nach taint php oder taint python führt Sie hin). Im Taint-Modus legt das Programm eine Liste darüber an, welche Variablen von außen beeinflusst werden können und welche Variablen sich wiederum davon ableiten. Alle diese Variablen gelten dann als zweifelhaft. Wenn sie an gefährlichen Stellen zum Einsatz kommen sollen, zum Beispiel in SQL-Befehlen oder Kommandos auf Betriebssystemebene wie eval(), wird die Ausführung abgebrochen.