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?
C'est en fait assez facile à faire ... Il existe des bibliothèques pour le faire pour vous:
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;
}
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
).Donc, dans cet esprit, les principales questions à poser sont:
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+ projetsrandom_bytes()
Ces deux sont moralement équivalents:
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
et
$_SESSION['nonce'] []= random_bytes(32);
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.
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 .
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 :).
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.