Популярнейшая многофункциональная 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. Если пользователь найден, то система будет отвечать, что пароль неверен, а если пользователя вообще нет, то система известит нас об этом в соответствующем сообщении.

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

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

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

Cтатьи из последних выпусков журнала можно покупать отдельно только через два месяца после публикации. Чтобы читать эту статью, необходимо купить подписку.

Подпишись на журнал «Хакер» по выгодной цене!

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

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

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

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

Check Also

Опубликована подробная информация о проблемах WPA2

Опубликованы подробности об уязвимостях в WPA2, представленных под общим названием KRACK. …