Недавно обнаруженная известным 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 степени. Причину я описал в первых абзацах. Ты спросишь,
почему же в этом примере утечка сгенерированного числа является проблемой
безопасности? Вот почему:

  1. Запуск генератора случайных чисел влияет не только на представленный в
    примере phpBB2, но и на остальные веб-приложения, установленные на этом
    сервере;
  2. Псевдослучайные числа, сгенерированные на основе предыдущего seed, будут
    предсказуемыми;
  3. Остальные приложения на этом же сервере могут создавать пароли, сессии и
    т.д. на основе полученного ранее seed.

Теперь рассмотрим ситуацию, когда phpBB2 и любимый мной WordPress установлены
на одном сервере. Отталкиваясь от полученной выше информации, Стефан описывает
такой алгоритм атаки на веб-приложения (Cross Application Attacks):

  1. Запускаем keep-alive соединение к поиску phpBB2 и ищем любое часто
    встречающееся слово, вроде «a», «the» и т.д;
  2. Если запрос вернул более 30 результатов поиска, то смотрим html-исходник
    страницы. В ссылке на следующую страницу форум должен вывести случайное
    число в параметре search_id, — запоминаем его;
  3. Запускаем брутфорс по найденному псевдослучайному числу из 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. 4. Запускаем mt_srand() с полученным значением seed и отбрасываем первое
    число — тот самый search_id;
  5. В том же keep-alive соединении отправляем запрос на смену пароля админа
    блога;
  6. На основе полученного сида генерируем случайное число для активационного
    ключа смены пароля, который блог должен был выслать на мыло админа;
  7. Снова все в том же keep-alive соединении переходим по сгенерированной
    эксплойтом активационной ссылке. Это должно привести к смене пароля
    администратора;
  8. Генерируем пароль той же функцией, с помощью которой получили
    активационный ключ, и заходим в админскую часть 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 работает в
дефолтной конфигурации, — что открывает новый вектор атаки на веб-приложения!

Подробнее о уязвимости советую прочитать по адресам, указанным в конце
статьи. Но вернемся к нашему эксплойту.

Принцип его работы изложен ниже:

  1. Регистрируем нового пользователя с логином admin[55 пробелов]x. Далее
    конечный символ «x» отсекается, и в базе мы получаем пользователя admin с 55
    пробелами, что для мускула фактически будет равно просто логину «admin»;
  2. Запрашиваем линк сброса пароля на свое мыло и получаем уникальный ключ
    из параметра key, который был сгенерирован функцией mt_rand();
  3. Сбрасываем пароль администратора с полученным ключом. В итоге, новый
    пароль уйдет только на мыло админа;
  4. На основе полученного ранее ключа ищем сид для вновь сгенерированного
    пароля. Тут можно сгенерировать 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/post/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 на основе предсказуемости случайных чисел.

Оставить мнение

Check Also

Цифровой паноптикум. Настоящее и будущее тотальной слежки за пользователями

Даже если ты тщательно заботишься о защите своих данных, это не даст тебе желаемой приватн…