Этот пост является продолжением прошлой статьи, в которой шла речь о проблемах генераторов случайных чисел в PHP с примером эксплоита для WordPress через уязвимость в phpBB2. В этот раз рассмотрим интересную особенность случайных чисел, сгенерированных с помощью функции rand(), а также способы защиты от подобного рода атак в своих веб-приложениях.
Чем mt_rand() лучше rand()?
Изучая документацию PHP по функциям генерации случайных чисел, я заметил, что разработчики настоятельно рекомендуют использовать mt_rand() вместо rand(), указывая на преимущество в скорости алгоритма Mersenne Twister (в 4 раза быстрее функции rand()). На самом деле, rand() не только медленная, но и соврешенно непригодная функция для криптографических целей по двум причинам.
-
Без передачи аргументов, указывающих пределы значений для генерируемого числа, функция rand() никогда не возвращает число больше 32767. Например, если в веб-приложении в качестве какого-либо идентификатора или ключа используется зашифрованное (например алгоритмом md5) случайное число, полученное с помощью rand(), то определить перебором исходное число не составит особого труда, даже средствами самого интерпретатора.
-
На самом деле, все не так страшно, если бы не еще одна особенность, о которой я еще не находил упоминаний в интернете. Исследуя работу функции 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
Leave a Reply