Как создавать и использовать одноразовые номера - PullRequest
60 голосов
/ 10 ноября 2010

У меня есть веб-сайт, и существует система подсчета очков, которая дает вам очки за количество раз, когда вы играете в игру.

Он использует хеширование, чтобы доказать целостность http-запроса на скоринг, чтобы пользователи не моглиизменить что-либо, однако, как я опасался, это может произойти, кто-то понял, что ему не нужно это менять, ему просто нужно набрать высокий балл и дублировать http-запрос, заголовки и все.

Ранее ябыло запрещено защищать от этого нападения, потому что это считалось маловероятным.Однако теперь, когда это произошло, я могу.Http-запрос исходит из флэш-игры, а затем проверяется php, и php вводит его в базу данных.

Я почти уверен, что nonce решит проблему, но я не совсем уверен, как реализоватьих.Каков общий и безопасный способ настройки одноразовой системы?

Ответы [ 5 ]

60 голосов
/ 10 ноября 2010

На самом деле это довольно легко сделать ... Есть несколько библиотек, которые сделают это для вас:

  1. Библиотека Nonce PHP
  2. Библиотека OpenID Nonce

Или, если вы хотите написать свой собственный, это довольно просто. Использование страницы WikiPedia в качестве отправной точки, в псевдокоде:

На стороне сервера вам нужны две вызываемые клиентом функции

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

А на стороне клиента:

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

Функция makeRandomString действительно просто должна возвращать случайное число или строку. Чем лучше случайность, тем лучше защита ... Также обратите внимание, что, поскольку она передается прямо в хеш-функцию, детали реализации не имеют значения от запроса к запросу. Версия клиента и версия сервера не должны совпадать. Фактически, единственный бит, который должен соответствовать 100%, - это хеш-функция, используемая в hash('sha512', $nonce . $cnonce . $data); ... Вот пример довольно безопасной makeRandomString функции ...

function makeRandomString($bits = 256) {
    $bytes = ceil($bits / 8);
    $return = '';
    for ($i = 0; $i < $bytes; $i++) {
        $return .= chr(mt_rand(0, 255));
    }
    return $return;
}
20 голосов
/ 01 марта 2016

Одноразовые номера - это банка червей.

Нет, действительно, одна из причин для нескольких записей CAESAR заключалась в разработке аутентифицированной схемы шифрования, предпочтительно на основе потокового шифра, чтоустойчив к повторному использованию.(Повторное использование одноразового номера с AES-CTR, например, нарушает конфиденциальность вашего сообщения до такой степени, что студент первого курса может его расшифровать.)

Существует три основных направления мысли с одноразовыми номерами:

  1. В криптографии с симметричным ключом: используйте увеличивающийся счетчик, стараясь никогда не использовать его повторно.(Это также означает использование отдельного счетчика для отправителя и получателя.) Для этого требуется программирование с сохранением состояния (т. Е. Сохранение одноразового номера где-то, чтобы каждый запрос не начинался с 1).
  2. Случайные одноразовые выражения с состоянием.Генерация случайного одноразового номера с последующим запоминанием для проверки позже. Это стратегия, используемая для победы над атаками CSRF, , которая звучит ближе к тому, о чем здесь просят.
  3. Большие случайные одноразовые числа без состояния.Имея безопасный генератор случайных чисел, вы почти гарантируете, что никогда не будете повторять одноразовый номер дважды в своей жизни.Это стратегия, используемая NaCl для шифрования.

Итак, имея в виду, основные вопросы:

  1. Какой изВышеуказанные школы мысли наиболее актуальны для проблемы, которую вы пытаетесь решить?
  2. Как вы генерируете одноразовый номер?
  3. Как вы проверяете одноразовый номер?

Генерация одноразового номера

Ответом на вопрос 2 для любого случайного одноразового номера является использование CSPRNG.Для проектов PHP это означает одно из:

  • random_bytes() для проектов PHP 7+
  • paragonie / random_compat , PHP5 polyfill для random_bytes()
  • ircmaxell / RandomLib , который представляет собой швейцарский армейский нож утилит случайности, который большинство проектов, которые имеют дело со случайностью (например, сброс паролей пихты), должны рассмотреть использование вместо броскаих собственные

Эти два морально эквивалентны:

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

и

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

Проверка одноразового номера

С учетом состояния

Одноразовые выражения с состоянием просты и рекомендуются:

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

Не стесняйтесь заменить array_search() базой данных или поиском в memcached и т. Д.

Без сохранения состояния (здесь будут драконы)

Это сложная проблема, которую нужно решить: вам нужен какой-то способ предотвратить атаки воспроизведения, но ваш сервер имеет полную амнезию после каждого HTTP-запроса.

Единственным разумным решением будет аутентификация даты / времени истеченияминимизировать полезность повторных атак.Например:

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

Внимательный наблюдатель заметит, что это в основном нестандартный вариант JSON Web Tokens .

1 голос
/ 10 ноября 2010

Один из вариантов (который я упомянул в комментарии) - записывать игровой процесс и воспроизводить его в защищенной среде.

Другое дело - случайным образом или в определенные моменты времени записывать некоторые, казалось бы, невинные данные, которые позжебыть использован для проверки его на сервере (например, внезапно в реальном времени идет от 1% до 100%, или оценка от 1 до 1000, которые указывают на чит).При наличии достаточного количества данных мошенник может просто не попытаться подделать его.А потом, конечно, осуществить тяжелый бан :).

0 голосов
/ 09 марта 2019

Этот очень простой одноразовый номер меняется каждые 1000 секунд (16 минут) и может использоваться для избежания XSS, когда вы публикуете данные в одно и то же приложение. (Например, если вы находитесь в одностраничном приложении, в котором вы публикуете данные через javascript. Обратите внимание, что у вас должен быть доступ к одному и тому же генератору семени и одноразовых номеров с поста и получателя)

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

Но, проверив предыдущий одноразовый номер, пользователь будет обеспокоен, только если он подождет более 16,6 минут в худшем случае и 33 минуты в лучшем случае. Установка $ q = -4 даст пользователю не менее 2,7 часов

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

$ seed, может быть любым вызовом функции или именем пользователя и т. Д., Используемым в процессе.

0 голосов
/ 29 апреля 2013

Невозможно предотвратить обман.Вы можете только сделать это более сложным.

Если кто-то пришел сюда в поисках библиотеки Nonce PHP: я рекомендую не использовать первый, предоставленный ircmaxwell .

Первый комментарий на сайте описывает недостаток дизайна:

Одноразовый номер подходит для одного определенного временного окна, т. Е. Чем ближе пользователь подходит к концу этих окон, тем меньше времени ему или ей нужно отправить форму, возможно, менее одной секунды

Если вы ищете способ генерировать одноразовые номера с четко определенным временем жизни, взгляните на NonceUtil-PHP .

Отказ от ответственности: Я являюсьавтор NonceUtil-PHP

...