Недавно обнаруженная известным IT Security специалистом Стефаном Эссером уязвимость в интерпретаторе PHP теоретически может затронуть миллионы веб-сайтов, на которых используется PHP<=5.2.5. Тебе интересно, в чем суть уязвимости? В статье я разобрал advisory бага по полочкам.
Баг затрагивает функции генерации псевдослучайных чисел rand() и mt_rand(). Зачастую они используются для создания паролей, сессий, кукисов и других различных конфиденциальных данных пользователя.
Rand() — это просто враппер для библиотеки libc rand(), а mt_rand() — враппер для генератора псевдослучайных чисел Mersenne Twister. Обе функции используют так называемый seed (семя), который можно задавать соответственно функциями srand() и mt_srand(). По дефолту сид представляет собой 32?битный DWORD (2 в 32 степени или 4294967296 комбинаций). Обычно такой длины достаточно, чтобы обеспечить криптографическую стойкость приложения. Ведь для брутфорса пароля, сгенерированного с помощью одной из этих функций, необходимо знать не только сид, но и сгенерированные на его основе числа. Впрочем, существует ряд ситуаций, в которых брутфорс вполне применим...
Затравка
В 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-патч от Стефана Эссера.
Ссылки
Оригинальное advisory Стефана Эссера на тему mt_rand()
MySQL and SQL Column Truncation Vulnerabilities
Wordpress 2.6.1 (SQL Column Truncation) Admin Takeover Exploit
Wordpress 2.5 <= 2.6.1 through phpBB2 Reset Admin Password Exploit
Исследование raz0r’а на тему предсказуемости случайных чисел в mt_rand()
Исследование raz0r’а на тему усечения данных в MySQL
Исследование raz0r’а на тему предсказуемости случайных чисел в rand()
Уязвимости SMF на основе предсказуемости случайных чисел
Комментарии (0)
RSS свернуть / развернутьТолько зарегистрированные и авторизованные пользователи могут оставлять комментарии.