Security
October 1, 2023

Не все "ошибки" одинаково полезны - с точки зрения безопасности.

Всем знакомая ситуация: ввели не валидное значение - получили "ошибку".

Какие могут быть варианты обработок ошибок:

  • Приложение не обрабатывает исключения и выводит "Internal Server Error".
  • Все исключение обрабатываются одной ошибкой "Введите валидное значение".
  • Все варианты выводят человекочетаемую ошибку, а-ля "Значение X не валидно, введите Y".
  • Выводит ошибку из кода вида: "json: cannot unmarshal number into Go value of type requests.CreateDirectoryRequest"
  • В тексте ошибки выводится StackTrace - самый плохой для разработчика случай и отличный для багхантера случай.

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

Дополнительная информация о системе

Мы хотим протестировать веб-приложение. Первым делом нужно собрать о нем информацию: на каком языке программирования написано приложение, что используется в качестве базы данных, какие фреймворки, библиотеки и так далее.
Зачем это нужно? - для каждого ЯП есть свои "экзотические уязвимости".
Узнали версию библиотеки - проверили какие существуют CVE для нее, выставлена ли не безопасная конфигурация по умолчанию. Если библиотека не безопасна по умолчанию, то велика вероятность, что разработчики не прочитали документацию и функционал уязвим.
Здесь нам поможет текст ошибки.Часто разработчики выводят ошибку фреймворка, а не обрабатывают сами все случаи.

Определение ЯП и использование его "фичи":

Ввели не валидное значение, например %00 - получили "Translation missing: ru.errors.generic.nonexistent". Поняли, что это Ruby on Rails. Если проверка, реализована через if request.post? то передав _method=GET ее можно обойти. Например, передав параметры не доступные для редактирования в методе POST, но имеющиеся в контроллере получаем Mass Assignment.

Определили библиотеку и узнали ее не безопасную конфигурацию:

Увидели, что используется JavaScriptObjectDeserializer

Знаем, что тут может быть уязвимость Insecure deserialization, только в случае, если используется TypeResolver:

JavaScriptSerializer jsonSerializer = new JavaScriptSerializer(new SimpleTypeResolver());

Информация о внутренней инфраструктуре

В некоторых случаях метод возвращает StackTrace. Это настоящий подарок для багхантера, потому что StackTrace может содержать секреты: connection string, токены, внутренние хосты и так далее.

Сonnection string в тексте ошибки:

Если приложение не может подключится к базе данных, то вернет connection string.

