Die Spitze des Eisbergs

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

Im dritten und letzten Artikel der Reihe widmen wir uns dem sicheren Hashing von Passwörtern und der Frage, wieso das neue Passwort-API von PHP 5.5 solch ein großer Schritt nach vorne ist. Zudem geht es um das Access-Level-Control, dem großen Thema der Cross-Site Request Forgeries und dem Einsatz von Drittanbietersoftware.

Passwort-API

Glücklicherweise haben wir seit PHP 5.5 ein neues Passwort-API, das „from scratch“ sehr sichere Hashes zu unseren Passwörtern generiert, aber was war das Problem mit unseren alten Funktionen?

Zunächst einmal muss ein sicherer Algorithmus gefunden werden, und md5 zählt sicherlich nicht dazu, denn jeder mögliche zu generierende md5-Hash wurde inzwischen gebrute-forced und die Hash-Tabellen sind so bekannt, dass diese sogar mit Google gefunden werden. Ähnliche Hash-Tabellen gibt es auch für etwas komplexere Algorithmen und wird es auch immer mehr geben, da aufgrund der ganzen Cloud-Farmen oder auch immer größer werdenden Botnetze für relativ wenig Geld sehr große Rechenleistungen zur Verfügung stehen.

Auch entsprechende wilde Konstrukte von md5, die dann nochmal mit base64 enkodiert werden (das übrigens kein Hash- oder Verschlüsselungsalgorithmus ist und deswegen hier gar nichts zu suchen hat) helfen nicht und machen das Knacken der Passwörter ggf. sogar einfacher, da die Anzahl der Hash-Konflikte erhöht wird, wenn z. B. md5 mehrfach oder sogar auf einen verkürzten String angewendet wird.

Aber die Nutzung eines guten Algorithmus allein ist nicht ausreichend, denn die zu generierenden Hashes müssen einen zufälligen Salt erhalten, der eben genau die Benutzung von Standard-Hash-Tabellen unmöglich macht. So wird zu jedem zu hashenden String ein neuer Salt generiert, der an den String angehängt wird und somit bei jeder Validierung dort vorkommen muss. Um nun also mit solch einer Hash-Tabelle zu arbeiten, muss diese schon speziell mit diesem Salt erstellt worden sein. Da sie für jedes Passwort einen eigenen Salt generieren, müsste man also schon Unmengen solcher Tabellen erzeugen, was den Aufwand enorm erhöht, je nachdem wie lang der Algorithmus benötigt mit der aktuellen zur Verfügung stehenden CPU-Leistung sogar unmöglich macht.

Klingt eigentlich gar nicht so schwer, doch dieses Wissen muss man erst mal haben und es dann auch entsprechend anwenden. Somit ist das neue Passwort-API zwar nicht unbedingt technisch, aber von der Usability für Entwickler, ein Meilenstein und wird hoffentlich die Menge an unsicher gehashten Passwörtern sehr schnell verringern. Das Generieren und Verifizieren solch eines Passworts funktioniert nun also mit folgenden Funktionen:

$hash = password_hash($password); 

if (password_verify($password, $hash)) { 
  // Success! 
} else { 
  // Failed 🙁 
}

Mehr ist nicht notwendig. Der erste Aufruf password_hash würde einen entsprechenden Hash generieren und der zweite Aufruf password_verify überprüft diesen gegen ein gegebenes Passwort.

Sie fragen sich nun evtl., wieso der generierte Hash von password_hash nicht einfach mit einem Equal-Operator mit dem gespeicherten Wert in der Datenbank verglichen wird? Wie erwähnt fügt das Passwort-API zu jedem String automatisch einen neuen Salt hinzu, somit wird jedes Mal ein neuer Hash erstellt und ein entsprechender Vergleich würde fehlschlagen. password_verify geht dagegen her und liest den Salt entsprechend aus, um dann den ursprünglich gehashten Wert vergleichen zu können.

Aktuell wird zur Verschlüsselung automatisch BCRYPT eingesetzt, was aktuell als sicher angesehen wird, aber auch hier wurde direkt für die Zukunft mitgedacht. Mit neuen Releases von PHP wird auch der Algorithmus bei Bedarf aktualisiert, dennoch werden alte Passwörter verifiziert werden können, da der genutzte Algorithmus im Hash erkennbar ist und somit nicht mit der Abwärtskompatibilität gebrochen wird, neue Passwörter aber dennoch von einem stärkeren Algorithmus profitieren.

Sofern Sie noch kein PHP 5.5 einsetzen, gibt es auch einen Backport, der bis auf PHP 5.3.7 zurückgeht und von Anthony Ferrara bereitgestellt wird.

