Стефан Эссер не перестает меня удивлять: в этот раз в своем блоге он рассказал об особенностях генерации случайных чисел в PHP, которые при особых условиях позволяют предугадывать случайные значения. Его находки открывают возможность для реализации совершенно новых атак – Cross Application Attacks, в основе которых лежит неправильное использование веб-приложениями функций для генерации случайных чисел. При этом одно веб-приложение, не имея полноценных уязвимостей, но обладающее погрешностью в вычислении случайных чисел, открывает дверь для реализации атаки на совершенно другое веб-приложение, находящееся на том же сервере. В качестве примера Стефан описал алгоритм проведения атаки на WordPress через дыру в phpBB2. Так как подтверждение его концепции в виде эксплоита отсутствует, я решил провести собственное исследование и написать рабочий PoC. После нескольких дней тестирования на локальном веб-сервере, я пришел к выводу, что уязвимость действительно имеет место, и ее эксплуатация вполне реальна. В результате мной был написан эксплоит для WordPress <= 2.6.1 посредством phpBB2, позволяющий сменить пароль администратора, не имея доступа к его почтовому ящику.
Теория
Прежде чем перейти к непосредственному исследованию уязвимости, необходимо разобраться в способах генерации случайных чисел. Для этих целей в PHP существуют две функции: rand() и mt_rand(). Первая использует библиотеку libc, а вторая является реализацией генератора случайных чисел Mersenne Twister, причем mt_rand() позволяет получить более рандомные числа. Оба алгоритма относятся к детерминированным, т.е. генерируются случайные числа с определенной зависимостью между ними, иначе говоря, числа псевдослучайны. К слову, существует еще один тип генераторов, которые позволяют получить абсолютно случайные числа. Такие генераторы имеют внешний источник энтропии. Например, стандартное устройство в UNIX /dev/random генерирует случайные числа, используя внешний шум драйверов устройств. Своеобразной энтропией для генераторов псведослучайных чисел является первоначальное число или сид (seed), исходя из которого выводятся все последующие случайные числа. Например, такой код будет всегда возвращать одни и те же числа:
<?php mt_srand(1337); echo mt_rand()."\n"; echo mt_rand()."\n"; echo mt_rand(); ?>
При каждом запуске такого скрипта будут всегда возвращены одни и те же числа, так как все они образованы от одного сида. Как видно из примера, задать сид можно с помощью функции mt_srand() (или srand() для rand()), однако делать это вовсе не обязательно – при вызове функции mt_rand() PHP (начиная с версии 4.2.0) самостоятельно задаст первоначальное число. По умолчанию в качестве сида, как для rand(), так и для mt_rand(), PHP задает 32 битный dword (или 4294967295 – максимальное значение для типа данных integer). Как отмечает Стефан, для реализации криптографических операций этого недостаточно. Однако в большинстве случаев такое первоначальное число способно обеспечить достаточную безопасность, даже несмотря на то, что веб-приложения продолжают использовать rand() и mt_rand() для генерации паролей, активационных ссылок, cookie для автоматического входа и идентификаторов сессии. Тем не менее, при особых условиях подбор сида становится вполне осуществимым или даже совсем не нужным.
Ошибки в реализации
В PHP <= 5.2.5 существует ряд ошибок в реализации алгоритмов генерации, в частности в механизме автоматического назначения сида, что приводит к существенному сокращению его длины. Это относится для тех случаев, когда веб-приложение не задает собственный сид, а доверяет данную операцию интерпретатору. Когда же веб-приложение самостоятельно назначает первоначальное число, то это еще хуже! Популярными способами назначения сида являются:
<?php mt_srand(time()); mt_srand((double) microtime() * 100000); // max value 100K mt_srand((double) microtime() * 1000000); //max value 1M mt_srand((double) microtime() * 10000000); //max value 10M ?>
Так как time() не является случайным, то первый способ является самым опасным. Остальные также дают плохую защиту, так как брутфорс сида по первому сгенерированному случайному числу, займет небольшое количество времени (например для (double) microtime() * 1000000 – всего несколько секунд).
Keep-alive
На самом деле ошибки в реализации не так важны, как тот факт, что HTTP соединения, установленные как Keep-alive, обслуживаются одним и тем же процессом на удаленном веб-сервере. Это означает, что положение генератора случайных чисел будет тем же! Например, если какое-либо веб-приложение допускает вывод случайного числа, то, воспользовавшись предварительно сгенерированными таблицами, мы сможем определить сид, и впоследствии по нему предугадать все последующие числа. К тому же состояние генератора является общим ресурсом для всех виртуальных хостов на сервере. Однако это справедливо только для тех веб-серверов, где PHP используется как модуль Apache (mod_php) – в случае с CGI или fastcgi генератор случайных чисел всегда будет перезапускаться.
Эксплоит
Реализация атаки на веб-приложение, использующее для создания секретных идентификаторов генератор случайных чисел, возможна в случае, если:
- в атакуемом веб-приложении или в веб-приложении, располагающемся на том же физическом сервере, присутствует вывод данных, позволяющих определить положение генератора случайных чисел;
- брутфорс начального числа для генератора занимает относительно небольшой промежуток времени, иначе keep-alive соединение будет разорвано веб-сервером по достижении максимального тайм-аута.
Идеальной платформой, удовлетворяющей данные условия, является phpBB2. Этот форум самостоятельно задает сид и при этом выводит значение первого случайного числа в HTML. Несмотря на то, что в phpBB2 рандомное число генерируется для идентификации поискового запроса, это дает возможность проведения атак на те веб-приложения, располагающиеся на всех виртуальных хостах данного сервера, которые используют случайные числа для генерации секретных данных, например нового пароля администратора. К такому веб-приложению относится WordPress, так как эта платформа для ведения блогов генерирует активационный ключ для смены пароля и собственно сам пароль, используя mt_rand(). Итак, алгоритм эксплоита таков:
- В keep-alive соединении делаем запрос для поиска в phpBB2 любой часто встречаемой строки, например `привет`, `сайт`, etc
- Этот запрос должен вернуть более 30 результатов, чтобы получилось несколько страниц – в ссылке на следующую страницу phpBB2 выводит случайное число в search_id, которое генерируется так:
<?php mt_srand ((double) microtime() * 1000000); $search_id = mt_rand(); ?>
- Проводим бруфторс по найденному случайному числу для определения первоначального сида
- Вызываем mt_srand() с найденным сидом и отбрасываем первое случайное число, так как оно является тем самым search_id
- Во все еще активном keep-alive соединении отправляем запрос на смену пароля администратора блога
- WordPress генерирует случайное число, используя mt_rand(), и отправляет активационный ключ на email админа
- В эксплоите генерируем случайное число, которое будет таким же как в WordPress, так как локальный генератор имеет тоже положение
- В том же keep-alive соединении переходим по активационной ссылке, что приводит к смене пароля администратора
- Пароль генерируется той же функцией, что и активационный ключ – высчитываем пароль
- Получаем доступ в администраторскую панель WordPress
Если на удаленном веб-сервере используется PHP <= 5.2.5, то эксплоит необходимо запускать также на PHP <= 5.2.5. Вообще желательно запускать его на той же версии PHP, что и на удаленном веб-сервере.
Leave a Reply