Cross-Site-Request-Forgery (CSRF): Angriffe verhindern

Veröffentlicht am

Achtung: Dieser Beitrag erschien ursprünglich auf meinem alten Blog und ist sehr alt. Manche Meinungen und Ansichten können, aber müssen sich nicht geändert haben.

Bei einem Cross-Site-Request-Forgery-Angriff lockt der Angreifer einen Benutzer, der mit seinem Browser bei einem angreifbaren Dienst eingeloggt ist, auf eine präparierte Website, die dann Aktionen beim angreifbaren Dienst mit den Berechtigungen des eingeloggten Benutzers ausführt. Wenn zum Beispiel ein soziales Netzwerk angreifbar für CSRF ist, könnte der Angreifer im Namen des Opfers an eine beliebige Person Nachrichten verschicken oder andere Aktionen ausführen, die nicht gegen CSRF-Attacken abgesichert sind.

Ein Webbrowser benutzt Tabs damit man mehrere Webseiten parallel anzeigen lassen kann. Wenn man also beim angreifbaren Dienst eingeloggt ist und man öffnet in einem zweiten Tab ebenfalls diesen Dienst dann ist man dort ebenfalls eingeloggt.

Wenn man zum Beispiel private Nachrichten über bestimmte URL-Parameter verschicken könnte und würde den präparierten Link mit den URL-Parametern zum Senden der Nachricht dann auf der Website des Angreifers in einem Image-Tag angeben, dann würde der Browser die URL anfragen und versuchen das Bild einzubinden. Der Browser weiß ja gar nicht, ob diese URL am Ende ein Bild zurückgibt oder nicht. Die Anfrage an den Server des entsprechenden Dienstes findet trotzdem statt. Und wenn das Opfer dort eingeloggt ist, wird die schädliche Aktion ausgeführt. Im Quellcode auf der Website des Angreifers könnte dieser Teil so aussehen:

<img src="http://example.com/action/message.php?to=EmpfängerID&subject=Betreffszeile&message=Nachrichteninhalt" />

Wie schützt man sich vor Cross-Site-Request-Forgery?

Hier liegt es am entsprechenden Dienst zu verhindern, dass Benutzer nicht durch CSRF-Angriffe angegriffen werden. Um CSRF-Angriffe zu verhindern bzw. sicherzustellen, dass die Anfrage wirklich von der Seite des Dienstes und nicht etwa von einer präparierten Webseite (wie zum Beispiel der des Angreifers) kommt, sollte man CSRF-Tokens einsetzen, die bei jedem Request mitgeschickt werden und möglichst zufällig sind, so dass man sie nicht erraten kann.

Auf Serverseite schreibt man den generierten CSRF-Token dann in eine Session-Variable um sie dem Benutzer zuordnen zu können, und schreibt ihn dann in das entsprechende Formular. Wenn der Benutzer diese Aktionen (z.B. Nachricht versenden) jetzt auf ganz normalem Wege durchführt, dann steht nach dem Anfordern der Seite zum Versenden der Nachrichten bereits der Token im versteckten Formularfeld. Wenn der Benutzer das Formular jetzt abschickt kann auf Serverseite überprüft werden, ob der mitgesendete Token mit dem vorherig generierten übereinstimmt.

Wie oft sollte sich der CSRF-Token ändern?

Der CSRF-Token sollte sich am besten bei jedem einzelnen Seitenaufruf ändern. Es reicht aber auch, wenn der CSRF-Token einmalig für eine Session generiert wird. Das heißt, dass der Token neu generiert wird, sobald eine neue Session gestartet wird und so lange besteht, bis die Session abgelaufen ist. Diese Session ist serverseitig und läuft meistens dann ab, wenn man längere Zeit keine Aktion mehr auf einer Website tätigt.

Der Angreifer muss für einen erfolgreichen Angriff immer noch an den CSRF-Token kommen. Daran könnte er aber nur kommen, wenn er anderweitig Zugriff auf den Tab des (nun nicht mehr) per CSRF angreifbaren Dienstes hätte.

PHP-Funktion für CSRF-Tokens für die Dauer einer Sitzung

Die folgende PHP-Funktion ist ein einfaches Beispiel zum Generieren von CSRF-Tokens: Die Funktion prüft zuerst ob bereits ein CSRF-Token im Session-Array vorhanden ist und gibt ihn dann zurück. Falls nicht wird er einfach schnell erzeugt. Die Funktion gibt also immer einen CSRF-Token mit der Länge von 20 Zeichen zurück. So kann man hingehen und zu seinen Formularen ein weiteres, verstecktes Formularfeld erzeugen und in das Value-Attribut einfach <?=securityToken()?> schreiben.

function securityToken() {
	if(!isset($_SESSION['token'])) {
		$_SESSION['token'] = bin2hex(random_bytes(20));
	}

	return $_SESSION['token'];
}

Wenn der POST- oder GET-Request dann beim Server ankommt, kann man mittels PHP überprüfen, ob der Token gültig ist indem man einfach die Funktion securityToken() mit dem POST- oder GET-Parameter vergleicht. Und nur wenn die folgende Bedingung korrekt ist, sollte man die vom Client gewünschte Aktion durchführen:

if(securityToken() === $_POST['token']) {
	// some action
}
Bild meiner Person

Über mich

Hi. Ich bin Thomas. Hier veröffentlichte ich in unregelmäßgen Abständen mehr oder weniger interessante Beiträge über Dies und Jenes, hauptsächlich über Computer und IT. Außerdem mochte ich die Linux-Kommandozeile, vor allem wenn ich mit (m)einer mechanischen Tastatur darauf herumhacken kann. 😀 Es wird hier aus persönlichen Gründen nicht mehr viel neues geben.