• Партнер

  • Недавно обнаруженная известным 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 на основе предсказуемости случайных чисел.

    Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии