В PHP 4 и PHP 5 <= 5.2.0 присутствует следующая недоработка: любой seed, вызываемый mt_srand(), либо присваиваемый автоматически, имеет разрядность всего 31 бит, так как последний бит всегда устанавливается равным одному. Таким образом, для брутфорса семени нам нужно перебрать 2147483648 комбинаций. Уже лучше, но все-таки для эксплуатации такого бага времени потратить придется немало. В последующих версиях PHP эту недоработку залатали, но оставили другую. В PHP 4 и PHP <= 5.2.5 всякий раз, когда 26 последних бит становятся равными нулю, seed также принудительно становится равным нулю (либо 1, в зависимости от установки принудительных бит системой). Это правило действует для 32‑битных систем. На 64‑битных системах ситуация чуть лучше — сид просто становится 24‑битным. Принудительная генерация seed
Выше я раскрыл одну сторону бага, а теперь — самое вкусное! Если ты любишь покопаться в сорцах бесплатных PHP-цмсок, то, наверняка, знаешь, что их кодеры очень любят инициализировать генераторы псевдослучайных чисел при помощи функций srand() и mt_srand():
mt_srand(time());
mt_srand((double) microtime() * 100000);
mt_srand((double) microtime() * 1000000);
mt_srand((double) microtime() * 10000000);
Такая инициализация не криптоустойчива, потому что:
1. функция time() не является случайной. Ее значение будет известно хакеру. Даже если админы намеренно установят локальное время сервера ошибочным, — его точное значение всегда будет возвращаться в HTTP-заголовках;
2-4. первое слагаемое (double) microtime() будет равно 0, либо 1, а второе — соответственно, от 100000 до 10000000. В итоге, получаем для брутфорса все то же число: от 100000 до 10000000 значений. При 1000000 значений процесс брутфорса сида займет всего несколько секунд! Keep-alive соединения
Материал был бы бесполезным, если бы не тот факт, что Keep-alive HTTP-соединения всегда обслуживаются одним и тем же процессом на удаленном веб-сервере! Это означает, что seed, сгенерированный единожды на одном домене этого сервера, будет таким же и для другого домена на этом сервере! То есть, если какой-либо php-скрипт выведет сгенерированные случайные числа, мы сможем определить по ним сид, — и остальные случайные числа генерить на его основе! Правило, как ты уже понял, относится не только к одному хосту, но и ко всем хостам на удаленном сервере. Нельзя не заметить, что это действует только для PHP, запущенного как модуль Апача, а вот для cgi генераторы псевдослучайных чисел всегда будут инициализироваться заново. Но cgi, скорее, исключение из правил, так что не будем брать его в расчет. Кстати, Стефан Эссер подсказал здесь хинт. Если ты хостишься на одном сервере с жертвой, то можешь принудительно запустить скрипт на своем хосте с srand(0) или mt_srand(0). Сид у жертвы будет, соответственно, 0 :). От теории к практике
Настало время обобщить все сказанное. Итак, запусти следующий скрипт:
<?php
mt_srand(31337);
print mt_rand()."\n";
print mt_rand()."\n";
print mt_rand()."\n";
print mt_rand();
?>
При каждом выводе mt_rand() тебе будут показаны одинаковые числа, так как seed везде один и тот же. Теперь запусти другой скрипт:
<?php
print rand()."\n";
print rand();
?>
Допустим, ты получил числа 11834 и 2795. Снова запускай данный код, но теперь в качестве сида укажи первое получившееся число:
<?php
srand(11834);
print rand()."\n";
print rand();
?>
В итоге ты получишь числа 2795 и 28744. Обрати внимание на предыдущий результат :). Эту особенность генератора обнаружил raz0r (ссылки на его адвисори смотри в конце статьи). Cross Application Attacks
Некоторые веб-приложения сами инициализируют seed, а затем выводят полученные на его основе псевдослучайные числа конечному пользователю. Пример такого приложения — phpBB2. Вот код из search.php:
mt_srand ((double) microtime() * 1000000);
$search_id = mt_rand();
Проблема в этом примере заключается в том, что количество комбинаций составляет всего 1000000, плюс в html-исходнике страницы мы увидим вывод значения $search_id. Как ты уже понял, зная сгенерированное случайное число, мы, фактически, знаем и seed! Тем более, на сравнение 1000000 результатов работы генератора с полученным $search_id уйдет совсем немного времени. Простор для действий тут очень большой. Можно создать rainbow-таблицы со всего лишь 1000000 значений.
Ситуация верна для PHP 5 => 5.2.1. А в случае с PHP 4 и PHP 5 <= 5.2.0 она становится еще лучше! Для них количество вариантов сокращается почти в два раза, то есть до 2 в 19 степени. Причину я описал в первых абзацах. Ты спросишь, почему же в этом примере утечка сгенерированного числа является проблемой безопасности? Вот почему: Запуск генератора случайных чисел влияет не только на представленный в примере phpBB2, но и на остальные веб-приложения, установленные на этом сервере; Псевдослучайные числа, сгенерированные на основе предыдущего seed, будут предсказуемыми; Остальные приложения на этом же сервере могут создавать пароли, сессии и т.д. на основе полученного ранее seed.
Теперь рассмотрим ситуацию, когда phpBB2 и любимый мной WordPress установлены на одном сервере. Отталкиваясь от полученной выше информации, Стефан описывает такой алгоритм атаки на веб-приложения (Cross Application Attacks): Запускаем keep-alive соединение к поиску phpBB2 и ищем любое часто встречающееся слово, вроде «a», «the» и т.д; Если запрос вернул более 30 результатов поиска, то смотрим html-исходник страницы. В ссылке на следующую страницу форум должен вывести случайное число в параметре search_id, — запоминаем его; Запускаем брутфорс по найденному псевдослучайному числу из search_id для определения изначального seed. Для этого raz0r предлагает функцию:
function search_seed($rand_num) {
$max = 1000000;
for($seed=0;$seed<=$max;$seed++){
mt_srand($seed);
$key = mt_rand();
if($key==$rand_num) return $seed;
}
return false;
}
4. Запускаем mt_srand() с полученным значением seed и отбрасываем первое число — тот самый search_id; В том же keep-alive соединении отправляем запрос на смену пароля админа блога; На основе полученного сида генерируем случайное число для активационного ключа смены пароля, который блог должен был выслать на мыло админа; Снова все в том же keep-alive соединении переходим по сгенерированной эксплойтом активационной ссылке. Это должно привести к смене пароля администратора; Генерируем пароль той же функцией, с помощью которой получили активационный ключ, и заходим в админскую часть WordPress :). Кстати, если на сервере-жертве стоит PHP 4 или PHP 5 <= 5.2.0, то желательно генерировать псевдослучайные числа на той же версии PHP; то же самое относится и к PHP 5 >= 5.2.1. Эксплойт, основанный на этом алгоритме, написал все тот же raz0r. часто встречающееся слово, вроде «a», «the» и т.д; Ссылку смотри ниже. Снова WordPress
Попробуем подойти к описанной уязвимости с другой стороны и рассмотреть последний эксплойт для WordPress, названный Wordpress 2.6.1 (SQL Column Truncation) Admin Takeover Exploit. Алгоритм эксплойта основан сразу на двух глобальных уязвимостях: на, собственно, предсказуемости псевдослучайных чисел и на SQL Column Truncation — усечении данных в MySQL.
Сделаю небольшое отступление и расскажу об этом пресловутом усечении данных в мускуле. Уже известный тебе Стефан Эссер опубликовал в своем блоге очередную advisory, посвященную новой уязвимости. Она связана с особенностями сравнения строк и автоматического усечения данных в MySQL. Известно, что любой столбец в таблице имеет определенную длину. Допустим, существует поле varchar(60) (как в WordPress <= 2.6.1 для логина пользователя). Что будет, если записать в это поле любое значение, которое превысит обозначенные 60 символов? Лишние символы отсекутся! В поле останутся первые 60 символов, которые мы попытались туда записать. Дальше. Если у нас есть поле в базе данных со значением «admin», и мы попытаемся сравнить это значение, например, с «admin » (admin и 2 пробела), то мускул это проделает и скажет, что поля равны. Эта особенность MySQL работает в дефолтной конфигурации, — что открывает новый вектор атаки на веб-приложения!
Подробнее о уязвимости советую прочитать по адресам, указанным в конце статьи. Но вернемся к нашему эксплойту.
Принцип его работы изложен ниже: Регистрируем нового пользователя с логином admin[55 пробелов]x. Далее конечный символ «x» отсекается, и в базе мы получаем пользователя admin с 55 пробелами, что для мускула фактически будет равно просто логину «admin»; Запрашиваем линк сброса пароля на свое мыло и получаем уникальный ключ из параметра key, который был сгенерирован функцией mt_rand(); Сбрасываем пароль администратора с полученным ключом. В итоге, новый пароль уйдет только на мыло админа; На основе полученного ранее ключа ищем сид для вновь сгенерированного пароля. Тут можно сгенерировать rainbow-таблицы для поиска, которые будут весить примерно 4294967296 (строк, возможных значений сида, номер строки=seed) * 20 (количество символов кея для смены пароля) = 85899345920 байт или 80 гигабайт. Для версий PHP 4, PHP 5 <= 5.2.0 и PHP 5 >= 5.2.1 нужно генерировать отдельные таблицы. В эксплойте также есть возможность искать seed и без применения радужных таблиц, но процесс займет очень долгое время. Делается это следующей функцией:
function getseed($resetkey) {
echo "[-] calculating rand seed for $resetkey (this will take a looong time)";
$max = pow(2,(32‑BUGGY));
for($x=0;$x<=$max;$x++) {
$seed = BUGGY ? ($x << 1) + 1 : $x;
mt_srand($seed);
$testkey = wp_generate_password(20,false);
if($testkey==$resetkey) {
echo "o\n"; return $seed;
}
if(!($x % 10000)) echo ".";
}
echo "\n";
return false;
}
Параметр BUGGY — не что иное, как вышеописанный баг, когда 26 последних бит сида становятся равными нулю, то есть число всех значений для перебора будет равным 2 в 31 степени. Вычисляется бажность генератора так:
Изучив исходник этого эксплойта, ты сможешь более подробно вникнуть в суть уязвимостей, найденных Стефаном Эссером.
Пока что эксплойты на вышеописанных багах не очень распространены. Я думаю, это из-за того, что для многих эксплуатация уязвимостей генераторов псевдослучайных чисел может показатьсячересчур сложной. На самом деле, это не так. Хакеру я посоветовал бы изучить исходники эксплойтов, ссылки на которые есть в сноске, и написать на основе полученной информации свои мегапробивные релизы. А для админов и просто юзеров — обновить свой PHP до последней версии и поставить Suhosin-патч от Стефана Эссера.
Good luck!
Ссылки
http://www.suspekt.org/2008/08/17/mt_srand-and-not-sorandom-numbers — оригинальное advisory Стефана Эссера на тему mt_rand() http://www.suspekt.org/2008/08/18/mysql-and-sql-columntruncation-vulnerabilities — MySQL and SQL Column Truncation Vulnerabilities http://milw0rm.com/exploits/6421 — Wordpress 2.6.1 (SQL Column Truncation) Admin Takeover Exploit http://raz0r.name/wp-content/uploads/2008/08/wp1.html — Wordpress 2.5 <= 2.6.1 through phpBB2 Reset Admin Password Exploit http://raz0r.name/articles/predskazyvaem-sluchajnye-chisla-v-php — исследование raz0r’а на тему предсказуемости случайных чисел в mt_rand() http://raz0r.name/vulnerabilities/sql-column-truncation-security — исследование raz0r’а на тему усечения данных в MySQL http://raz0r.name/articles/magiya-sluchajnyx-chisel-chast-2 — исследование raz0r’а на тему предсказуемости случайных чисел в rand() http://raz0r.name/vulnerabilities/uyazvimosti-v-simple-machinesforum — уязвимости SMF на основе предсказуемости случайных чисел.