Предсказываем случайные числа в PHP

Стефан Эссер не перестает меня удивлять: в этот раз в своем блоге он рассказал об особенностях генерации случайных чисел в 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(). Итак, алгоритм эксплоита таков:

  1. В keep-alive соединении делаем запрос для поиска в phpBB2 любой часто встречаемой строки, например `привет`, `сайт`, etc
  2. Этот запрос должен вернуть более 30 результатов, чтобы получилось несколько страниц – в ссылке на следующую страницу phpBB2 выводит случайное число в search_id, которое генерируется так:
    <?php
    mt_srand ((double) microtime() * 1000000);
    $search_id = mt_rand();
    ?>
  3. Проводим бруфторс по найденному случайному числу для определения первоначального сида
  4. Вызываем mt_srand() с найденным сидом и отбрасываем первое случайное число, так как оно является тем самым search_id
  5. Во все еще активном keep-alive соединении отправляем запрос на смену пароля администратора блога
  6. WordPress генерирует случайное число, используя mt_rand(), и отправляет активационный ключ на email админа
  7. В эксплоите генерируем случайное число, которое будет таким же как в WordPress, так как локальный генератор имеет тоже положение
  8. В том же keep-alive соединении переходим по активационной ссылке, что приводит к смене пароля администратора
  9. Пароль генерируется той же функцией, что и активационный ключ – высчитываем пароль
  10. Получаем доступ в администраторскую панель WordPress

Эксплоит для смены пароля администратора в WordPress 2.5 <= 2.6.1 через phpBB2 (WordPress 2.5 <= 2.6.1 through phpBB2 Reset Admin Password Exploit)

Если на удаленном веб-сервере используется PHP <= 5.2.5, то эксплоит необходимо запускать также на PHP <= 5.2.5. Вообще желательно запускать его на той же версии PHP, что и на удаленном веб-сервере.


19 comments:

  1. Digimortal, 29. August 2008, 22:36

    Спасибо. Было очень инетерсно почитать. )

     
  2. Pento, 29. August 2008, 23:38

    Поначалу не предал значения этому посту Стефана, но после твоего поста решил перечитать и более глубоко войти в тему 🙂
    Зачётный пост.

     
  3. Luka Tony, 30. August 2008, 7:40

    Не, ну ты мужик, отвечаю. Как с тобой связаться можно?

     
  4. Raz0r, 31. August 2008, 16:42

    @Digimortal & Pento
    спасибо =)

    @Luka Tony
    под тэгами есть секция контакты =)

     
  5. t0ster, 4. September 2008, 20:26

    Блестяще

     
  6. Makaka, 5. September 2008, 15:22

    WOW
    А можа как нить запретить устанавливать соединения Keep-Alive ?

     
  7.  

    […] пост является продолжением прошлой статьи, в которой шла речь о проблемах генераторов случайных […]

     
  8. Raz0r, 5. September 2008, 23:51

    >А можа как нить запретить устанавливать соединения Keep-Alive ?
    можно, о методах защиты написал отдельный пост:
    http://raz0r.name/articles/magiya-sluchajnyx-chisel-chast-2/

     
  9. Alabaster, 15. September 2008, 11:25

    Здравствуй Raz0r, опрости на моему плохаму русскому язику, но я прочитала твой пост и можна бить что я не понимаю хорошо тебе но мне интересует где и как можна применить этот эксплоит?
    Опрости ещё раз на моему русскому язику, но я уверена что ты мне будешь понимать! (:
    Спосибо!

     
  10. Alabaster, 15. September 2008, 12:16

    Извините Raz0r, я забила написать ещё это что я понимала что этот эксплоит применьуется в phpBB2 форуме которие применуют mod WordPress, если это точно?

     
  11. Raz0r, 15. September 2008, 20:44

    @Alabaster: это эксплоит для WordPress – платформы для ведения блогов; phpBB выступает как необходимое звено, без которого атака на WordPress не удастся
    P.S. if you know English better than Russian feel free to ask me further questions in English (in ICQ plz)

     
  12. Alabaster, 16. September 2008, 0:19

    Спасибо Rаz0r!
    Твой ответ мне бил тельны!
    Ти мне понимал и я тебе также, и теперь я довольная!
    P.S. я из Сербии и мне Русский язик близкий и я люблю говорить по Русский, но не пишем так хорошо (: …..
    Последний раз спасибо Rаz0r!

     
  13.  

    […] которые еще не видели эту статью, думаю, догадываются, о чем там ведется речь. Журнал я покупаю время от времени, в основном, чтобы […]

     
  14. Имя карася так) лёша, 25. December 2008, 13:48

    Чё за оО Разор пишет умные и длинные посты))) Когданиь прочту))

     
  15. 3463463, 30. August 2009, 22:14

    а как насчет такой генерации?
    md5( mt_rand(0,10000) . time() . microtime() )
    возможно угадать,сбрутить?

     
  16. Raz0r, 31. August 2009, 12:22

    Только случайное число, полученное с помощью mt_rand(), но не сам md5, и то только в случае, если используется слабый сид, который должен задаваться где-то ранее в коде

     
  17. Александр, 28. November 2009, 17:47

    А на сайте: http://wm-game.com/game.php такой способ поможет предугадывать выпадение того, или иного числа в рулетке, например?

     
  18. mailbrush, 6. March 2010, 23:10

    Статья понравилась, спасибо. Но возник один вопрос. Я посмотрел эксплоит, и меня заинтересовала ф-ция брутфорса сида. Сид перебирается от 0 до 1000000. Мне кажется, это недостаточно, т.к. я не видел ни одного шестизначного сида. Только девятизначные. Так вот, передав этой функции (search_seed) любое число, я всегда получаю в ответ false, т.к. сид не в диапазоне 0-1000000. Соответственно это не есть эффективным способом брута, т.к. на него уйдет уйма времени. Или я что-то не так делаю?

     
  19. Raz0r, 7. March 2010, 0:35

    Эта функция работает для тех случайных чисел, сид которых задается так:
    mt_srand ((double) microtime() * 1000000);
    Теперь попробуй посмотреть значение выражения, которое в результате попадает в mt_srand() и все станет понятным.

     

Write a comment: