Skip to content

Websecurity: Cookie Tossing

Github hat alle Github Pages auf eigene github.io-Domains verlagert. Auslöser dafür war unter anderem die Gefahr von "Cookie Tossing", einem Angriff, der nicht nur Github gefährlich werden kann. Was es damit auf sich hat, erfahren Sie hier.

Als Beispiel-Webanwendung dient ein Portal, dass passenderweise auf portal.example läuft und das es seinen Benutzern unter anderem erlaubt, unter [benutzername].portal.example eigene Websites zu speichern. Die Angriffe wären aber genauso über XSS-Schwachstellen auf portal.example möglich.

Schritt 1: Wir setzen einen Session-Cookie

Webanwendungen verwenden meist Cookies, um einen Benutzer während einer laufenden Session zu erkennen. Diese Cookies haben meist einen entsprechenden Namen, zum Beispiel session, _session, id oder ähnliches. Im folgenden soll der Session-Cookie der Demo-Anwendung _session heißen. Der Session-Cookie wird mit folgenden Header über HTTPS gesetzt:

Set-Cookie: _session=Wert_des_Session-Token; path=/; expires=Fr, 11-April-2014 00:00:00 GMT; secure; HttpOnly

Für diesen Cookie sind zwei Flags gesetzt:

  • secure sorgt dafür, dass der Cookie nur über die geschützten HTTPS-Verbindungen und nicht über ungesicherte HTTP-Verbindungen übertragen wird.
  • HttpOnly verhindert, dass über JavaScript auf den Cookie zugegriffen wird. Der Cookie wird vom Webbrowser bei jeden Request an die entsprechende Domain mitgeschickt, kann aber nicht von zum Beispiel über eine XSS-Schwachstelle eingeschleusten JavaScript-Code gelesen werden.

Außerdem wird der Cookie für die Hauptdomain portal.example gesetzt, er ist also für die Subdomains unter *.portal.example wie zum Beispiel irgendwer.portal.example nicht zugänglich.

Der Cookie ist also gut geschützt: Die Seiten der Benutzer können nicht darauf zugreifen, und da er nur über HTTPS übertragen wird, kann er nur von einem Man-in-the-Middle in der HTTPS-Verbindung belauscht werden.

Schritt 2: Ein Angreifer bewirft uns mit Cookies

Zwar kann von den Subdomains aus nicht auf den Session-Cookie der Hauptdomain zugegriffen werden, die Subdomains können aber Cookies für die Hauptdomain setzen. Bevor ich dazu kommen, muss ich erst noch das Senden der Cookies vom Browser an den Server erklären.

Mit jedem HTTP-Request sendet der Webbrowser automatisch die zur jeweiligen Domain gehörenden Cookies zurück. Konkret bedeutet dass, das bei einen Request an portal.example alle dafür gesetzten Cookies mitgeschickt werden:

GET / HTTP/1.1
Host: portal.example
Cookie: ein-cookie=sein-Wert; _session=Wert_des_Session-Token; noch-einer=der-Wert;

Beim "Cookie Tossing" wird ausgenutzt, dass nur Name und Wert der Cookies übertragen werden, aber nicht die weiteren dazu gehörenden Informationen wie der Pfad oder die Domain.

Ein Angreifer könnte auf angreifer.portal.example über JavaScript einen Cookie setzen, der an die Hauptdomain portal.example gesendet wird:

document.cookie = "_session=Ein_gefaelschtes_Sesion-Token; Path=/; Domain=.portal.example"

Bei einem Request an portal.example würde dieser Cookie mitgeschickt:

GET / HTTP/1.1
Host: portal.example
Cookie: ein-cookie=sein-Wert; _session=Wert_des_Session-Token; noch-einer=der-Wert; _session=Ein_gefaelschtes_Sesion-Token;

Der HTTP-Request enthält also zwei Cookies mit identischen Namen, und es gibt keine Möglichkeit, festzustellen, woher welcher Cookie stammt. Denn die einzig möglichen Unterscheidungsmerkmale Domain, Pfad, secure- und HttpOnly-Flag werden ja nicht mitgeschickt.

An diesem Punkt wirkt sich eine Unterlassungssünde im für die Statusverwaltung zuständigen Standard RFC 6265, HTTP State Management Mechanism, negativ aus: Die Reihenfolge, in der die für eine Domain und ihre Subdomain gesetzten Cookies in den Requests übertragen wird, wurde nicht spezifiziert. Die Browser können die Cookies in der Reihenfolge senden, die ihnen gefällt. Die WebKit-Browser ordnen die Cookies zum Beispiel nach dem Zeitpunkt des Anlegens.

Das gleiche gilt sinngemäß für die Webserver und Webserver-Interfaces der Webanwendungen: Welchen von mehreren Cookies mit gleichen Namen sie verwenden, ist ihnen überlassen. Rack, das Webserver-Interface für Rails, verwendet zum Beispiel immer den Wert des ersten im Request gefundenen Cookies, die Werte weiterer Cookies mit gleichem Namen werden verworfen.