Rainbow Tables

Neben inzwischen öffentlichen Hash-Tabellen, die jeden möglichen Hash enthalten, gibt es eine weitere Gefahr in Tabellenform, die als Rainbow Tables (Abb. 1) bezeichnet werden. Um eine Rainbow Table zu erstellen, benötigt man zwei Funktionen:

Eine Hash-Funktion; diese muss dieselbe sein, wie sie auch verwendet wurde, als der zu knackende Hash generiert wurde. Sie sehen schon, wenn Sie entsprechende Salts einsetzen, ist es ungleich schwerer, Ihre Hashes mit Rainbow Tables zu knacken, da für jeden Datensatz somit quasi auch eine eigene Rainbow Table erzeugt werden müsste.

Eine Reduktionsfunktion, die aus einem generierten Hash wieder ein mögliches Passwort generiert. Gern genutzt ist hier z. B. ein base64_encode, das auf z. B. acht Zeichen gekürzt wird.

Nun wählt man einen zufälligen String aus, der einem möglichen Passwort entsprechen könnte, ruft die Hash-Funktion und die Reduktionsfunktion auf. Dies wiederholt man 100 000x und speichert am Ende nur den ursprünglichen Anfangs- und den Endwert ab, dies ist dann eine so genannte Kette. Man speichert nur den Anfangs- und Endwert, um Speicherplatz zu sparen, da sich die 100 000 Operationen dazwischen relativ einfach rekonstruieren lassen.

Abb. 1: Zur Erstellung einer Rainbow Table werden Hash- und Reduktionsfunktion mehrfach hintereinander ausgeführt und nur die Start- und Endwerte gespeichert, um Speicherplatz zu sparen

Abb. 1: Zur Erstellung einer Rainbow Table werden Hash- und Reduktionsfunktion mehrfach hintereinander ausgeführt und nur die Start- und Endwerte gespeichert, um Speicherplatz zu sparen

Hat man ausreichend viele Ketten erstellt, kann man nun diese 100 000 Operationen auf den ursprünglichen Hash-Wert, den man knacken möchte, anwenden. Trifft man dabei während einem der Durchläufe auf einen der generierten Endwerte, weiß man, in welcher Kette das Passwort vorkam. Man nimmt nun also den Anfangswert dieser Kette und lässt die Hash-Funktion und Endfunktion erneut durchführen, bis man dabei wieder auf den zu knackenden Hash trifft und hat somit Zugriff auf das Passwort im Klartext.

Der Vorteil dabei liegt in dem pragmatischen Ansatz, was Speicherplatz und benötigte Rechenpower angeht, da beides nicht unbegrenzt zur Verfügung steht. Durch die schlaue Kombination und dem Wissen der Reduktionsfunktion kommt man aber doch relativ schnell zu einem Ergebnis.

Im Idealfall nutzen Sie für jeden Schritt eine eigene Reduktionsfunktion, sodass dieselben Hashes nicht immer auch zu demselben neuen Passwort generiert werden, ansonsten haben Sie große Dopplungen in Ihren Ketten und verschwenden unnötig Rechenleistung und Speicherplatz.

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

Access Level Control

Selbstverständlich sollte auch jeweils im ACL der Webanwendung selbst geprüft werden, ob der aktuelle Client überhaupt den entsprechenden Datensatz lesen oder vielleicht sogar manipulieren darf. Hier gibt es in den diversen Frameworks schon sehr gute Implementierungen, die einem helfen, sehr schnell, einfach und auch sicher ein entsprechendes Rechte-/Rollensystem zu etablieren. Leider liefert gerade PHP keine ACL-Implementierung nativ mit, greifen Sie also auf gut funktionierende Lösungen vom Zend Framework oder Symfony zurück und versuchen Sie nicht selbst, das Rad neu zu erfinden und dabei ggf. kritische Stellen zu übersehen.

Implementieren Sie Controller, die nur nach einer Authentifizierung zugänglich sind, so sollte ein Zugriff auf diese standardmäßig deaktiviert sein, implementieren Sie also nur die Allow-Regeln, anstatt erst mal jedem Zugriff darauf zu geben und dann nur einzelne User auszuschließen. Sie könnten nicht nur jemanden vergessen, sondern ihre Nutzerbasis ändert sich auch dauernd, so kommen neue Nutzer, Gruppen oder sogar Rollen hinzu.

