Магия случайных чисел (часть 2)

Этот пост является продолжением прошлой статьи, в которой шла речь о проблемах генераторов случайных чисел в PHP с примером эксплоита для WordPress через уязвимость в phpBB2. В этот раз рассмотрим интересную особенность случайных чисел, сгенерированных с помощью функции rand(), а также способы защиты от подобного рода атак в своих веб-приложениях.

Чем mt_rand() лучше rand()?

Изучая документацию PHP по функциям генерации случайных чисел, я заметил, что разработчики настоятельно рекомендуют использовать mt_rand() вместо rand(), указывая на преимущество в скорости алгоритма Mersenne Twister (в 4 раза быстрее функции rand()). На самом деле, rand() не только медленная, но и соврешенно непригодная функция для криптографических целей по двум причинам.

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

  2. На самом деле, все не так страшно, если бы не еще одна особенность, о которой я еще не находил упоминаний в интернете. Исследуя работу функции rand(), я обнаружил определенную зависимость чисел между собой. Например, выполним следующий код:

    <?php
    echo rand()."\r\n";
    echo rand();
    ?>

    Допустим, результатом стали числа 11834 и 2795. Снова запускаем данный код, но перед вызовом функций добавим srand(), в качестве сида указав первое полученное число.

    <?php
    srand(11834);
    echo rand()."\r\n";
    echo rand();
    ?>

    Получаем числа 2795 и 28744. Если обратить внимание на прошлый результат, то можно заметить, что число 2795 было получено после 11834, а в этот раз оно как бы поднялось вверх на одну ступень. Иными словами, каждое число, полученное с помощью rand() задается как сид для следующего случайного числа. Т.е. узнав любое случайное число, можно предсказать все последующие без знания первоначального сида, как это необходимо в случае с mt_rand().

Защита

Если рассматривать сугубо внешние с позиции веб-приложения способы защиты от подобных атак, то можно выделить два пункта:

  • Запрет инициирования keep-alive соединений с веб-сервером. На примере Apache вопрос решается путем указания соответствующей директивы в httpd.conf. Главный минус данного способа заключается в том, что браузеры широко применяют keep-alive соединения для одновременной загрузки нескольких страниц или файлов, в частности изображений, – это несомненно скажется на скорости работы пользователей с вашим сайтом. Кроме того, полной безопасности данный способ гарантировать не может, так как для эксплуатации возможной уязвимости может потребоваться всего один запрос к серверу.

  • PHP как CGI. Keep-alive соединения обрабатываются отдельными процессами, если PHP работает посредством интерфейса CGI. Этот факт исключает возможность предугадывания случайных чисел в независимых веб-приложениях, расположенных на одном сервере, так как при каждом запросе будет всегда назначаться новый сид.

  • Suhosin patch. Для борьбы с атаками на генераторы случайных чисел может помочь известное дополнение для PHP, – Suhosin patch, автором которого является Стефан Эссер. Патч игнорирует попытки назначения сида с помощью mt_srand() и srand() в самом веб-приложении и задает собственное первоначальное число с приемлемой длиной. Решение является универсальным и не требует модификации кода веб-приложений.

Тем не менее, все же лучше доверять себе и своему коду, ведь настоящая безопасность начинается именно с этого. В этом случае вариантов защиты гораздо больше – все зависит от вашей фантазии:

  • Использование генератора случайных чисел с внешним источником энтропии. Данный способ идеален для платформ на UNIX, где есть устройство /dev/random (или /dev/urandom), позволяющее в любой момент получить абсолютно случайное число.

  • Использование md5 хэша в качестве сида. Такой способ наиболее часто используется в качественных веб-приложениях: имеется собственная функция для генерации случайных чисел, которая при каждом вызове извлекает md5 хэш из базы данных (или другого источника), модифицирует его, назначает в качестве сида и генерирует случайное число, исходя из этого md5 значения. При этом сид изменяется таким образом, что его невозможно определить, имея полученное из него случайное число (mt_rand() и rand() вообще не используются). Пример простейшего генератора:

    <?php
    function random()
    {
    	global $rand_seed;
    
    	$val = $rand_seed . microtime();
    	$val = md5($val);
    	$rand_seed = md5($rand_seed . $val);
    
    	return substr($val, 4, 16);
    }
    ?>

Вот собственно и все, надеюсь данная статья помогла понять какие уязвимости таят в себе штатные функции PHP для генерации случайных чисел и как их устранить. Безопасного Вам кода!

P.S. Как выяснилось позже особенности rand(), о которых я писал выше, возможны только на win32! В UNIX числа получаются достаточно длинными и не связанными между собой – видимо PHP получает случайные числа из устройства /dev/(u)random


7 comments:

  1.  

    […] not so random numbers * * More information about the behaviour of rand() on win32 (in Russian): * Магия случайных чисел (часть 2) | Raz0r.name – блог о web-безопа

     
  2.  

    […] только для win32. Наверное, автор не заметил мой маленький апдейт статьи =). В целом, статья неплохая. Спасибо Магу за врезку в […]

     
  3. Liar, 27. December 2008, 1:38

    Вот только сегодня читал данную статью в хакере и смотрел видео, потом решил зайти сюда, статья понравилась, так же проверял у себя на компе, чтож блог в избранном у меня, тоже хочу завети для себя блог, с тобой ссылками можно будет объменятся ?

     
  4. Raz0r, 27. December 2008, 13:16

    Можно, стучи в асю =)

     
  5. blackzert, 9. May 2009, 20:37

    Вообще говоря есть разница между /dev/random и /dev/urandom. Первое генерирует действительно случайные числа, которые берутся из пула. Чтение будет приостановлено, до появления новых данных, в случае опустошения пула. Второе же устройство генерирует псевдослучайные последовательности по определенному алгоритму. Сабж предугадывания следующих значений подобных алгоритмов не нов, примером могут служить проблемы в /dev/urandom в системах BSD, обнаруженные как то давно… Посему читаем маны, сводки безопастности и поним, что абсолютно защищенных систем не существует.
    А сабж понравился =)

     
  6. FallDi, 19. July 2012, 12:45

    Добрый день!
    Можете уточнить откуда берется первый $rand_seed в последнем куске кода? И обязательно ли использовать md5(или что-то другое не обратимое) в последующем вычисление $rand_seed. За ссылку на материал по теме так же буду очень признателен.

     
  7. Raz0r, 19. July 2012, 21:03

    $rand_seed берется из БД; использовать также можно, например, sha1(). Писал давно, но если мне не изменяет память, такой способ использовался в phpBB 3.

     

Write a comment: