DasLaboratorium.de

17
Aug

Das Geheimnis der Form-Tokens – oder: wie mache ich meine Webformulare bombensicher?


Webformulare bergen zwei Risiken in sich, die bei der Webentwicklung beachtet werden müssen. Zum Einen besteht die Gefahr, dass Nutzer durch zweimaliges Absenden oder Neuladen der Webseite eine Aktion doppelt ausführen. Zum Anderen, dass Webformulare durch automatisierte Programme zum Beispiel für eine BruteForce Attacke oder zum Verteilen von Spam missbraucht werden.

Ersteres kann man zuverlässig mit so genannten Form-Tokens verhindern. Zweiteres lässt sich durch Web-Tokens erschweren, aber nicht gänzlich verhindern. Hier muss beispielsweise zusätzlich noch ein Captcha oder ein anderes Sicherheitssystem verwendet werden.

Was sind Form-Tokens? Jetzt stellt euch mal vor, ihr seid auf dem Amt. Und wie das so auf dem Amt ist, da zieht man erstmal eine Nummer, bevor sich ein Sachbearbeiter um einen kümmert. Wir sind alle nur Nummern. Und so ist das bei Formularen auch. Sobald ich ein Webformular aufrufe – also sobald ein Formular angezeigt wird – wird diesem Formularaufruf eine Nummer – ein Token – zugewiesen.

Dieses Token wird mit dem Abschicken des Formulars mitgegeben und gleichzeitig lokal auf dem Webserver in der Nutzersession gespeichert. Kommen Daten aus einem Formular beim Server an, wird kontrolliert, ob der mit dem Formular übermittelter Token auch dem in der Nutzersession gespeichertem entspricht. Ist das nicht der Fall, so wird eine Fehlermeldung ausgegeben.

Aber jetzt machen wir uns erstmal an den Code:

Ich habe eine Klasse “User”, die ich bei jedem Seitenaufruf im Globalen Namensraum erstelle. Diese Klasse beinhaltet die Funktionen zum An- und Abmelden des Nutzers und auch folgende zwei Funktionen:

  1. public function token() {}
  2. public function checktoken($inputvar = INPUT_POST) {}

Zusätzlich existieren diese zwei Klasseneigenen Variablen:

  1. public $token = null;
  2. public $token_valid = null;

Bei jedem Formular auf der Webseite gibt es ein verstecktes Feld, dessen Inhalt bei der Übermittlung mitgesendet wird. Die Einbindung des Tokens sieht so aus:

  1. <input name="token" type="hidden" value="<?php echo($user->token()); ?>" />

Wie man sieht, wird der Token mittels Aufruf der token() Funktion in das versteckte Feld des Formulars geschrieben. Der Token ist eine einfache, zufällige Zeichenkette mit einer Länge von 32 Buchstaben. Alternativ kann man auch den Hash-Wert der session_id() nehmen. Dabei ist aber darauf zu achten, dass das Berechnen des Hash-Wertes dem Server etwas Leistung abverlangt und dass dem Nutzer die Session-Id bekannt sein könnte und er den Hash-Wert so leicht selbst berechnen könnte. Ich betrachte also die Erzeugung einer eigenen Zufallszeichenkette für schneller und sicherer. Die Funktion sieht also folgendermaßen aus:

  1. public function token() {
  2.   // wenn noch kein Token existiert, dann erzeugen wir einen
  3.   if(!$this->token) {
  4.     // System_Util::randomstring(32) ist ein einfacher Zufallsgenerator
  5.     $this->token = System_Util::randomstring(32);
  6.     $_SESSION[‘token’] = $this->token;
  7.   }
  8.   // und geben den Token zurück
  9.   return($this->token);
  10. }

Man beachte: Ich erzeuge nur EINEN Token pro Seitenaufruf. Wurde bereits ein Token erstellt, so gebe ich den bereits vorhandenen zurück. Sollten nämlich zwei oder mehrere Formulare auf einer Seite vorhanden sein, so würde immer nur das zuletzt erstellte funktionieren (da die Tokens der vorherigen Formulare ja in der Session überschrieben werden würden).

Bei der Verarbeitung von eigenenden Daten aus Formularen muss also jetzt nur noch überprüft werden, ob der Token dem in der Session gespeicherten entspricht. Dazu folgende Funktion (Anm. Ich verwende die filter_input funktionen zur Datenvalidierung):

  1. public function checktoken($inputvar = INPUT_POST) {
  2.   // Filtern wir die Input-Variable nach dem Token
  3.   if($token = filter_input($inputvar, ‘token’, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW)) {
  4.     // Wenn ein Token übermittelt wurde, dann checken wir, ob es dem Token in der Session entspricht
  5.     if(isset($_SESSION[‘token’]) AND ($token == $_SESSION[‘token’])) {
  6.       // Wir "killen" den Token in der Session, damit das Formular beim reload einen Fehler verursacht
  7.       $_SESSION[‘token’] = ;
  8.       // Und geben true (valid) zurück
  9.       return($this->token_valid = true);
  10.     } else {
  11.       // Wenn das Token nicht korrekt ist, geben wir false (invalid) zurück
  12.       return($this->token_valid = false);
  13.     }
  14.   } else {
  15.     // Wenn gar kein Token übermittelt wurde, dann gibts false zurück
  16.     return(false);
  17.   }
  18. }

Wenn also ein Formular mit falschen Token übermittelt wurde, dann geben wir eine Fehlermeldung aus. Bei mir sähe das so aus:

  1. if($user->token_valid === false) {
  2.   // wrong token
  3.   $msg = array(‘headline’ => ‘An invalid token has been detected’, ‘description’ => ‘Please make sure, to only enter data into the forms presented on ‘ . PROJECT_HTTP_ROOT);
  4.   $page->body->append(new Display_Template(‘misc_errormsg’, $msg));
  5. }

Und fertig. Fragen/Anmerkungen/Verbesserungsvorschlage? Dazu sind die Kommentare da.

Tweet this!

Kommentieren/Beantworten