Womit wir auch schon beim Stichwort wären: Benutzen Sie Rollen zur Validierung der User, schreiben Sie auf keinen Fall eine Validierung anhand von User-IDs oder sonstigen dynamischen Kriterien. Ihr Code wird mit einer Rolle, z. B. Editor oder Moderator nicht nur lesbarer, als eine kryptische User-ID, sondern Sie müssen auch nicht ständig Ihren Quellcode ändern, wenn sich die Berechtigungen ändern sollen oder ein neuer User hinzukommt.

Cross-Site Request Forgery

Neben den schon besprochenen Problemen des Cross-Site Scripting und der SQL Injections dürfte wohl Cross-Site Request Forgery (CSRF) eines der bekanntesten, aber auch weiterhin weit verbreitetsten Probleme sein (Abb. 2).

Um uns mögliche Lösungen anzuschauen, sollten wir zunächst klären, wo denn genau das Problem liegt. Gehen wir davon aus, dass ein User sich auf einer Website einloggt. Normalerweise ist der Zugang für Dritte entsprechend geschützt, da Sie alle bereits genannten Sicherheitsempfehlungen angewandt haben. Gleichzeitig besucht der User aber dennoch in einem weiteren Tab noch eine andere Website, die zumindest teilweise unter der Kontrolle des Angreifers steht.

Abb. 2: Bei einer CSRF-Attacke führt eine Drittseite über den authentifizierten Browser Anfragen auf eine eigentlich vertrauenswürdige Webseite aus

Abb. 2: Bei einer CSRF-Attacke führt eine Drittseite über den authentifizierten Browser Anfragen auf eine eigentlich vertrauenswürdige Webseite aus

Was nun passieren kann, ist, dass die präparierte Website nun durch den Browser des Clients einen Request auf die eigentlich geschützte Seite absetzt. Dies kann im einfachsten Fall durch einen Redirect geschehen, aber auch durch versteckte iFrames oder sogar über JavaScript entsprechende POST-Requests absetzen. Das Problem ist dabei, dass der Clientbrowser auf der eigentlich sicheren Website eingeloggt ist, also ein entsprechendes Cookie und Sessiondaten existieren, sodass Request-Anfragen auch ausgeführt werden. Hier können also mit etwas Kenntnis des entsprechenden Systems Daten manipuliert werden, z. B. das Ändern von Content-Blöcken oder evtl. sogar das Anlegen eines entsprechenden neuen Useraccounts mit vordefinierten Logindaten, die dem Angreifer dann bekannt sind, sodass die CSRF-Lücke zukünftig gar nicht mehr genutzt werden muss. Einfachere typische Attacken sind auch z. B. das automatische Ausloggen oder Löschen des Users, was nicht nur sehr nervig ist, sondern auch zu einer geringeren Conversionrate führt.

One-Time Token

Ein häufig anzutreffender Schutz sind so genannte One-Time Tokens. Dabei wird bei jedem Request ein neues Token generiert und an jeden Link, der bei der Response mitgeliefert wird, angehängt. Dieses Token wird auf dem Server entsprechend zwischengespeichert und durch das Anhängen der Informationen an die Links beim nächsten Request wiederum an den Server geliefert. Jedes Token ist dabei zufällig gewählt und wird nur einmalig vergeben, somit ist es keiner weiteren Person bekannt.

Bei dem direkt darauffolgenden nächsten Request wird validiert, ob das übermittelte Token dem zwischengespeicherten Token entspricht. Wird also ein Request von einer Drittseite ausgelöst, die das Token nicht kennen kann, wird der Request auch entsprechend geblockt. Dies funktioniert technisch sehr gut, ein großer Usability-Nachteil ist aber z. B., dass das Surfen in mehreren Tabs auf derselben Website nicht möglich ist, da die zuvor generierten Token im einen Tab durch das Laden der Seite und in einem weiteren Tab ungültig gemacht werden und der User sogar in entsprechende Fehlermeldungen aufgrund von ungültigen Token läuft.

Zudem muss wirklich sichergestellt werden, dass das Token keinem Dritten bekannt wird, die Tokens müssen also über eine verschlüsselte Leitung via HTTPS versendet werden, da diese sonst gesnifft werden könnten.

XSS-/CSRF-Kombinationen

Ganz gemein wird es zudem, wenn Sie eine XSS-Lücke auf Ihrer Website haben, denn dann sind die Token quasi nutzlos, da diese entsprechend ausgelesen werden können. Genau dies ist zuletzt Twitter passiert, die zwar auf entsprechende One-Time Token setzen, aber deren Kombination aus eigentlich kleineren Lücken zu einem sehr großen und erfolgreichen Angriff auf das System führte.

