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

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 die Nachrichtenfunktion eines 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 (CSRF)?

Hier liegt es am entsprechenden Dienst, zu verhindern, dass Benutzer durch CSRF angegriffen werden. Um CSRF-Angriffe zu verhindern bzw. sicherzustellen, dass die HTTP-Anfrage wirklich aus dem Kontext der Webseite des Dienstes und nicht etwa von einer präparierten Webseite des Angreifers kommt, sollte man sog. 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. Diesen CSRF-Token schreibt man dann in das entsprechende Formular, das Aktionen wie das Senden einer Nachricht auslösen soll. 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 Token aus der Session-Variable übereinstimmt.

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. Nur wenn die folgende Bedingung korrekt ist, sollte man die vom Client gewünschte Aktion durchführen:

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

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 die Dauer einer 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.

Hinweis:
Dies ist ein älterer Artikel von meinem alten Blog. Die Kommentare zu diesem Artikel werden (falls vorhanden) später noch hinzugefügt.

Der Autor

Unter dem Namen »TheBlackPhantom« alias »BlackY« veröffentlichte ich auf meinem alten Blog, BlackPhantom.DE, in der Zeit von 2011 bis 2015 leidenschaftlich Beiträge über Computer, Internet, Sicherheit und Malware. Während der BlackPhantom-Zeit war ich noch grün hinter den Ohren und lernte viel dazu. Mehr Infos vielleicht in Zukunft...