Что-то пошло не так, попробуйте еще раз. A timeout occured after 30000ms selecting a server using CompositeServerSelector{ Selectors = MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector, LatencyLimitingServerSelector
{ AllowedLatencyRange = 00:00:00.0150000 }
}. Client view of cluster state is { ClusterId : "1", ConnectionMode : "Automatic", Type : "Standalone", State : "Disconnected", Servers : [{ ServerId: "
{ ClusterId : 1, EndPoint : "Unspecified/mongo-test:27017" }
", EndPoint: "Unspecified/mongo-test:27017", State: "Disconnected", Type: "Unknown", HeartbeatException: "MongoDB.Driver.MongoConnectionException: An exception occurred while opening a connection to the server.\n

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

Дисклеймер!
Класть базу даннных, что бы получить строку подключения на багбаунти крайне не рекомендую.

Токен в тексте ошибки - пример с hackerone от CIRCUIT

Слив какого-то access токена :)

Ввели в поле userId=Ic%27 https://api.qiwi.me/social-networks/vk?userId=lc%27

Получили исключение с маскированным access_token:

{"application":"PiggyBox[public] v-1.25.393","error":"https://api.vk.com/method/users.get?access_token=165981de1**********4bfdb3b085c5eb7f61de75e3\u0026fields=photo_200%2Ccity\u0026lang=ru\u0026user_ids=lc%27\u0026v=5.101","trace_id":"9c508a391ae7fbff","uptime":"1h44m31.433579453s"}

Избыточная информация в тексте ошибки

Иногда разработчики стараются добавить в текст ошибки больше информации, что приводит к раскрытию персональных данных.

Информация о пользователях:

Есть система в которой компания может создавать задания исполнителям.
Например, задание "доставка пиццы" для курьеров.
Перебирая contractors_ids можно перебрать ФИО исполнителей других компаний.

Если бы разработчик выводили тут еще и телефон или весь объект contractor, то утечка персональных данных в этой системе стала бы критичной.

Информация от пользователя, которая не должна попадать в логи:

Метод смены пароля часто имеет правила. "Пароль 111 должен быть не короче 8 символов, содержать хотя бы одну заглавную букву и цифру". Текст сообщения содержит ошибку с информацией, которую ввел пользователь.
Так как пользователи добавляют обычно к своему паролю символы, которые требуются в правилах, то логи приложения будут содержать "почти верный пароль пользователя". Отличающийся на 1-2 символа.
В данном случае ошибка должна была быть - "Пароль должен быть не короче 8 символов, содержать хотя бы одну заглавную букву и цифру"

Уязвимости в обработке ошибки

Валидация может содержать уязвимости. Не буду приводить пример с error based sql injection - все о ней знают и хотя встречается чаще всего на CTF, но забывать о ней не стоит.

XXE:

XML - парсеры могут быть узявимы к XXE(XML External Entity). Если разработчики вывели в каком элементе значение было не верным, то можно увидеть результат выполнение XXE и получить в тексте ошибки любые локальные файлы сервера доступные под пользователем под которым запущенно приложение. Иногда оно запущенно из под root. Проверяем можем ли мы получить /etc/shadow и если да, то это root, поздравляю.

Ошибки могут дезинформировать

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

Например, при тестировании на IDOR в методах создания/удаления/изменения сущности - полагаться только на успешный ответ/ошибку не стоит.

Метод возвращает ошибку и выполняется

Метод удаления файла. Пробуем удалить файл

Получаем ошибку "Невозможно удалить файл". Думаете файл действительно не удалился?

Файл был удален.
Почему это происходит разберем на примере построения зиккурата.

Выполнение продолжается после вывода ошибки

if () {
print("Нельзя сотворить здесь")
}
строим зиккурат

Метод должен прекращать выполнение. Один из вариантов - реализация через вызов исключения.

if () {
} else {
return new ExceptionResult("Нельзя сотворить здесь")
}
строим зиккурат

Проверки нет и выводится ошибка от другого действия

Например, проверили, что земля проклята после строительства зуккурата.

строим зиккурат
if (земля проклята) {
прокачиваем зиккурат
} else {
throw new exception("Нельзя сотворить здесь")
}
преобразовываем зиккурат в башню духов

Нужно "до"

if (земля проклята) {
строим зиккурат
} else {
throw new exception("Нельзя сотворить здесь")
}

Где это встречается?

  • Когда в методе много шагов с разными проверками
  • Есть интеграция и ошибку можем получить, когда клиент оборвал соединение, но продолжил выполнение. Например, метод вернул ошибку биллинга, но деньги списались, потому что разработчик забыл передать CancellationToken.

Надо сказать, что оба варианта встречаются редко - чаще метод возвращает 200 success, когда код не был выполнен. Тем не менее такие кейсы встречаются на проде и в багбаунти и бывает полезно убедится, что ошибка стабильна и метод действительно не был выполнен.

В заключение:

Как быть, если ты разработчик/QA и хочешь не допустить перечисленные проблемы?

  • Не выводить StackTrace в текст ошибки
  • StackTrace выводить в систему логирования
  • Не выводить избыточную информацию в текст ошибки
  • Задуматься какую информацию выводите пользователям
  • Искать и исправлять уязвимости :)

Пользуйтесь чек-листом:

Чек-лист "Безопасность обработки ошибок"