Und zwar fanden einige sehr pfiffige Leute heraus, dass es möglich war, in seinem eigenen Profil JavaScript-Code einzuschleusen, wenn dieser zuvor urlencoded wurde (Listing 1). Dieser JavaScript-Code wurde also entsprechend ausgeführt, wann immer jemand das Profil des Users ansah. Durch diesen Code wurde dann das zuletzt gesetzte Token aus einem Link im DOM ausgelesen. Mithilfe des Tokens war es dann möglich, einen Tweet abzusetzen und zudem wiederum das Profil des aktuell eingeloggten Users automatisiert und unbemerkt zu editieren und zwar mit genau demselben JavaScript-Code, der soeben ausgeführt wurde. Nach kurzer Zeit war also nicht nur das eine Profil, das bewusst manipuliert wurde, ein entsprechende Infektionsquelle, sondern auch ein Dutzend weiterer Profile, die eigentlich nur das ursprüngliche Profil angesehen haben, ohne bewusst eine Aktion auszuführen. Da diese Profile auch infiziert waren, wurde der JavaScript-Code nun auch verbreitet, wann immer eines dieser Profile angesehen wurde, und die Sicherheitslücke verbreitete sich entsprechend in weiten Teilen des Twitter-Universums.

var update = urlencode("Visit www. evil. com");
var xss = urlencode('http:// www. evil. com">' +
  '' +
  '<a ');
var ajaxConn = new XHConn();
ajaxConn.connect(
  "/status/update", 
  "POST", 
  "authenticity_token=" + authtoken + 
    "&status=" + update + 
    "&tab=home&update=update"
);
ajaxConn.connect(
  "/account/settings", 
  "POST", 
  "authenticity_token=" + authtoken + 
    "&user[url]=" + xss + 
    "&tab=home&update=update"
);

Authentication

Das Problem wurde natürlich zunächst dadurch behoben, dass die XSS-Lücke geschlossen wurde. Zudem wurde aber eine weitere Authentifizierungsschicht eingeführt, so muss nun jeweils erneut das Passwort eingegeben werden, wenn der User sein Profil ändern möchte. Dies ist die einzige Möglichkeit, um wirklich zu validieren, dass der User selbst auch tatsächlich den Aufruf getätigt hat und interessiert daran ist, sein Profil entsprechend anzupassen. Ähnliche Authentifizierungen findet man bei vielen großen Shopsystemen, wie z. B. Amazon, wenn man entweder eine Bestellung absetzt oder eben seine Kontodaten ändern möchte, unabhängig davon, ob man eigentlich schon eingeloggt ist, um Missbrauch zu vermeiden. Neben dem Passwort könnte auch nach irgendeiner anderen Information gefragt werden, die nur dem eigentlichen User bekannt ist.

Eine häufig genutzte Alternative ist z. B. das Einblenden von Captchas, sofern man darauf vertraut, dass diese nur durch eine menschliche Person gelöst werden kann. Problem ist aber, dass dieses Captcha prinzipiell von jeder menschlichen Person, also auch z. B. dem Angreifer, gelöst werden kann, und somit keine zuverlässige Authentifizierung bietet, es verhindert aber zumindest, dass ein Robot den Request entsprechend durchführt.

Komponenten mit bekannten Schwachstellen

Third-Party-Software Wenn Sie eine Library einsetzen, sollten Sie unabhängig von der Popularität des Projekts einen Blick in den Quellcode werfen, welche Qualität die Software hat, wie auch der „Pro-Tipp“ in Abbildung 3 warnt. Denn selbst auf GitHub finden Sie viele Projekte mit offensichtlichen SQL Injections, die dort nicht unbedingt absichtlich aber aus Unwissenheit hinterlassen wurden. Sicherlich ist es nicht immer möglich, ein umfassendes Projekt mit mehreren tausend Zeilen Code zu reviewen, aber ein erster Blick liefert schon viele Informationen über die allgemeine Codequalität und die Sicherheit der Module.

Abb. 3: Tweet von @PHPeeHaa: Die Popularität sagt nichts über die Qualität aus

Abb. 3: Tweet von @PHPeeHaa: Die Popularität sagt nichts über die Qualität aus

Haben Sie sich für eine Library entschieden, sollten Sie unbedingt darauf achten, diese aktuell zu halten. Denn auch in einer noch so sorgsam entwickelten Applikation schleichen sich Fehler ein, die dann gerade bei beliebteren Repositories sehr schnell bekannt werden und mit der Sie sich direkt in das Schussfeld katapultieren.

