So erstellen und verwenden Sie Nonces

74

Ich betreibe eine Website und es gibt ein Punktesystem, das Ihnen Punkte für die Häufigkeit gibt, mit der Sie ein Spiel spielen.

Es verwendet Hashing, um die Integrität der http-Anforderung für die Bewertung zu beweisen, sodass Benutzer nichts ändern können. Wie ich jedoch befürchtet hatte, stellte jemand fest, dass sie es nicht ändern mussten, sondern nur eine hohe Punktzahl erzielen und die duplizieren mussten http Anfrage, Header und alle.

Zuvor war es mir verboten worden, mich vor diesem Angriff zu schützen, da dies als unwahrscheinlich angesehen wurde. Jetzt, wo es passiert ist, kann ich es jedoch. Die http-Anfrage stammt aus einem Flash-Spiel und wird dann von PHP validiert und von PHP in die Datenbank eingegeben.

Ich bin mir ziemlich sicher, dass Nonces das Problem lösen werden, aber ich bin mir nicht ganz sicher, wie ich sie implementieren soll. Was ist eine übliche und sichere Methode zum Einrichten eines Nonce-Systems?

Malfist
quelle
1
Beachten Sie, dass alles, was ein Flash-Spiel auf Ihrem Client tut, von jemandem mit einem Dekompiler / Paket-Sniffer und genügend Zeit repliziert werden kann. So kann jeder Schutz, den Sie hinzufügen, besiegt werden.
CDhowie
Es ist die Zeit, die investiert wird, um fälschlicherweise daran zu arbeiten, dass ich daran interessiert bin, zu wachsen. Ja, sie können es dekompilieren und ersetzen, aber der Hashing-Algorithmus ist kein Geheimnis und schützt nur, weil er ein geheimes Salz enthält. Wenn sie klug sind, können sie es mit einem Regenbogentisch herausfinden.
Malfist
13
Deshalb wurde der Bannhammer erfunden.
Planer
3
@cdhowie - Nicht wirklich, Sie können ein Spiel aufzeichnen und es dann auf dem Server wiedergeben und dann die aus der Wiedergabe gewonnene Punktzahl nehmen, um die Dinge kurz zu machen. Coding Horror allerdings;).
Maurycy
Maurycy: Das würde nicht verhindern, dass dasselbe Spiel immer wieder wiederholt wird. Es würde die Leute auch nicht daran hindern, ihren eigenen Spielaufzeichnungsgenerator zu implementieren.
CDhowie

Antworten:

61

Es ist eigentlich ganz einfach ... Es gibt einige Bibliotheken, die das für Sie erledigen können:

  1. PHP Nonce Bibliothek
  2. OpenID Nonce Library

Oder wenn Sie Ihre eigenen schreiben möchten, ist es ziemlich einfach. Verwenden der WikiPedia-Seite als Ausgangspunkt, Im Pseudocode:

Auf der Serverseite benötigen Sie zwei vom Client aufrufbare Funktionen

getNonce() {
    $id = Identify Request //(either by username, session, or something)
    $nonce = hash('sha512', makeRandomString());
    storeNonce($id, $nonce);
    return $nonce to client;
}

verifyNonce($data, $cnonce, $hash) {
    $id = Identify Request
    $nonce = getNonce($id);  // Fetch the nonce from the last request
    removeNonce($id, $nonce); //Remove the nonce from being used again!
    $testHash = hash('sha512',$nonce . $cnonce . $data);
    return $testHash == $hash;
}

Und auf der Kundenseite:

sendData($data) {
    $nonce = getNonceFromServer();
    $cnonce = hash('sha512', makeRandomString());
    $hash = hash('sha512', $nonce . $cnonce . $data);
    $args = array('data' => $data, 'cnonce' => $cnonce, 'hash' => $hash);
    sendDataToClient($args);
}