Ein bösartiger Benutzer kann das ausnutzen, um von seiner Subdomain aus zum Beispiel den Session-Cookie der Hauptdomain zu manipulieren, wodurch die angegriffenen Benutzer dann von der Webanwendung nicht mehr erkannt werden. Mitunter werden die Cookies aber auch für andere Zwecke verwendet, bei Github zum Beispiel zur Abwehr von CSRF-Angriffen. Egor Homakov hat beschrieben, wie ein Angreifer diesen Cookie manipulieren und danach einen CSRF-Angriff durchführen kann.

Das Problem bei so einem Angriff: Der Angreifer weiß, wie sich der Server verhält, aber nicht, welchen Browser das Opfer verwendet. Das ist aber kein großes Problem, eine Browserweiche reicht aus, um den passenden Code für den Angriff auszuwählen.

Cookie Tossing verhindern

Github hatte es bei der Abwehr der Angriffe einfach: Indem der Code zum Parsen der Cookies in Rack übersprungen wurde, konnte geprüft werden, ob ein Request doppelte Einträge, konkret für den _session-Cookie, enthält. Wurde dadurch ein Angriff erkannt, wurde der von der Subdomain gesetzte _session-Cookie gelöscht.

Sofern Sie keinen vergleichbaren Zugriff auf den Webserver bzw. dessen Interface haben, gibt es eine andere einfache Gegenmaßnahme: Lassen Sie keinen fremden JavaScript-Code unter Ihrer Domain laufen. Das bedeutet zum einen, dass Ihre Webanwendung keine XSS-Schwachstelle enthalten darf. Das ist ja eigentlich eine Selbstverständlichkeit, und Cookie Tossing ist so ziemlich das kleinste denkbare Problem beim Vorhandensein einer XSS-Schwachstelle. Zum anderen darf es keinen von Benutzern hochgeladenen JavaScript-Code geben - was Sie nur betrifft, wenn Sie eine Webanwendung betreiben, die "user generated content" samt JavaScript-Code verwendet. Am einfachsten erreichen Sie dass gewünschte Ziel dann, wenn Sie die Benutzerseiten auf einer anderen Domain unterbringen, so wie es Github mit der Einführung von github.io getan hat. Auch Blogger trennt Dashboard (unter blogger.com) und Benutzerblogs (unter blogspot.com) durch den Einsatz zweier Domains.

Weitere Angriffe

Escapen der Cookies

Ein weiterer Angriff ist möglich, weil RFC 6265 auch nicht festlegt, ob und wenn ja wie Cookies escaped werden sollen. Die meisten Webserver inklusive Rack gehen davon aus, dass Cookies URL-kodiert sind. Das ist eine brauchbare Annahme, wenn man davon ausgeht, dass sie ja auch Nicht-ASCII-Zeichen enthalten können. Beim Erzeugen der Cookies aus dem Request werden die Cookies daher URL-dekodiert. Ein bösartiger Benutzer kann seinen gefälschten Cookie also so kodieren, dass der Webserver ihn für den angegriffenen Cookie hält, obwohl er es nicht ist. So würde der Webserver zum Beispiel _%73ession URL-dekodieren und für den Cookie _session halten. Das obige Beispiel sähe dann so aus:

GET / HTTP/1.1
Host: portal.example
Cookie: ein-cookie=sein-Wert; _session=Wert_des_Session-Token; noch-einer=der-Wert; _%73ession=Ein_gefaelschtes_Sesion-Token;

Der Code zum Löschen des Subdomain-Cookies würde in diesem Fall einen Header erzeugen, der keine Wirkung auf den Cookie hätte: Gelöscht würde der (nicht vorhandene) Cookie _session, der gesetzte Cookie _%73ession bliebe erhalten. Als Gegenmaßnahme muss der Unescape-Code des Webservers übersprungen werden, so dass für jeden empfangenen Cookie geprüft werden kann, ob er nach dem Escapen den Namen eines bereits vorhandenen erhält. Der so ermittelte gefälschte Cookie kann dann unter seinem richtigen (escaped bzw. in diesem Fall URL-kodierten) Namen gelöscht werden.

Cookie-Überlauf

Zu guter Letzt kann ein Angreifer auch noch ausnutzen, dass die Anzahl Cookies, die für eine Domain gesetzt werden kann, von allen Browser limitiert wird. Und das ist auch gut so, denn sonst könnte ein Angreifer ja beliebig viele Cookies setzen, was auch wieder zu Problemen führen kann. Theoretisch wäre es zum Beispiel möglich, mit den üblicherweise 4 KB kleinen Plätzchen die Festplatte zu füllen. Bei den heutigen Festplattengrößen von Desktop-Rechnern im TB-Bereich braucht man dafür zwar eine gewaltige Anzahl, es gibt aber inzwischen genug andere Clients mit deutlich weniger Speicherplatz. Außerdem könnte eine zu große Anzahl an Cookies zu Angriffen auf mögliche Pufferüberlauf-Schwachstellen in den Webservern und -anwendungen ausgenutzt werden.

