Популярнейшая многофункциональная 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. Туда я помещу нового пользователя.
Почти все готово. Теперь включаем в настройках Joomla плагин авторизации через LDAP.
Затем переходим в настройки этого плагина и вбиваем наши данные.
Обрати внимание на параметр Search String
, его я взял из официальной документации по настройке этого плагина на сайте CMS.
Переходим к изучению причин уязвимости. Наш тернистый путь начинается с класса LoginController
.
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
.
58: $app = JFactory::getApplication(); ... 64: $result = $app->login($credentials, array('action' => 'core.login.admin'));
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
, и данные уходят на обработку соответствующим плагинам.
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
.
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. Что ты и можешь наблюдать в сорцах.
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
.
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
.
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 год2990 р. Экономия 1400 рублей! |
1 месяц490 р. 25-30 статей в месяц |
Уже подписан?
Читайте также
Последние новости