Популярнейшая многофункциональная CMS Joomla снова с нами. Несколько месяцев назад мы уже разбирали уязвимость в ней, но тогда это была SQL-инъекция, а теперь на повестке более экзотическая штука — LDAP Injection. Уязвимости такого типа встречаются нечасто, тем более в системах управления сайтами, но тем интереснее будет изучить ее.

Уязвимость называется CVE-2017-14596, и найдена она была аж 27 июля 2017 года. А нашел ее Йоханес Дасе из RIPS Technologies GmbH. Уязвимы все версии CMS, начиная с 1.5.0 и заканчивая 3.7.5 включительно. То есть ошибка оставалась в коде необнаруженной в течение восьми лет, до того как ее пофиксили 19 сентября 2017-го.

 

Стенд

Как устанавливать Joomla, я думаю, все в курсе. Чтобы не париться с базовой настройкой сервера, я слепил файл Docker, который ты можешь скачать из моего репозитория. После его запуска тебе останется только пройти по шагам установки CMS.

Теперь дело за LDAP. Мне совсем не хотелось разбираться с настройкой сервера под Linux, поэтому я решил использовать OpenLDAP для Windows и установить все это дело в три клика.

После установки, если ты оставил все параметры по умолчанию, реквизиты для коннекта будут следующими:

User: cn=Manager,dc=maxcrc,dc=com
Password: secret

Для управления сервером я воспользуюсь утилитой LDAP Admin. По умолчанию на сервере уже имеется предустановленный OU (organisation unit) People. Туда я помещу нового пользователя.

Созданный юзер в программе LDAP Admin
Созданный юзер в программе LDAP Admin

Почти все готово. Теперь включаем в настройках Joomla плагин авторизации через LDAP.

Активация плагина LDAP-авторизации
Активация плагина LDAP-авторизации

Затем переходим в настройки этого плагина и вбиваем наши данные.

Настройка LDAP-авторизации
Настройка LDAP-авторизации

Обрати внимание на параметр Search String, его я взял из официальной документации по настройке этого плагина на сайте CMS.

 

Детали уязвимости

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

/administrator/components/com_login/controller.php
17: class LoginController extends JControllerLegacy
...
48:     /**
49:      * Method to log in a user.
50:      *
51:      * @return  void
52:      */
53:     public function login()
54:     {
...
60:         $model = $this->getModel('login');
61:         $credentials = $model->getState('credentials');

Как видно из названия, он отвечает за процесс авторизации пользователей. Данные пользователей, переданные в форме логина, попадают в переменную $credentials.

После этого они передаются в метод login.

/administrator/components/com_login/controller.php
58:         $app = JFactory::getApplication();
...
64:         $result = $app->login($credentials, array('action' => 'core.login.admin'));
/libraries/cms/application/cms.php
019: class JApplicationCms extends JApplicationWeb
...
859:    public function login($credentials, $options = array())
860:    {
861:        // Get the global JAuthentication object.
862:        $authenticate = JAuthentication::getInstance();
863:        $response = $authenticate->authenticate($credentials, $options);

В процессе обработки кредсов выполняется метод authenticate.

Дальше алгоритм работы зависит от активированных плагинов, которые отвечают за авторизацию. Отрабатывает метод onUserAuthenticate, и данные уходят на обработку соответствующим плагинам.

/libraries/joomla/authentication/authentication.php
017: class JAuthentication extends JObject
...
253:    public function authenticate($credentials, $options = array())
254:    {
255:        // Get plugins
256:        $plugins = JPluginHelper::getPlugin('authentication');
...
268:        foreach ($plugins as $plugin)
269:        {
...
283:            // Try to authenticate
284:            $plugin->onUserAuthenticate($credentials, $options, $response);

Так как у нас настроена авторизация через LDAP, то выполнение переходит к классу PlgAuthenticationLdap.

/plugins/authentication/ldap/ldap.php
014: /**
015:  * LDAP Authentication Plugin
016:  *
017:  * @since  1.5
018:  */
019: class PlgAuthenticationLdap extends JPlugin
...
032:    public function onUserAuthenticate($credentials, $options, &$response)
033:    {

В этом плагине имя пользователя попадает в запрос к серверу LDAP, который мы указывали как опцию search_string. Документация Joomla говорит о том, что в строке запроса темплейт [search] напрямую изменяется на текст, переданный в поле login. Что ты и можешь наблюдать в сорцах.

/plugins/authentication/ldap/ldap.php
069:        switch ($auth_method)
070:        {
071:            case 'search':
072:            {
...
086:                    // Search for users DN
087:                    $binddata = $ldap->simple_search(str_replace('[search]', $credentials['username'], $this->params->get('search_string')));

Мы указали uid=[search] в параметре Search String в настройках плагина, так что после замены полученная строка уходит в метод simple_search.

/libraries/vendor/joomla/ldap/src/LdapClient.php
016: class LdapClient
017: {
...
275:    public function simple_search($search)
276:    {
277:        $results = explode(';', $search);
278: 
279:        foreach ($results as $key => $result)
280:        {
281:            $results[$key] = '(' . $result . ')';
282:        }
283: 
284:        return $this->search($results);
285:    }

В качестве заключительного шага сгенерированная строка поиска уходит на LDAP-сервер с помощью search.

/libraries/vendor/joomla/ldap/src/LdapClient.php
298:    public function search(array $filters, $dnoverride = null, array $attributes = array())
299:    {
...
313:        foreach ($filters as $search_filter)
314:        {
315:            $search_result = @ldap_search($resource, $dn, $search_filter, $attributes);

На данный момент у нас на руках LDAP-инъекция. Так как никакой фильтрации входных данных не происходит, мы можем влиять на отправляемую на сервер строку.

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

Разные сообщения об ошибках в зависимости от переданных кредсов
Разные сообщения об ошибках в зависимости от переданных кредсов

Вызывать различные ошибки можно с помощью шаблонов поиска.

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

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

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

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

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


1 комментарий

  1. Anon

    11.10.2017 at 21:45

    А как быть если jQuery is not defined ?

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

Check Also

«Я хотел её только настроить…» Как я искал уязвимости в IP-камерах и нашел их

Новостями про уязвимости в тех или иных моделях IP-камер уже сложно кого-то удивить. Регул…