Приветствую!
В новой статье разберём атаку LDAP Injection и обойдём аутентификацию web-приложения, используя различные конструкции для изменения запросов в свою пользу. Приступим к разбору кейсов!
Немного теории
Облегчённый протокол доступа к каталогам (Lightweight Directory Access Protocol, LDAP) — это протокол, используемый для доступа к серверам каталогов, таким как Active Directory (AD). Веб-приложения могут использовать LDAP для интеграции с AD или другими службами каталогов в целях аутентификации или получения данных. Если пользовательский ввод вставляется в запросы LDAP без надлежащей очистки, могут возникнуть уязвимости, связанные с внедрением кода в LDAP.
LDAP-инъекция — это атака, при которой злоумышленник вводит специальные LDAP-операторы в поля ввода приложения, чтобы изменить логику запроса, обойти аутентификацию, получить несанкционированный доступ к данным или модифицировать каталог.
Для лучшего понимания как работают инъекции разберём синтаксис LDAP-запросов:
Основные операторы LDAP
- Равенство (=) Пример: (name=Kaylie)
- Приблизительное равенство (~=) Пример: (name~=Kaylie)
- Больше/меньше или равно (>=, <=) Пример: (uid>=10) (uid<=10)
- Любое значение (=*) Пример: (department=*)
- Любое, но за исключением это значение !(=*) Пример: (!(department=*))
Логические операторы LDAP
- И (&( )( )) Пример: (&(name=Kaylie)(title=Manager))
- Или (|( )( )) Пример: (|(name=Kaylie)(title=Manager))
- Нет (!( )) Пример: (!(name=Kaylie))
And фильтры Or поддерживают более двух аргументов. Например, (&(attr1=a)(attr2=b)(attr3=c)(attr4=d))
Операторы True/False
- True (&)
- False (|)
Фильтрация запросов
LDAP поддерживает подстановочный знак «*», поэтому мы можем определять фильтры поиска с подстановочными знаками
- (name=*) Соответствует всем записям, содержащим атрибут name.
- (name=K*) Соответствует всем записям, содержащим атрибут name, который начинается с K.
- (name=*a*) Соответствует всем записям, содержащим атрибут name, в котором есть a.
Атрибуты LDAP
LDAP-атрибуты — это поля/свойства объектов в каталоге (как столбцы в таблице БД). Каждый объект (пользователь, группа, компьютер) имеет набор атрибутов (cn (Common Name), sn (Surname), mail (Основной email), uid (User ID), mobile (Мольный номер) и т.д.). Атрибуты могут быть дефолтными для LDAP и кастомные. Основные можно легко подобрать при наличии уязвимости т.к. они общеизвестны.
LDAP - Обход аутентификации
Сперва нам предоставляют форму авторизации, которая работает через протокол LDAP.
Попробуем зайти под учёткой admin, не зная пароль:
admin:*
Если бы в приложении был пользователь admin, то мы бы зашли т.к. в поле password содержит значение *. То есть, зная синтаксис LDAP-запросов, поиск происходил по всей таблице password пока не нашлось нужное значение пароля. Это говорит о том, что в полях формы авторизации нет фильтрации ввода пользователя и она уязвима к LDAP Injection.
А если пользователь с повышенными правами не с логином admin, а superadmin или administrator или ещё что? По такому же принципу поставим * и перед значением admin и после, если логин содержит это слово где-то в середине. Пароль будет подбираться по такому же принципу, что и раньше.
*admin*:*
А вот и наш флаг! Догадка насчёт логина была верной.
А что если везде внедрить * в двух полях? Принцип будет такой же, как с паролем.
Тогда мы зайдём под первым же найденным пользователем htb-stdnt.
В некоторых случаях веб-приложение может внести звездочку в черный список, что сделает невозможным обход аутентификации с помощью вышеупомянутого метода. К счастью, существует альтернативный метод обхода аутентификации без использования подстановочных знаков.
Например, если мы укажем имя пользователя admin)(|(& и пароль abc), веб-приложение будет использовать следующий поисковый фильтр:
(&(uid=admin)(|(&)(userPassword=abc)))
Всё, что внутри (&(..............), должно быть истинным, чтобы вход был успешным. uid=admin проверяет, что имя пользователя admin. Допустим мы заведомо знаем, что этот логин верный. (|(&)(userPassword=abc)) → это условие OR, внутри которого две части:
- (&) → это пустой AND, который всегда считается истинным.
- (userPassword=abc) → проверка пароля, который мы не знаем и следовательно условие ложное.
В OR достаточно, чтобы хотя бы одно условие было истинным, чтобы мы смогли зайти под admin.
Таким образом, нам нужно указать только верное имя пользователя, чтобы войти в указанную учетную запись, тем самым успешно обойдя аутентификацию без использования символа *.
LDAP — кража данных и слепая эксплуатация
В данном случае внедрим другой атрибут description в LDAP-запрос для раскрытия в нём информации:
username=admin)(|(description=a*&password=invalid)
Так запрос LDAP будет выглядеть следующим образом:
(&(cn=admin)(|(description=a*)(userPassword=invalid)))
Для аутентификации нам нужно, чтобы username и password совпали с записями в AD, то есть давали True. Ну, это понятно. Допустим, логин admin мы знаем, однако пароль для нас — загадка.
За счёт оператора OR внедряем атрибут description, где лежит флаг. И будем перебирать значения флага.
Вот, например, если первая буква атрибута будет верной, то сервер отреагирует не "login failed!", а длинной ошибкой сайта:
username=admin)(|(description=h*&password=invalid)
Узнав первое значение флага, двигаем фильтр на позицию вперёд:
username=admin)(|(description=ht*&password=invalid)
Так мы можем перебрать весь флаг в Intruder (флаг содержит только буквы нижнего регистра и спец. символы), а можем написать эксплойт.
Таким образом можно вытаскивать чувствительные данные из различных атрибутов. Такой приём можно также применить без внедрения атрибута. Например, перебрать значения пароля.
Выводы
LDAP-инъекции — это похожая уязвимость на SQL-инъекции, но про неё знают меньше, потому что LDAP используется реже.
Хотя SQL-инъекции известны многим веб-разработчикам, LDAP-инъекции встречаются не так часто. Из-за этого про них мало кто знает. Но если веб-приложение использует LDAP, то такая уязвимость может там быть.
Защититься от LDAP-инъекций просто — нужно экранировать специальные символы, которые могут сломать запрос. Это значит заменять опасные символы на безопасные коды, чтобы они воспринимались как обычный текст, а не как часть команды.
Например, злоумышленник хочет вставить ")(" чтобы сломать запрос, а получает "\29\28" — для LDAP это просто часть текста, а не команда.
Спасибо за внимание! Не забывайте оценивать моё творчество лайком. Всех благ!