Laut RFC 6265 sollen Cookies mindestens 4 KB gross sein und die Browser pro Domain 50 Cookies und insgesamt 3000 Cookies verwalten können. Firefox kann zum Beispiel 150 Cookies pro Domain verwalten, Chrome 180. Da in RFC 6265 auch nicht festgelegt ist, welche Cookies gesendet werden müssen oder sollen, ist auch dies den Browsern überlassen.

Chrome zum Beispiel sendet die neuesten 180 Cookies, unabhängig davon, ob sie von der Hauptdomain oder einer Subdomain gesetzt wurden, ob sie über HTTP oder JavaScript gesetzt wurden oder ob secure- oder HttpOnly-Flag gesetzt sind oder nicht. Es gibt nur die Einschränkung, dass Cookies mit gesetztem secure-Flag nur über HTTPS gesendet werden - zusammen mit so vielen Cookies ohne secure-Flag, wie bis zum Erreichen der 180 Cookies fehlen.

Ein Angreifer auf einer Subdomain kann also alle Cookies der Hauptdomain mit seinen eigenen Cookies überschreiben. Zuerst muss er dazu 180 beliebige Cookies per JavaScript setzen:

for (i = 0; i < 180; i++) {
    document.cookie = "cookie" + i + "=irgendwas; Path=/; Domain=.portal.example"
}

Jetzt gibt es die 180 Cookies cookie0 bis cookie179, und alle von der Hauptdomain gesetzten Cookies wurden überschrieben. Jetzt können diese 180 Cookies ungültig gemacht werden, indem ihr Ablaufdatum auf einen Wert in der Vergangenheit gesetzt wird:

for (i = 0; i < 180; i++) {
    document.cookie = "cookie" + i + "=irgendwas; Path=/; Domain=.portal.example; Expires=Thu, 01-Jan-1970 00:00:01 GMT;"
}

Wird jetzt ein _session-Cookie gesetzt, ist er der einzige vorhandene Cookie, und er wird automatisch mit den folgenden Requests an den Server gesendet:

document.cookie = "_session=Ein_gefaelschtes_Sesion-Token; Path=/; Domain=.portal.example"

Der Server hat keine Möglichkeit, diesen von der Subdomain per JavaScript gesetzten Cookie von dem von ihm über HTTP gesetzten _session-Cookie mit gesetztem secure- und HttpOnly-Flag zu unterscheiden.

Firefox verarbeitet die Cookies übrigens vorsichtiger. Um Überläufe zu vermeiden, werden die Cookies für Subdomains und Hauptdomain getrennt gespeichert, und die Cookies werden auch geordnet gesendet.

Fazit

Das Fazit lautet: Fremder JavaScript-Code auf der eigenen Domain einer Webanwendung ist eine schlechte Idee. Dass eine Webanwendung keine XSS-Schwachstelle haben darf, ist selbstverständlich. Aber auch "user generated content" darf keinen (schädlichen) JavaScript-Code enthalten. Die einfachste Lösung dieses Problems ist wie oben schon erwähnt der Einsatz getrennter Domains für Webanwendung und Benutzerdaten.

Carsten Eilers

Trackbacks

Dipl.-Inform. Carsten Eilers am : Drucksache: PHP Magazin 4.2013 - GitHub-Sicherheit

Vorschau anzeigen
Im PHP Magazin 4.2013 ist ein Artikel über die Sicherheit von Git und insbesondere GitHub erschienen. Unter anderem geht es um zwei bekannte Schwachstellen bzw. Angriffe auf GitHub und GitHubs Reaktionen darauf: Zum einen um den &quot;Angri

Dipl.-Inform. Carsten Eilers am : Drucksache: windows.developer Magazin 7.2013 - GitHub-Sicherheit

Vorschau anzeigen
Im windows.developer Magazin 7.2013 ist ein Artikel über die Sicherheit von Git und insbesondere GitHub erschienen. Es handelt sich um eine erweiterte und aktualisierte Version des Artikels aus dem PHP Magazin 4.2013. An einer Stelle wurde

entwickler.de am : PingBack

Die Anzeige des Inhaltes dieses Trackbacks ist leider nicht möglich.

Dipl.-Inform. Carsten Eilers am : Drucksache: PHP Magazin 4.2015 - Cross-Side Request Forgery

Vorschau anzeigen
Im PHP Magazin 4.2015 ist ein Artikel über Cross-Site Request Forgery (CSRF) erschienen. Cross-Site Request Forgery (CSRF) ist eine sehr alte Schwachstelle. Das zu Grunde liegende Problem wurde erstmals 1988 unter dem Namen &quot;Confused Dep

Dipl.-Inform. Carsten Eilers am : Neues eBook: "Websecurity - Angriffe mit SSRF, CSRF und XML"

Vorschau anzeigen
Bei entwickler.press ist ein neues eBook von mir erschienen: &quot;Websecurity - Angriffe mit SSRF, CSRF und XML&quot; (ISBN: 978-3-86802-569-9, Preis: 2,99 €, erhältlich in den üblichen eBook-Shops). Der Shortcut beschäftigt sich