CSRF, Schutz vor Angriffen

Immer noch in den Top Ten der beliebtesten Angriffe. Die CRSF Attacke.

PHP und CSRF, Schutz vor Angriffen

CSRF, Schutz vor Angriffen

CRSF Attacke sind immer noch beliebt. Der Hauptgrund dürfte sein, weil es so einfach ist. Einfach einen vergifteten Link an jemanden Senden, der vermutlich unbedarft ist und schon hat man durch Ihn ein Formular abgeschickt , einen User angelegt oder etwas gelöscht. 

Ein vereinfachtes Beispie In einer Mail oder Webseite befindet sich folgendes Stück HTML:

<img src="http://Meinebank.de/ueberweisung.cgi?von=12345678&an=3333333333&betrag=1000&datum=20150702" width="0" height="0" />

Wenn jetzt die Banking Seite nicht gesichert wäre und der Benutzer sich vorher eingelogt hatte , würde einfach eine Überweisung abgesendet....Überraschung!

Was kann man also jetzt dagegen tun?

Keine GET Variablen benutzen

Wie man im obigen Beispiel sehr schön sehen kann würde das ganze so einfach schon nicht mehr funktionieren, wenn keine GET Variablen genutzt werden, man könnte jetzt natürlich hingehen und das Ganze in Javascript mit gefälschten POST  Variablen ausführen, aber das ist dann schon nicht mehr so einfach und viele Email Programme führen Javascript nicht aus (Zumindest die Guten nicht.) 

Sessions nutzen

Das Absenden solcher wichtiger Formulare darf nur für eingelogte Benutzer möglich sein. Obendrein ist es Sinnvoll den Benutzer nach einer gewissen Zeit auszuloggen, damit würde so ein Angrif sagen wir mal nach 15 Minuten Inaktivität schon nicht mehr funktionieren. Solch ein Timeout ist auch Sinvoll für Formulare in OnlineShops wo ja auch ettliche Benutzer am liebsten bestellen wollen ohne einen Account zu erzeugen . (Sicherheitstechnisch ein Unding)

Fingerprinting

IP Adresse , Browserkennung, Browsereinstellungen... es gibt eine ganze Reihe an Merkmalen um den Browser eines Benutzers zu Identifizieren. Solch ein Fingerprint kann allerdings auch Nachteile haben. Die IP Adresse kann durch ein Proxy verändert worden sein und sogar schon mal wechseln. Obendrein hat es in letzter Zeit hat es einige Fälle gegeben in denen Browser wechselnde Kennungen gesendet haben. Bei letzteren handelte es sich um einen Bug in Chrome. Letztendlich muss ich dann jetzt als Seitenanbieter entscheiden ob mir die zusätzliche Sicherheit eines solchen Fingerprints den möglicherweise etwas erhöten Supportaufwand wert ist.

Verschlüsselte Übertragung mit https  

Https erhöht die Sicherheit drastisch, greift aberr nicht wirklich bei CSRF Attacken, da ja der Aufruf eigentlich vom Browser des richtigen Anwenders ausgeht. Https ist allerdigs sehr Nützlich um "Man in the Middle Angriffe" zu erschweren und das Klauen von Klartext Authentifiezierungsdaten zu verhindern.  Insgesammt ist https so wichtig, das jede Seite die Irgendwelche Formulare und Logins anbietet auf jeden fall https nutzen sollte!

Referer 

Eigentlich das unsicherste Hilfsmittel der bisher Vorgestellten. Referer werden oft unterdrückt und sind einfach zu fälschen. allerdings wenn der Referer sagt das der Absenders des entsprechenden Forms von ganz woanders kommt, ist das ein sicherer Hinweis das da etwas nicht stimmt. Also warum sollte man sich dieses kleine Quentchen extra Sicherneit nicht gönnen. Man muß nur dran denken das ein korrekter Referer nicht heist, das alles stimmt

Ein Token im Formular

Jetzt kommen wir zum meistgenutzen gegenmittel gegen CSRF Attacken. Einem Automatisch generiertem Token, in einem versteckten Formularfeld. Meist schaut die Grundidee so aus , das man ein Token generiert , das in der Session speichert und nach dem Absenden des Formulars wieder mit der Session vergleicht. Da CSRF Angriffe ja blind ausgeführt werden, ist solch ein Schutz auch schon völlig ausreichend. Da der Angreifer ein solches Token praktisch nicht erraten kann, kann er das Formular auch nicht absenden.  Oft wird jetzt argumentiert , jaaa aber wenn der Angreifer das Token abfangen kann ? Nun wenn der Angreifer das kann, hat man ein ganz anderes Problem. Dann kann der Angreifer den gesamten Datenverkehr mitschneiden inklusive des Session Cookies und damit kann der Angreifer sich einfach selbst einloggen und dasn ganze einfach manuell ausführen, sprich den Kauf tätigen oder die Überweisung vornehmen. Vor allem kannn er dann einfach Passwort und Username mitschneiden und damit ist dann Alles vorbei. Deswegen ist eben Https so wichtig. Das gleiche gilt für den Fall das der Browser übernommen wurde, wenn jemand den Browser übernommen hat, dan gibt er sich nicht mehr mit CSRF ab.