Die Funktion muss makeRandomStringwirklich nur eine Zufallszahl oder Zeichenfolge zurückgeben. Je besser die Zufälligkeit, desto besser die Sicherheit ... Beachten Sie auch, dass die Implementierungsdetails von Anforderung zu Anforderung keine Rolle spielen, da sie direkt in eine Hash-Funktion eingespeist werden. Die Client-Version und die Server-Version müssen nicht übereinstimmen. Tatsächlich ist das einzige Bit, das zu 100% übereinstimmen muss, die Hash-Funktion, die in hash('sha512', $nonce . $cnonce . $data);... Hier ist ein Beispiel für eine einigermaßen sichere makeRandomStringFunktion ...

function makeRandomString($bits = 256) {
    $bytes = ceil($bits / 8);
    $return = '';
    for ($i = 0; $i < $bytes; $i++) {
        $return .= chr(mt_rand(0, 255));
    }
    return $return;
}
ircmaxell
quelle
2
Eine gute Antwort, aber das Generieren einer Nonce und das Senden über http (dh Open Wire) verhindert keine Wiederholungsangriffe. Das uralte Problem "Kannst du dem Kunden vertrauen?" Und die Antwort lautet immer "Nein". Wenn Sie keine serverseitige Sitzungslogik haben, ist dies schwierig.
Zebrabox
5
@zebrabox, Nonces würde vor Wiederholungsangriffen schützen. Nonces sind "Zahlen, die nur einmal verwendet werden". Sie führen also nur eine Liste der zuvor verwendeten Nonces und lehnen jeden Versuch ab, eine Nonce zweimal zu verwenden.
Malfist
2
Downvoted, da die verknüpfte Bibliothek FT-Nonce bei der Generierung eindeutiger Schlüssel äußerst unsicher ist.
Corey Ballou
Was ist die Logik des Schaffens $id? Wenn wir eine Sitzung verwenden, müssen Sie die Sitzungs-ID an den Client übergeben und die Sitzungs-ID des Clients an den Server zurücksenden, um zu erkennen, welche Nonce verwendet wird.
TomSawyer
29

Nonces sind eine Dose Würmer.

Nein, eine der Beweggründe für mehrere CAESAR- Einträge war das Entwerfen eines authentifizierten Verschlüsselungsschemas, das vorzugsweise auf einer Stream-Verschlüsselung basiert und gegen die Nicht-Wiederverwendung resistent ist. (Die Wiederverwendung einer Nonce mit AES-CTR zerstört beispielsweise die Vertraulichkeit Ihrer Nachricht in dem Maße, wie ein Programmierstudent im ersten Jahr sie entschlüsseln könnte.)

Es gibt drei Hauptgedankensschulen mit Nonces:

  1. Bei der Kryptografie mit symmetrischen Schlüsseln: Verwenden Sie einen zunehmenden Zähler, und achten Sie darauf, ihn niemals wiederzuverwenden. (Dies bedeutet auch, dass ein separater Zähler für Sender und Empfänger verwendet wird.) Dies erfordert eine zustandsbehaftete Programmierung (dh das Speichern der Nonce irgendwo, damit nicht jede Anforderung bei beginnt 1).
  2. Stateful zufällige Nonces. Generieren Sie eine zufällige Nonce und merken Sie sich diese, um sie später zu validieren. Dies ist die Strategie zur Abwehr von CSRF-Angriffen, die näher an dem liegt, was hier gefordert wird.
  3. Große staatenlose zufällige Nonces. Mit einem sicheren Zufallszahlengenerator können Sie fast garantieren, dass Sie eine Nonce in Ihrem Leben niemals zweimal wiederholen. Dies ist die Strategie, die NaCl für die Verschlüsselung verwendet.

Vor diesem Hintergrund sind folgende Fragen zu stellen:

  1. Welche der oben genannten Denkrichtungen sind für das Problem, das Sie lösen möchten, am relevantesten?
  2. Wie generierst du das Nonce?
  3. Wie validieren Sie die Nonce?

