So fügen Sie CSRF-Token (Cross-Site Request Forgery) mithilfe von PHP ordnungsgemäß hinzu

94

Ich versuche, den Formularen auf meiner Website etwas Sicherheit zu verleihen. Eines der Formulare verwendet AJAX und das andere ist ein einfaches "Kontakt" -Formular. Ich versuche, ein CSRF-Token hinzuzufügen. Das Problem, das ich habe, ist, dass das Token nur manchmal im HTML- "Wert" angezeigt wird. Der Rest der Zeit ist der Wert leer. Hier ist der Code, den ich auf dem AJAX-Formular verwende:

PHP:

if (!isset($_SESSION)) {
    session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;

}

HTML

 <form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>

Irgendwelche Vorschläge?

Ken
quelle
Nur neugierig, wofür token_timewird verwendet?
Zerkms
@ zerkms Ich benutze derzeit nicht token_time. Ich wollte die Zeit begrenzen, innerhalb der ein Token gültig ist, habe den Code jedoch noch nicht vollständig implementiert. Aus Gründen der Klarheit habe ich es aus der obigen Frage entfernt.
Ken
1
@ Ken: Kann der Benutzer den Fall erhalten, wenn er ein Formular geöffnet, veröffentlicht und ein ungültiges Token erhalten hat? (da es ungültig gemacht wurde)
zerkms
@ zerkms: Danke, aber ich bin ein wenig verwirrt. Gibt es eine Chance, mir ein Beispiel zu geben?
Ken
2
@ Ken: sicher. Angenommen, der Token läuft um 10:00 Uhr ab. Jetzt ist es 09:59 Uhr. Der Benutzer öffnet ein Formular und erhält ein Token (das noch gültig ist). Anschließend füllt der Benutzer das Formular 2 Minuten lang aus und sendet es. Solange es jetzt 10:01 Uhr ist, wird das Token als ungültig behandelt, sodass der Benutzer einen Formularfehler erhält.
Zerkms

Antworten:

284

Für Sicherheitscodes generieren Sie Ihre Token bitte nicht wie folgt: $token = md5(uniqid(rand(), TRUE));

Probieren Sie es aus:

Generieren eines CSRF-Tokens

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

Nebenbemerkung: Eines der Open-Source-Projekte meines Arbeitgebers ist eine Initiative zum Backport random_bytes()und random_int()in PHP 5-Projekte. Es ist MIT-lizenziert und auf Github und Composer als paragonie / random_compat verfügbar .

PHP 5.3+ (oder mit ext-mcrypt)

session_start();
if (empty($_SESSION['token'])) {
    if (function_exists('mcrypt_create_iv')) {
        $_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
        $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
}
$token = $_SESSION['token'];

Überprüfen des CSRF-Tokens

Nicht nur verwenden ==oder sogar ===verwenden hash_equals()(nur PHP 5.6+, sondern für frühere Versionen mit der Hash-kompatiblen Bibliothek verfügbar ).

if (!empty($_POST['token'])) {
    if (hash_equals($_SESSION['token'], $_POST['token'])) {
         // Proceed to process the form data
    } else {
         // Log this as a warning and keep an eye on these attempts
    }
}

Mit Per-Form-Token noch weiter gehen

Sie können Token weiter einschränken, damit sie nur für ein bestimmtes Formular verfügbar sind, indem Sie verwenden hash_hmac(). HMAC ist eine bestimmte verschlüsselte Hash-Funktion, die auch bei schwächeren Hash-Funktionen (z. B. MD5) sicher verwendet werden kann. Ich empfehle jedoch, stattdessen die SHA-2-Familie von Hash-Funktionen zu verwenden.

Generieren Sie zuerst ein zweites Token zur Verwendung als HMAC-Schlüssel und verwenden Sie dann die folgende Logik, um es zu rendern:

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

Verwenden Sie dann eine kongruente Operation, um das Token zu überprüfen:

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

Die für ein Formular generierten Token können nicht ohne Wissen in einem anderen Kontext wiederverwendet werden $_SESSION['second_token']. Es ist wichtig, dass Sie ein separates Token als HMAC-Schlüssel verwenden als das, das Sie gerade auf der Seite ablegen.

Bonus: Hybridansatz + Zweigintegration

Jeder, der die Twig-Template-Engine verwendet, kann von einer vereinfachten Doppelstrategie profitieren, indem er diesen Filter zu seiner Twig-Umgebung hinzufügt:

$twigEnv->addFunction(
    new \Twig_SimpleFunction(
        'form_token',
        function($lock_to = null) {
            if (empty($_SESSION['token'])) {
                $_SESSION['token'] = bin2hex(random_bytes(32));
            }
            if (empty($_SESSION['token2'])) {
                $_SESSION['token2'] = random_bytes(32);
            }
            if (empty($lock_to)) {
                return $_SESSION['token'];
            }
            return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
        }
    )
);

Mit dieser Twig-Funktion können Sie beide Allzweck-Token wie folgt verwenden:

<input type="hidden" name="token" value="{{ form_token() }}" />

Oder die gesperrte Variante:

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twig befasst sich nur mit dem Rendern von Vorlagen. Sie müssen die Token noch ordnungsgemäß validieren. Meiner Meinung nach bietet die Twig-Strategie mehr Flexibilität und Einfachheit bei gleichzeitiger Wahrung der Möglichkeit maximaler Sicherheit.


CSRF-Token für den einmaligen Gebrauch

Wenn Sie die Sicherheitsanforderung haben, dass jedes CSRF-Token genau einmal verwendet werden darf, wird es nach jeder erfolgreichen Validierung mit der einfachsten Strategie neu generiert. Dadurch wird jedoch jedes vorherige Token ungültig, was sich nicht gut mit Personen vermischt, die mehrere Registerkarten gleichzeitig durchsuchen.

Paragon Initiative Enterprises unterhält eine Anti-CSRF-Bibliothek für diese Eckfälle. Es funktioniert ausschließlich mit Einweg-Token pro Formular. Wenn in den Sitzungsdaten genügend Token gespeichert sind (Standardkonfiguration: 65535), werden die ältesten nicht eingelösten Token zuerst ausgewechselt.

Scott Arciszewski
quelle
schön, aber wie kann man das $ -Token ändern, nachdem der Benutzer das Formular gesendet hat? In Ihrem Fall wird ein Token für die Benutzersitzung verwendet.
Akam
1
Schauen Sie sich genau an, wie github.com/paragonie/anti-csrf implementiert ist. Die Token sind zum Einmalgebrauch bestimmt, speichern jedoch mehrere.
Scott Arciszewski
@ScottArciszewski Was halten Sie davon, einen Nachrichtenauszug aus der Sitzungs-ID mit einem Geheimnis zu generieren und dann den empfangenen CSRF-Token-Auszug mit einem erneuten Hashing der Sitzungs-ID mit meinem vorherigen Geheimnis zu vergleichen? Ich hoffe du verstehst was ich meine.
MNR
1
Ich habe eine Frage zum Überprüfen des CSRF-Tokens. Wenn $ _POST ['token'] leer ist, sollten wir nicht fortfahren, da diese Post-Anfrage ohne das Token gesendet wurde, oder?
Hiroki
1
Weil es in das HTML-Formular übernommen wird und Sie möchten, dass es unvorhersehbar ist, damit Angreifer es nicht einfach fälschen können. Sie implementieren hier wirklich eine Challenge-Response-Authentifizierung, nicht einfach "Ja, dieses Formular ist legitim", da ein Angreifer dies nur fälschen kann.
Scott Arciszewski
24

Sicherheitswarnung : Diesmd5(uniqid(rand(), TRUE)) ist keine sichere Methode zum Generieren von Zufallszahlen. In dieser Antwort finden Sie weitere Informationen und eine Lösung, die einen kryptografisch sicheren Zufallszahlengenerator nutzt.

Sieht so aus, als ob Sie ein anderes mit Ihrem Wenn brauchen.

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}
Datensatz
quelle
11
Hinweis: Ich würde md5(uniqid(rand(), TRUE));Sicherheitskontexten nicht vertrauen .
Scott Arciszewski
2

Die Variable $tokenwird nicht aus der Sitzung abgerufen, wenn sie sich dort befindet

Dani
quelle