Ein Problem gibt es noch und zwar, das dieses Verfahren immer nur einen Schlüssel zuläst , wenn jetzt Jemand im Backend eines CMS arbeitet und dazu mehrere Browser Tabs benutzt so werden die Formulare mit dem Laden eines neuen Formulars immer wieder ungültig , so das der Anwender im Endeffekt nicht mit mehreren Tabs arbeiten kann. Deswegen wurden sebst signierende Tokens entwickelt , die dieses Problem einfach umgehen. Und auch hier gilt, sollte ein Angreifer in der Lage sein die Serverdaten und das Secret + Formname  auszulesen, dann hat man ein riesiges Problem allerdings nicht mit CSRF.

Ich hatte vor einer Weile zu dem Thema mal eine kleine Klasse geschrieben:

<?php
/*
KLASSE für Selbst signierende TOKEN
License LGPL v3
Copyright Norbert Heimsath
*/
class Token {
    // Klassen Variablen 
    public $Timeout=3600; //Eine Stunde
    public $Secret ="dlfkjsddlfkjsdf3434jfpos"; //you can set your own secret
    public $FingerPrint="";
    public $ServerString="";
    public $TokenName="formtoken"; 

    // Das Token erzeugen
    function create ($FormName="") {
        // Secret zusammenstellen
        $Secret= $this->Secret . $this->FingerPrint . $this->ServerString . $FormName;

        //Timeout festlegen
        $Timeout= time()+$this->Timeout;

        //netter zufälliger Tokenstring mit nicht festgelegter länge könnte man auch noch komplexer gestalten
        $Token= dechex(mt_rand()); 

        // Es gibt bessere Hashes aber jetzt nehmen wir einfach mal sha1.
         $Hash= sha1($Secret.'-'.$Token.'-'.$Timeout); 

        // Zum Selbst signierenden Term zusammenstellen
        $Signed= $Token.'-'.$Timeout.'-'.$Hash;

        return $Signed;
    }

    // Ausgabe für GET Parameter     formtoken=34ß029-20934802898-82304923409
    function get ($FormName=""){
        $Token=$this->create($FormName);
        $Out= $this->TokenName."=".$Token;
        return $Out;
    }

    // Ausgabe als Input feld    
    // <input type="hidden" name="formtoken" value="34ß029-20934802898-82304923409" title="" alt="" />
    function field ($FormName=""){
        $Token=$this->create($FormName);
        $Out= sprintf('<input type="hidden" name="%s" value="%s" title="" alt="" />', $this->TokenName, $Token );
        return $Out;
    }

    // get result as an array
    function arry ($FormName=""){
        $Token=$this->create($FormName);
        $Out=array("name" => $this->TokenName , "token" => $Token);
        return $Out;
    }

    // Token überprüfen
    function check($FormName="", $Signed=""){
        // Versuch das Token vom $_REQUEST zu holen wenn kein wert übergeben
        // ansonsten Abbruch
        if (!$Signed) { 
            if (isset ($_REQUEST[$TokenName])) $Signed=$_REQUEST[$TokenName];
            else return ("Kein Token gefunden");
        }
        
        //Secret zusammenstellen
        $Secret= $this->Secret . $this->FingerPrint . $this->ServerString . $FormName;

        // Zerlegen        
        $Parts= explode('-', $Signed);

        //Token hat immer 3 Teile 
        if (count($Parts)!=3) return ("Invalides Token");

        //Auslesen
        list ($Token, $Timeout, $Hash)= $Parts;
        
        //Alter Testen
        if ($Timeout < time()) return ("Altes Token");     

        // Signierung testen
        // Hat jemand am Timeout oder am Token gespielt, dann passt der Hash nicht mehr
        // und umgekehrt.
        if ($Hash!=sha1($Secret.'-'.$Token.'-'.$Timeout)) return ("Unsigniertes Token"); 
            
        return false;
    }
}

 

//Aufgerufen wird die Klasse am besten wenn das Script/CMS Initialisiert wird :
$Tok = new Token;

// Einige Werte wie das Secret oder eventuell ein Fingerprint initialisieren
$Tok->Secret ="dlfkjsddlferrerekjsdos";
$Tok->TokenName ="mytoken";

// Danach gibt es 3 Möglichkeiten einen token zu erstellen
$Result1=$Tok->get("Formularname");      // kann genutzt werden für GET parameter 
$Result2=$Tok->arry("Formularname");     // gibt  Tokenname und Token als Array aus.
$Result3=$Tok->field("Formularname");    // gibt ein komplettes Input Feld zurück
$Result4=$Tok->create("Formularname");   // erzeugt nur das Token alleine

// getestet wird dan das Token einfach folgendermaßen
$check=$Tok->check("Formularname", $Result4);
if ($check) echo $check;
else        echo "ERFOLG!!!";

echo "<br><br>";

// Ich pfusche mal am token
$Result4.="r";
$check=$Tok->check("Formularname", $Result4);
if ($check) echo $check;
else        echo "ERFOLG!!!";

// Wenn der check() Methode kein Token übergeben wird, versucht sie das Token im $_REQUEST zu finden 

Das Ganze ist zwar recht rudimentär , aber man kann natürlich auch einfach eine Methode einbauen die den Fingerprint/Serverstring selbst erzeugt. Der Code steht unter LGPL, was die Verwendung einfach machen dürfte. Der Vorteil ist das kein Zugriff auf eine Session benötigt wird und auch keine Probleme mit mehrfach geöffneten Tabs entstehen. Man könnte natürlich die Session in das Secret mit einbauen, so das alle Formulare ungültig werden wenn die SessionID sich ändert. 


Kommentare

Kommentar schreiben

Letzte Worte eines Nachtwächters:
"Hallo, ist da wer?"