Nonce generieren

Die Antwort auf Frage 2 für zufällige Nonce ist die Verwendung eines CSPRNG. Für PHP-Projekte bedeutet dies Folgendes:

Diese beiden sind moralisch gleichwertig:

$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);

und

$_SESSION['nonce'] []= random_bytes(32);

Nonce validieren

Staatsbürgerlich

Stateful Nonces sind einfach und zu empfehlen:

$found = array_search($nonce, $_SESSION['nonces']);
if (!$found) {
    throw new Exception("Nonce not found! Handle this or the app crashes");
}
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);

Fühlen Sie sich frei, die array_search()durch eine Datenbank oder eine zwischengespeicherte Suche usw. zu ersetzen .

Staatenlos (hier sind Drachen)

Dies ist ein schwer zu lösendes Problem: Sie benötigen eine Möglichkeit, um Wiederholungsangriffe zu verhindern, aber Ihr Server weist nach jeder HTTP-Anforderung eine vollständige Amnesie auf.

Die einzig vernünftige Lösung wäre die Authentifizierung eines Ablaufdatums / einer Ablaufzeit, um den Nutzen von Wiederholungsangriffen zu minimieren. Zum Beispiel:

// Generating a message bearing a nonce
$nonce = random_bytes(32);
$expires = new DateTime('now')
    ->add(new DateInterval('PT01H'));
$message = json_encode([
    'nonce' => base64_encode($nonce),
    'expires' => $expires->format('Y-m-d\TH:i:s')
]);
$publishThis = base64_encode(
    hash_hmac('sha256', $message, $authenticationKey, true) . $message
);

// Validating a message and retrieving the nonce
$decoded = base64_decode($input);
if ($decoded === false) {
    throw new Exception("Encoding error");
}
$mac = mb_substr($decoded, 0, 32, '8bit'); // stored
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true); // calcuated
if (!hash_equals($calc, $mac)) {
    throw new Exception("Invalid MAC");
}
$message = json_decode($message);
$currTime = new DateTime('NOW');
$expireTime = new DateTime($message->expires);
if ($currTime > $expireTime) {
    throw new Exception("Expired token");
}
$nonce = $message->nonce; // Valid (for one hour)

Ein aufmerksamer Beobachter wird feststellen, dass dies im Grunde eine nicht standardkonforme Variante von JSON-Web-Tokens ist .

Scott Arciszewski
quelle
2
Können Sie Wiederholungsangriffe nicht verhindern, indem Sie das Nonce in die Datenbank schreiben, und wenn es "eingelöst" wird, löschen Sie es aus der Datenbank? Nehmen Sie also Ihre "Stateful" -Methode und ersetzen Sie $ _SESSION ['nonces'] durch eine nonces-Tabelle, die das nonce mit einem Benutzernamen (und möglicherweise einer Zeitüberschreitung) verknüpft.
Stephen R
1
Das haben wir getan, aber mit einer Ablaufspalte, damit wir die Lebensdauer begrenzen und abgebrochene SSO-Versuche ausschließen können.
Majestic12
2

Eine Option (die ich im Kommentar erwähnt habe) ist das Aufzeichnen des Gameplays und das Wiedergeben in einer sicheren Umgebung.

Die andere Sache ist, zufällig oder zu bestimmten Zeiten scheinbar unschuldige Daten aufzuzeichnen, die später verwendet werden können, um sie auf dem Server zu validieren (wie plötzlich geht Live von 1% auf 100% oder Punktzahl von 1 bis 1000, was auf Betrug hinweist ). Mit genügend Daten ist es für Betrüger möglicherweise nicht möglich, zu versuchen, sie zu fälschen. Und dann natürlich schweres Verbot implementieren :).

Maurycy
quelle
0

Es ist nicht möglich, Betrug zu verhindern. Sie können es nur schwieriger machen.

