Затравка
В 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 степени. Вычисляется бажность генератора так:
mt_srand(2); $a = mt_rand(); mt_srand(3); $b = mt_rand();
define('BUGGY', $a == $b);
Изучив исходник этого эксплойта, ты сможешь более подробно вникнуть в суть
уязвимостей, найденных Стефаном Эссером.
Пока что эксплойты на вышеописанных багах не очень распространены. Я думаю,
это из-за того, что для многих эксплуатация уязвимостей генераторов
псевдослучайных чисел может показатьсячересчур сложной. На самом деле, это не
так. Хакеру я посоветовал бы изучить исходники эксплойтов, ссылки на которые
есть в сноске, и написать на основе полученной информации свои мегапробивные
релизы. А для админов и просто юзеров — обновить свой 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 на основе предсказуемости случайных чисел.
|