NoSQL Injection
NoSQL Injection — это уязвимость, при которой атакующий может вмешиваться в запросы, которые приложение выполняет к базе данных NoSQL. NoSQL Injection может позволить атакующему:
Обойти аутентификацию или механизмы защиты.
Извлечь или изменить данные.
Вызвать отказ в обслуживании.
Выполнить код на сервере.
Базы данных NoSQL хранят и извлекают данные в формате, отличном от традиционных реляционных таблиц SQL. Они используют широкий спектр языков запросов вместо универсального стандарта, такого как SQL, и имеют меньше реляционных ограничений.
Дополнительная информация
Для получения дополнительной информации о базах данных NoSQL и их отличиях от SQL-баз данных см.:
Типы NoSQL Injection
Существует два разных типа NoSQL Injection:
Инъекция синтаксиса (syntax injection) — возникает, когда вы можете нарушить синтаксис запроса NoSQL, что позволяет внедрить собственный полезный нагрузочный фрагмент (payload). Методология похожа на используемую при SQL-инъекции. Однако характер атаки существенно отличается, так как базы данных NoSQL используют различные языки запросов, типы синтаксиса и структуры данных.
Инъекция операторов (operator injection) — возникает, когда вы можете использовать операторы запросов NoSQL для манипулирования запросами.
Инъекция синтаксиса
Вы можете потенциально обнаружить уязвимости NoSQL Injection, пытаясь нарушить синтаксис запроса. Для этого систематически тестируйте каждый ввод, отправляя fuzz-строки (fuzz strings) и специальные символы, которые вызывают ошибку базы данных или иное заметное поведение, если они ввод недостаточно очищен или отфильтрован приложением. Если вы знаете язык API целевой базы данных используйте специальные символы и fuzz-строки, которые релевантны этому языку. В противном случае используйте разнообразные fuzz-строки, нацеленные на несколько языков API.
Выявление инъекции синтаксиса в MongoDB
Рассмотрим приложение-магазин, которое отображает продукты по категориям. Когда пользователь выбирает категорию Fizzy drinks, его браузер запрашивает следующий URL:
Это заставляет приложение отправить JSON-запрос для извлечения соответствующих продуктов из коллекции product в базе данных MongoDB:
Чтобы проверить, может ли ввод быть уязвим, отправьте fuzz-строку в значение параметра category. Пример строки для MongoDB:
Используйте эту fuzz-строку, чтобы сформировать следующую атаку:
Если это вызывает изменение по сравнению с исходным ответом, это может указывать на то, что пользовательский ввод не фильтруется или не очищается должным образом.
Note
Уязвимости NoSQL-Injection могут возникать в самых разных контекстах, и вам нужно соответствующим образом адаптировать свои fuzz-строки. В противном случае вы можете просто вызвать ошибки валидации, из-за которых приложение никогда не выполнит ваш запрос.
В этом примере мы внедряем fuzz-строку через URL, поэтому строка закодирована как URL. В некоторых приложениях вам может потребоваться внедрить полезную нагрузку через свойство JSON. В этом случае эта полезная нагрузка станет:
Определение обрабатываемых символов
Чтобы определить, какие символы интерпретируются приложением как синтаксис, вы можете внедрять отдельные символы. Например, вы можете отправить ', что приведет к следующему запросу MongoDB:
Если это вызывает изменение по сравнению с исходным ответом, это может означать, что символ ' нарушил синтаксис запроса и вызвал синтаксическую ошибку. Вы можете подтвердить это, отправив корректную строку запроса во ввод, например экранировав кавычку:
Подтверждение условного поведения
Обнаружив уязвимость, следующий шаг — определить, можете ли вы влиять на булевы условия с помощью синтаксиса NoSQL.
Для проверки отправьте два запроса: один с ложным условием и один с истинным. Например, вы можете использовать условные выражения ' && 0 && 'x и ' && 1 && 'x следующим образом:
Если приложение ведет себя по-разному, это говорит о том, что ложное условие влияет на логику запроса, а истинное — нет. Это указывает на то, что внедрение такого стиля синтаксиса влияет на серверный запрос.
Переопределение существующих условий
Теперь, когда вы определили, что можете влиять на булевы условия, вы можете попытаться переопределить существующие условия, чтобы эксплуатировать уязвимость. Например, вы можете внедрить JavaScript-условие, которое всегда истинно, такое как '||'1'=='1:
Это приводит к следующему запросу MongoDB:
Поскольку внедренное условие всегда истинно, модифицированный запрос возвращает все элементы. Это позволяет просматривать все продукты в любой категории, включая скрытые или неизвестные категории.
Warning
Будьте осторожны при внедрении условия, которое всегда истинно, в запрос NoSQL. Хотя это может быть безвредно в исходном контексте, в который вы внедряете, приложения часто используют данные из одного запроса в нескольких разных запросах. Если приложение использует их при обновлении или удалении данных, например, это может привести к случайной потере данных.
Вы также можете добавить нулевой символ (null character) после значения категории. MongoDB может игнорировать все символы после нулевого символа. Это означает, что любые дополнительные условия в запросе MongoDB игнорируются. Например, в запросе может быть дополнительное ограничение this.released:
Ограничение this.released == 1 используется для показа только выпущенных продуктов. Для невыпущенных продуктов, по-видимому, this.released == 0.
В этом случае атакующий может сформировать атаку следующим образом:
Это приводит к следующему запросу NoSQL:
Если MongoDB игнорирует все символы после нулевого символа, это убирает требование, чтобы поле released было равно 1. В результате отображаются все продукты в категории fizzy, включая невыпущенные.
Инъекция операторов
Базы данных NoSQL часто используют операторы запросов, которые позволяют задавать условия, которым должны соответствовать данные, чтобы быть включенными в результат запроса. Примеры операторов запросов MongoDB:
$where— сопоставляет документы, удовлетворяющие выражению JavaScript.$ne— сопоставляет все значения, не равные указанному значению.$in— сопоставляет все значения, указанные в массиве.$regex— выбирает документы, где значения соответствуют указанному регулярному выражению.
Вы можете попытаться внедрить операторы запросов для манипулирования запросами NoSQL. Для этого систематически отправляйте разные операторы в различные пользовательские вводы, затем анализируйте ответы на наличие сообщений об ошибках или других изменений.
Отправка операторов запросов
В JSON-сообщениях вы можете вставлять операторы запросов как вложенные объекты. Например, {"username":"wiener"} становится {"username":{"$ne":"invalid"}}.
Для ввода на основе URL вы можете вставлять операторы запросов через параметры URL. Например, username=wiener становится username[$ne]=invalid. Если это не работает, вы можете попробовать следующее:
Преобразовать метод запроса из
GETвPOST.Изменить заголовок
Content-Typeнаapplication/json.Добавить JSON в тело сообщения.
Внедрить операторы запросов в JSON.
Примечание
Вы можете использовать расширение Content Type Converter для автоматического преобразования метода запроса и изменения URL-кодированного запроса POST в JSON.
Выявление инъекции операторов в MongoDB
Рассмотрим уязвимое приложение, которое принимает имя пользователя и пароль в теле запроса POST:
Протестируйте каждый ввод с набором операторов. Например, чтобы проверить, обрабатывает ли ввод имени пользователя оператор запроса, вы можете попробовать следующую инъекцию:
Если оператор $ne применяется, это выполняет запрос ко всем пользователям, у которых имя пользователя не равно invalid.
Если и ввод имени пользователя, и ввод пароля обрабатывают оператор, возможно, удастся обойти аутентификацию с помощью следующей полезной нагрузки:
Этот запрос возвращает все учетные данные входа, где и имя пользователя, и пароль не равны invalid. В результате вы входите в приложение как первый пользователь в коллекции.
Чтобы нацелиться на конкретную учетную запись, вы можете сформировать полезную нагрузку, включающую известное имя пользователя или имя пользователя, которое вы угадали. Например:
Эксплуатация NoSQL Injection для извлечения данных
Во многих базах данных NoSQL некоторые операторы или функции запросов могут выполнять ограниченный JavaScript-код, такие как оператор $where и функция mapReduce() в MongoDB. Это означает, что если уязвимое приложение использует эти операторы или функции, база данных может выполнять JavaScript как часть запроса. Следовательно, вы можете использовать функции JavaScript для извлечения данных из базы.
Чтение данных в MongoDB
Рассмотрим уязвимое приложение, которое позволяет пользователям искать другие зарегистрированные имена пользователей и отображает их роль. Это инициирует запрос к URL:
Это приводит к следующему запросу NoSQL к коллекции users:
Поскольку запрос использует оператор $where, вы можете попытаться внедрить функции JavaScript в этот запрос, чтобы он возвращал конфиденциальные данные. Например, вы можете отправить следующую полезную нагрузку:
Это возвращает первый символ строкового пароля пользователя, позволяя извлекать пароль по символам.
Вы также можете использовать функцию JavaScript match() для извлечения информации. Например, следующая полезная нагрузка позволяет определить, содержит ли пароль цифры:
Идентификация имен полей
Поскольку MongoDB работает с полуструктурированными данными, не требующими фиксированной схемы, вам может потребоваться определить допустимые поля в коллекции, прежде чем вы сможете извлекать данные с помощью инъекции JavaScript.
Например, чтобы определить, содержит ли база данных MongoDB поле password, вы можете отправить следующую полезную нагрузку:
Отправьте полезную нагрузку снова для существующего поля и для несуществующего поля. В этом примере вы знаете, что поле username существует, поэтому вы можете отправить следующие полезные нагрузки:
Если поле password существует, вы ожидаете, что ответ будет идентичен ответу для существующего поля (username), но отличаться от ответа для несуществующего поля (foo). Если вы хотите протестировать разные имена полей, вы можете выполнить атаку словарем (dictionary attack), используя список слов для перебора различных потенциальных имен полей.
Note
В качестве альтернативы вы можете использовать NoSQL-Injection для извлечения имен полей по символам. Это позволяет определить имена полей, не прибегая к угадыванию или атаке словарем. Мы научим вас делать это в следующем разделе.
Эксплуатация NoSQL-Injection для извлечения данных
Даже если исходный запрос не использует операторы, позволяющие выполнять произвольный JavaScript, вы можете попытаться внедрить один из этих операторов самостоятельно. Затем вы можете использовать булевы условия, чтобы определить, исполняет ли приложение какой-либо JavaScript, который вы внедряете через этот оператор.
Внедрение операторов в MongoDB
Рассмотрим уязвимое приложение, которое принимает имя пользователя и пароль в теле запроса POST:
Чтобы проверить, можете ли вы внедрить операторы, вы можете попробовать добавить оператор $where в качестве дополнительного параметра, затем отправить один запрос, где условие оценивается как ложь, и другой — где как истина. Например:
Если между ответами есть разница, это может указывать на то, что выражение JavaScript в разделе $where выполняется.
Извлечение имен полей
Если вы внедрили оператор, позволяющий выполнять JavaScript, вы можете использовать метод keys() для извлечения имен полей данных. Например, вы можете отправить следующую полезную нагрузку:
Это исследует первое поле данных в объекте пользователя и возвращает первый символ имени поля. Данное обстоятельство позволяет извлекать имя поля по символам.
Извлечение данных с использованием операторов
В качестве альтернативы вы можете извлекать данные с помощью операторов, которые не позволяют выполнять JavaScript. Например, вы можете использовать оператор $regex для извлечения данных по символам.
Рассмотрим уязвимое приложение, которое принимает имя пользователя и пароль в теле запроса POST. Например:
Вы можете начать с проверки, обрабатывается ли оператор $regex, следующим образом:
Если ответ на этот запрос отличается от получаемого при отправке неправильного пароля, это указывает на то, что приложение может быть уязвимо. Вы можете использовать оператор $regex для извлечения данных по символам. Например, следующая полезная нагрузка проверяет, начинается ли пароль с a:
Timing based инъекции
Иногда вызов ошибки базы данных не приводит к отличию в ответе приложения. В этой ситуации вы все же можете обнаружить и эксплуатировать уязвимость, используя инъекцию JavaScript для вызова условной задержки по времени.
Чтобы провести инъекцию на основе времени:
Загрузите страницу несколько раз, чтобы определить базовое время загрузки.
Вставьте нагрузку с задержкой (timing-based payload) во ввод. Такая нагрузка вызывает намеренную задержку в ответе при выполнении. Например,
{"$where": "sleep(5000)"}вызывает намеренную задержку в 5000 мс при успешной инъекции.Определите, загружается ли ответ медленнее. Это указывает на успешную инъекцию.
Следующие полезные нагрузки на основе времени вызовут задержку, если пароль начинается с буквы a:
Предотвращение NoSQL Injection
Подход к предотвращению атак NoSQL Injection зависит от конкретной используемой технологии NoSQL. Поэтому рекомендуется изучить документацию по безопасности для выбранной вами базы данных NoSQL. Тем не менее, следующие общие рекомендации также помогут:
Очищайте и валидируйте пользовательский ввод, используя список разрешённых символов.
Вставляйте пользовательский ввод с использованием параметризованных запросов вместо конкатенации пользовательского ввода непосредственно в запрос.
Чтобы предотвратить инъекцию операторов, применяйте список разрешённых ключей.
Last updated