Wenn jemand hierher gekommen ist, um nach einer PHP Nonce-Bibliothek zu suchen: Ich empfehle, die erste von ircmaxwell nicht zu verwenden .

Der erste Kommentar auf der Website beschreibt einen Designfehler:

Die Nonce ist für ein bestimmtes Zeitfenster gültig, dh je näher der Benutzer dem Ende dieses Fensters kommt, desto weniger Zeit muss er oder sie das Formular einreichen, möglicherweise weniger als eine Sekunde

Wenn Sie nach einer Möglichkeit suchen, Nonces mit einer genau definierten Lebensdauer zu generieren, schauen Sie sich NonceUtil-PHP an .

Haftungsausschluss: Ich bin der Autor von NonceUtil-PHP

Timo Stamm
quelle
Können Sie bestätigen, dass dieses Dienstprogramm, das vor mehr als einem Jahr erstellt wurde, immer noch eine gute Wahl ist?
Nathan Arthur
5
@ NathanArthur Wenn Sie den Autor überprüfen, ist es Timo selbst. Seine NonceUtil.php verwendet sha-1, überprüft keine vorherigen Nonces und hat eine fest codierte Zeichenfolge für Salt ... Die erste Bibliothek, die in der Antwort von ircmaxwell aufgeführt ist, verwendet md5 und die zweite verwendet einen einfachen Rand, um die Nonce zu erstellen. Dh keiner benutzt sha256 / sha512. Wenn das Ziel der Nonce jedoch nur darin besteht, eine nicht wiederholbare Zeichenfolge zu haben, benötigen Sie nur einen guten Zufallsgenerator (z. B. openssl_random_pseudo_bytes ) und speichern die generierten Zeichenfolgen , um sie vor der Verwendung zu überprüfen.
Armfoot
1
Ich würde NonceUtil nicht empfehlen, wie es derzeit implementiert ist.
Scott Arciszewski
0

Diese sehr einfache Nonce ändert sich alle 1000 Sekunden (16 Minuten) und kann verwendet werden, um XSS zu vermeiden, wenn Sie Daten an und von derselben Anwendung senden. (Wenn Sie sich beispielsweise in einer einseitigen Anwendung befinden, in der Sie Daten über Javascript veröffentlichen. Beachten Sie, dass Sie von der Post- und der Empfangsseite aus auf denselben Seed- und Nonce-Generator zugreifen müssen.)

function makeNonce($seed,$i=0){
    $timestamp = time();
    $q=-3; 
    //The epoch time stamp is truncated by $q chars, 
    //making the algorthim to change evry 1000 seconds
    //using q=-4; will give 10000 seconds= 2 hours 46 minutes usable time

    $TimeReduced=substr($timestamp,0,$q)-$i; 

    //the $seed is a constant string added to the string before hashing.    
    $string=$seed.$TimeReduced;
    $hash=hash('sha1', $string, false);
    return  $hash;
}   

Wenn Sie jedoch nach der vorherigen Nonce suchen, wird der Benutzer nur dann gestört, wenn er im schlimmsten Fall mehr als 16,6 Minuten und im besten Fall 33 Minuten gewartet hat. Wenn Sie $ q = -4 einstellen, hat der Benutzer mindestens 2,7 Stunden Zeit

function checkNonce($nonce,$seed){
//Note that the previous nonce is also checked giving  between 
// useful interval $t: 1*$qInterval < $t < 2* $qInterval where qInterval is the time deterimined by $q: 
//$q=-2: 100 seconds, $q=-3 1000 seconds, $q=-4 10000 seconds, etc.
    if($nonce==$this->makeNonce($seed,0)||$nonce==$this->makeNonce($seed,1))     {
         //handle data here
         return true;
     } else {
         //reject nonce code   
         return false;
     }
}

Der $ seed kann ein beliebiger Funktionsaufruf, Benutzername usw. sein, der im Prozess verwendet wird.

Oleviolin
quelle