web-dev-qa-db-fra.com

Comment créer et utiliser des nonces

Je gère un site Web et il existe un système de notation qui vous donne des points pour le nombre de fois que vous jouez à un jeu.

Il utilise le hachage pour prouver l'intégrité de la demande de notation http afin que les utilisateurs ne puissent rien changer, mais comme je le craignais, quelqu'un a compris qu'ils n'avaient pas besoin de le changer, ils avaient juste besoin d'obtenir un score élevé et de dupliquer le demande http, en-têtes et tout.

Auparavant, il m'était interdit de me protéger contre cette attaque, car elle était considérée comme peu probable. Cependant, maintenant que c'est arrivé, je peux. La requête http provient d'un jeu flash, puis est validée par php et php la saisit dans la base de données.

Je suis presque sûr que les nonces résoudront le problème, mais je ne sais pas exactement comment les implémenter. Qu'est-ce qu'un moyen courant et sûr de configurer un système nonce?

61
Malfist

C'est en fait assez facile à faire ... Il existe des bibliothèques pour le faire pour vous:

  1. Bibliothèque PHP Nonce
  2. Bibliothèque OpenID Nonce

Ou si vous voulez écrire le vôtre, c'est assez simple. Utiliser la page WikiPedia comme point de départ, en pseudo-code:

Côté serveur, vous avez besoin de deux fonctions appelables par le client

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;
}

Et côté client:

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

La fonction makeRandomString a juste besoin de renvoyer un nombre ou une chaîne aléatoire. Plus le caractère aléatoire est bon, meilleure est la sécurité ... Notez également que comme il est directement intégré à une fonction de hachage, les détails de l'implémentation n'ont pas d'importance d'une demande à l'autre. La version du client et la version du serveur n'ont pas besoin de correspondre. En fait, le seul bit qui doit correspondre à 100% est la fonction de hachage utilisée dans hash('sha512', $nonce . $cnonce . $data);... Voici un exemple d'une fonction makeRandomString raisonnablement sécurisée ...

function makeRandomString($bits = 256) {
    $bytes = ceil($bits / 8);
    $return = '';
    for ($i = 0; $i < $bytes; $i++) {
        $return .= chr(mt_Rand(0, 255));
    }
    return $return;
}
61
ircmaxell

Les nonces sont une boîte de vers.

Non, vraiment, l'une des motivations de plusieurs CAESAR entrées était de concevoir un schéma de chiffrement authentifié, de préférence basé sur un chiffrement de flux, résistant à la réutilisation nonce . (La réutilisation d'un nonce avec AES-CTR, par exemple, détruit la confidentialité de votre message dans la mesure où un étudiant en programmation de première année pourrait le décrypter.)

Il existe trois principales écoles de pensée avec nonces:

  1. En cryptographie à clé symétrique: utilisez un compteur croissant, en prenant soin de ne jamais le réutiliser. (Cela signifie également utiliser un compteur séparé pour l'expéditeur et le récepteur.) Cela nécessite une programmation avec état (c'est-à-dire stocker le nonce quelque part afin que chaque demande ne démarre pas à 1).
  2. Nonces aléatoires avec état. Générer un nonce aléatoire puis le mémoriser pour le valider plus tard. C'est la stratégie utilisée pour vaincre les attaques CSRF, qui sonne plus proche de ce qui est demandé ici.
  3. Grands nonces aléatoires sans état. Étant donné un générateur de nombres aléatoires sécurisé, vous pouvez presque garantir de ne jamais répéter un nonce deux fois dans votre vie. Il s'agit de la stratégie utilisée par NaCl pour le chiffrement.

Donc, dans cet esprit, les principales questions à poser sont:

  1. Lesquelles des écoles de pensée ci-dessus sont les plus pertinentes par rapport au problème que vous essayez de résoudre?
  2. Comment générez-vous le nonce?
  3. Comment validez-vous le nonce?

Génération d'un Nonce

La réponse à la question 2 pour tout nonce aléatoire est d'utiliser un CSPRNG. Pour les projets PHP, cela signifie l'un des éléments suivants:

  • random_bytes() pour PHP 7+ projets
  • paragonie/random_compat , a PHP 5 polyfill for random_bytes()
  • ircmaxell/RandomLib , qui est un couteau suisse des utilitaires d'aléatoire que la plupart des projets qui traitent de l'aléatoire (par exemple, réinitialisation du mot de passe du sapin) devraient envisager d'utiliser au lieu de rouler le leur

Ces deux sont moralement équivalents:

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

et

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

Validation d'un nonce

Avec état

Les nonces avec état sont faciles et recommandés:

$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]);

N'hésitez pas à remplacer la array_search() par une base de données ou une recherche memcached, etc.

Apatride (ici les dragons)

Il s'agit d'un problème difficile à résoudre: vous avez besoin d'un moyen pour empêcher les attaques de relecture, mais votre serveur présente une amnésie totale après chaque demande HTTP.

La seule solution sensée serait d'authentifier une date/heure d'expiration pour minimiser l'utilité des attaques par rejeu. Par exemple:

// 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)

Un observateur attentif notera qu'il s'agit essentiellement d'une variante non conforme aux normes de JSON Web Tokens .

21

Une option (que j'ai mentionnée dans le commentaire) consiste à enregistrer le gameplay et à le rejouer dans un environnement sécurisé.

L'autre chose consiste à enregistrer au hasard, ou à des moments précis, des données apparemment innocentes, qui peuvent ensuite être utilisées pour les valider sur le serveur (comme soudainement en direct passe de 1% à 100%, ou un score de 1 à 1000 qui indiquent la triche ). Avec suffisamment de données, il pourrait tout simplement ne pas être possible pour un tricheur d'essayer de truquer. Et puis bien sûr implémenter une interdiction lourde :).

1
Maurycy

Ce nonce très simple change toutes les 1000 secondes (16 minutes) et peut être utilisé pour éviter XSS où vous publiez des données vers et depuis la même application. (Par exemple, si vous êtes dans une application d'une seule page où vous publiez des données via javascript. Notez que vous devez avoir accès au même générateur de semences et de nonce depuis la publication et la réception)

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;
}   

Mais en vérifiant le nonce précédent, l'utilisateur ne sera dérangé que s'il a attendu plus de 16,6 minutes dans le pire des cas et 33 minutes dans le meilleur des cas. La définition de $ q = -4 donnera à l'utilisateur au moins 2,7 heures

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;
     }
}

Le $ seed, pourrait être tout appel de fonction ou nom d'utilisateur, etc. utilisé dans le processus.

0
oleviolin