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.
Trackbacks
Dipl.-Inform. Carsten Eilers am : Drucksache: PHP Magazin 4.2013 - GitHub-Sicherheit
Vorschau anzeigen
Dipl.-Inform. Carsten Eilers am : Drucksache: windows.developer Magazin 7.2013 - GitHub-Sicherheit
Vorschau anzeigen
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
Dipl.-Inform. Carsten Eilers am : Neues eBook: "Websecurity - Angriffe mit SSRF, CSRF und XML"
Vorschau anzeigen