Prüfen Sie also regelmäßig die News-Seiten, Foren und Mailinglisten Ihrer eingesetzten Module. VersionEye nimmt Ihnen viel Arbeit ab. Dort können Sie sich automatisiert über Updates Ihrer Library informieren lassen und z. B. für die Symfony-Toolbar existiert sogar ein Plug-in, das Ihnen diese Information direkt über alle installierten Module ausgibt.

Metasploit Metasploit ist ein Tool zum Penetration-Testing Ihrer Website, das nach bekannten Sicherheitslücken in eingesetzten Libraries und Software sucht. Nach der Installation wird automatisch der Service gestartet und über ein Webinterface kann man entsprechende Tests konfigurieren und starten. Bitte nutzen Sie das Tool nur für Ihre eigenen Systeme, da Scans auf Server von Mitbewerbern oder anderen Personen strafbar sind!

Wir werden hier kurz am Beispiel des Web-App-Tests zeigen, wie die Software genutzt werden kann. Nach dem Start des Webinterface muss man einen Account einrichten. Sobald das geschehen ist, landet man auf dem Overview-Dashboard. Dort wählt man dann die Option „Web App Test“.

Auf der darauffolgenden Seite können Einstellungen vorgenommen werden, wie z. B. in welchem Intervall gescannt werden soll, oder wie viele gleichzeitige Anfragen ausgeführt werden sollen. Für unsere Zwecke reicht es, die Standardeinstellungen zunächst zu belassen und nur einen Projektnamen plus Einstiegs-URL anzugeben.

Nach dem Klick auf „Start Scan“ wird der Scan automatisch im Hintergrund ausgeführt. Den aktuellen Status können Sie im Tab „Tasks“ einsehen. Metasploit crawlt nun zunächst die Website und sucht nach Hinweisen, welche Software z. B. für die Web-App eingesetzt wurde und scannt dabei auch nach üblichen Verzeichnissen wie awstats oder phpinfo.php, die üblicherweise nicht verlinkt sind. Wird eine Software entsprechend entdeckt, wird nach Anzeichen auf eine Versionsnummer gescannt und im Anschluss gegen die Datenbank mit bekannten Sicherheitslücken geprüft. Entsprechende Schwachstellen werden danach in Listenform ausgegeben und können von Ihnen behoben werden.

Neben dem Webinterface, welches sehr einfach zu bedienen ist, gibt es auch eine Metasploit-Konsole, welche Ihnen noch mehr Funktionalitäten bietet, und zudem auch z. B. in ihr Continuous-Integration-System wie Jenkins integriert werden kann, um regelmäßig nach bekannten Schwachstellen zu suchen.

Redirects und Forwards Durch Redirects und Forwards geht wiederum eine Gefahr aus, wenn diese manipuliert werden können. Die OWASP rät dazu, diese komplett zu vermeiden, was allerdings in der Praxis nicht immer umsetzbar ist. Man kann sich aber durch eine Whitelist oder indirekte Objektreferenzen (siehe PHP Magazin 3.14) sehr gut dagegen schützen und die URLs vor Manipulationen schützen.

Last but not least bleibt zu sagen, dass die vorgestellten OWASP-Top-10-Sicherheitslücken hier aber nur die Spitze des Eisbergs sind und sich die Angriffe und Techniken kontinuierlich weiterentwickeln, sodass auch wir Entwickler ständig auf der Hut sein müssen und unser Wissen regelmäßig auffrischen sollten, um möglichst sicher zu bleiben.

 

 

Autore(en) Tobias Zander
Titel Security im E-Commerce
Untertitel Absicherung von Shopsystemen wie Magento, Shopware und Oxid
Seiten 130
Preis 12,90 Euro
Verlag entwickler.press
Jahr Juni 2014
ISBN 978-3-86802-129-5

 

Das Buch „Security im E-Commerce“ richtet sich besonders an Webentwickler und IT-Entscheider, die eine E-Commerce-Seite betreiben wollen oder dies bereits schon tun. Es werden neben den häufigsten Angriffsszenarien wie XSS, Injection oder CSRF auch Exoten wie Pixel Perfect Timing beschrieben und mögliche Verteidigungsmethoden besprochen. Die Beispiele beruhen dabei auf der langjährigen Erfahrung des Autors und sind zum Großteil auch für Applikationen außerhalb des E-Commerce wie CMS-Projekte, Blogs oder Intranetapplikationen interessant. Zudem werden Frameworks und Tools analysiert, die helfen, Ihre Applikation mit möglichst geringem Aufwand abzusichern.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -