Сегoдня мы рассмотрим эксплуатацию критической 1day-уязвимости в популярной CMS Joomla, которая прогремела на просторах интернета в конце октября. Речь пойдет об уязвимостях с номерами CVE-2016-8869, CVE-2016-8870 и CVE-2016-9081. Все три происходят из одного кусочка кода, который пять долгих лет томился в недрах фреймворка в ожидании своего часа, чтобы затем вырваться на свободу и принести с собой хаос, взломанные сайты и слезы ни в чем не повинных пользователей этой Joomla. Лишь самые доблестные и смелые разработчики, чьи глаза красны от света мониторов, а клавиатуры завалены хлебными крошками, смогли бросить вызов разбушевавшейся нечисти и возложить ее голову на алтарь фиксов.

WARNING

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.
 

С чего все началось

6 октября 2016 года Дэмис Пальма (Demis Palma) создал топик на Stack Exchange, в котором поинтересовался: а почему, собственно, в Joomla версии 3.6 существуют два метода регистрации пользователей с одинаковым названием register()? Первый находится в контроллере UsersControllerRegistration, а второй — в UsersControllerUser. Дэмис хотел узнать, используется ли где-то метод UsersControllerUser::register(), или это лишь эволюционный анахронизм, оставшийся от старой логики. Его беспокоил тот факт, что, даже если этот метод не используется никаким представлением, он может быть вызван при помощи сформированного запроса. На что получил ответ от девелопера под ником itoctopus, подтвердившего: проблема действительно существует. И направил отчет разработчикам Joomla.

Далее события развивались самым стремительным образом. 18 октября разработчики Joomla принимают репорт Дэмиса, который к тому времени набросал PoC, позволяющий регистрировать пользователя. Он опубликовал заметку на своем сайте, где в общих чертах рассказал о найденной проблеме и мыслях по этому поводу. В этот же день выходит новая версия Joomla 3.6.3, которая все еще содержит уязвимый код.

После этого Давиде Тампеллини (Davide Tampellini) раскручивает баг до состояния регистрации не простого пользователя, а администратора. И уже 21 октября команде безопасности Joomla прилетает новый кейс. В нем речь уже идет о повышении привилегий. В этот же день на сайте Joomla появляется анонс о том, что во вторник, 25 октября, будет выпущена очередная версия с порядковым номером 3.6.3, которая исправляет критическую уязвимость в ядре системы.

25 октября Joomla Security Strike Team находит последнюю проблему, которую создает обнаруженный Дэмисом кусок кода. Затем в главную ветку официального репозитория Joomla пушится коммит от 21 октября с неприметным названием Prepare 3.6.4 Stable Release, который фиксит злосчастный баг.

После этого камин-аута к междусобойчику разработчиков подключаются многочисленные заинтересованные личности — начинают раскручивать уязвимость и готовить сплоиты.

27 октября исследователь Гарри Робертс (Harry Roberts) выкладывает в репозиторий Xiphos Research готовый эксплоит, который может загружать PHP-файл на сервер с уязвимой CMS.

 

Детали

Что ж, с предысторией покончено, переходим к самому интересному — разбору уязвимости. В качестве подопытной версии я установил Joomla 3.6.3, поэтому все номера строк будут актуальны именно для этой версии. А все пути до файлов, которые ты увидишь далее, будут указываться относительно корня установленной CMS.

Благодаря находке Дэмиса Пальмы мы знаем, что есть два метода, которые выполняют регистрацию пользователя в системе. Первый используется CMS и находится в файле /components/com_users/controllers/registration.php:108. Второй (тот, что нам и нужно будет вызвать), обитает в /components/com_users/controllers/user.php:293. Посмотрим на него поближе.

286:    /**
287:     * Method to register a user.
288:     *
289:     * @return  boolean
290:     *
291:     * @since   1.6
292:     */
293:    public function register()
294:    {
295:    JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN'));
...
300:    // Get the form data.
301:    $data = $this->input->post->get('user', array(), 'array');
...
315:    $return = $model->validate($form, $data);
316:
317:    // Check for errors.
318:    if ($return === false)
319:    {
...
345:    // Finish the registration.
346:    $return = $model->register($data);

Здесь я оставил только интересные строки. Полную версию уязвимого метода можно посмотреть в репозитории Joomla.

Разберемся, что происходит при обычной регистрации пользователя: какие данные отправляются и как они обрабатываются. Если регистрация пользователей включена в настройках, то форму можно найти по адресу http://joomla.local/index.php/component/users/?view=registration.

Настройка, отвечающая за разрешение регистрации пользователей
Настройка, отвечающая за разрешение регистрации пользователей

Легитимный запрос на регистрацию пользователя выглядит как на следующем скриншоте.

За работу с пользователями отвечает компонент com_users. Обрати внимание на параметр task в запросе. Он имеет формат $controller.$method. Посмотрим на структуру файлов.

Структура контроллеров компонента com_users
Структура контроллеров компонента com_users

Имена скриптов в папке controllers соответствуют названиям вызываемых контроллеров. Так как в нашем запросе сейчас $controller = "registration", то вызовется файл registration.php и его метод register().

Внимание, вопрос: как передать обработку регистрации в уязвимое место в коде? Ты наверняка уже догадался. Имена уязвимого и настоящего методов совпадают (register), поэтому нам достаточно поменять название вызываемого контроллера. А где у нас находится уязвимый контроллер? Правильно, в файле user.php. Получается $controller = "user". Собираем все вместе и получаем task = user.register. Теперь запрос на регистрацию обрабатывается нужным нам методом.

Попали в уязвимый метод класса UsersControllerUser
Попали в уязвимый метод класса UsersControllerUser

Второе, что нам нужно сделать, — это отправить данные в правильном формате. Тут все просто. Легитимный register() ждет от нас массив под названием jform, в котором мы передаем данные для регистрации — имя, логин, пароль, почту (см. скриншот с запросом).

  • /components/com_users/controllers/registration.php:
      124:    // Get the user data.
      125:    $requestData = $this->input->post->get('jform', array(), 'array');
    

Наш подопечный получает эти данные из массива с именем user.

  • /components/com_users/controllers/user.php:
      301:    // Get the form data.
      302:    $data = $this->input->post->get('user', array(), 'array');
    

Поэтому меняем в запросе имена всех параметров с jfrom на user.

Третий наш шаг — это нахождение валидного токена CSRF, так как без него никакой регистрации не будет.

  • /components/com_users/controllers/user.php:
      296:    JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN'));
    

Он выглядит как хеш MD5, а взять его можно, например, из формы авторизации на сайте /index.php/component/users/?view=login.

CSRF-токен из формы авторизации
CSRF-токен из формы авторизации

Теперь можно создавать пользователей через нужный метод. Если все получилось, то поздравляю — ты только что проэксплуатировал уязвимость CVE-2016-8870 «отсутствующая проверка разрешений на регистрацию новых пользователей».

Вот как она выглядит в «рабочем» методе register() из контроллера UsersControllerRegistration:

  • /components/com_users/controllers/registration.php:
      113:    // If registration is disabled - Redirect to login page.
      114:    if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0)
      115:    {
      116:        $this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));
      117:
      118:        return false;
      119:    }
    

А так в уязвимом:

  • /components/com_users/controllers/user.php:

Ага, никак.

Чтобы понять вторую, гораздо более серьезную проблему, отправим сформированный нами запрос и проследим, как он выполняется на различных участках кода. Вот кусок, который отвечает за проверку отправленных пользователем данных в рабочем методе:

Продолжение статьи доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все статьи на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для статей, опубликованных более двух месяцев назад.


4 комментария

Подпишитесь на ][, чтобы участвовать в обсуждении

Обсуждение этой статьи доступно только нашим подписчикам. Вы можете войти в свой аккаунт или зарегистрироваться и оплатить подписку, чтобы свободно участвовать в обсуждении.

Check Also

Группировка APT28 эксплуатирует недавно исправленную уязвимость в Adobe Flash Player

Эксперты компании Proofpoint обнаружили, что «правительственные» хакеры из группы APT28 (о…