NoSQL Injection

NoSQL Injection — это уязвимость, при которой атакующий может вмешиваться в запросы, которые приложение выполняет к базе данных NoSQL. NoSQL Injection может позволить атакующему:

  • Обойти аутентификацию или механизмы защиты.

  • Извлечь или изменить данные.

  • Вызвать отказ в обслуживании.

  • Выполнить код на сервере.

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

nosql-injection-graphic.svg
circle-info

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

Для получения дополнительной информации о базах данных 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-строку, чтобы сформировать следующую атаку:

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

circle-info

Note

Уязвимости NoSQL-Injection могут возникать в самых разных контекстах, и вам нужно соответствующим образом адаптировать свои fuzz-строки. В противном случае вы можете просто вызвать ошибки валидации, из-за которых приложение никогда не выполнит ваш запрос.

В этом примере мы внедряем fuzz-строку через URL, поэтому строка закодирована как URL. В некоторых приложениях вам может потребоваться внедрить полезную нагрузку через свойство JSON. В этом случае эта полезная нагрузка станет:

Определение обрабатываемых символов

Чтобы определить, какие символы интерпретируются приложением как синтаксис, вы можете внедрять отдельные символы. Например, вы можете отправить ', что приведет к следующему запросу MongoDB:

Если это вызывает изменение по сравнению с исходным ответом, это может означать, что символ ' нарушил синтаксис запроса и вызвал синтаксическую ошибку. Вы можете подтвердить это, отправив корректную строку запроса во ввод, например экранировав кавычку:

Подтверждение условного поведения

Обнаружив уязвимость, следующий шаг — определить, можете ли вы влиять на булевы условия с помощью синтаксиса NoSQL.

Для проверки отправьте два запроса: один с ложным условием и один с истинным. Например, вы можете использовать условные выражения ' && 0 && 'x и ' && 1 && 'x следующим образом:

Если приложение ведет себя по-разному, это говорит о том, что ложное условие влияет на логику запроса, а истинное — нет. Это указывает на то, что внедрение такого стиля синтаксиса влияет на серверный запрос.

Переопределение существующих условий

Теперь, когда вы определили, что можете влиять на булевы условия, вы можете попытаться переопределить существующие условия, чтобы эксплуатировать уязвимость. Например, вы можете внедрить JavaScript-условие, которое всегда истинно, такое как '||'1'=='1:

Это приводит к следующему запросу MongoDB:

Поскольку внедренное условие всегда истинно, модифицированный запрос возвращает все элементы. Это позволяет просматривать все продукты в любой категории, включая скрытые или неизвестные категории.

circle-exclamation

Вы также можете добавить нулевой символ (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. Если это не работает, вы можете попробовать следующее:

  1. Преобразовать метод запроса из GET в POST.

  2. Изменить заголовок Content-Type на application/json.

  3. Добавить JSON в тело сообщения.

  4. Внедрить операторы запросов в JSON.

circle-info

Примечание

Вы можете использовать расширение Content Type Converterarrow-up-right для автоматического преобразования метода запроса и изменения 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), используя список слов для перебора различных потенциальных имен полей.

circle-info

Note

В качестве альтернативы вы можете использовать NoSQL-Injection для извлечения имен полей по символам. Это позволяет определить имена полей, не прибегая к угадыванию или атаке словарем. Мы научим вас делать это в следующем разделе.

Эксплуатация NoSQL-Injection для извлечения данных

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

Внедрение операторов в MongoDB

Рассмотрим уязвимое приложение, которое принимает имя пользователя и пароль в теле запроса POST:

Чтобы проверить, можете ли вы внедрить операторы, вы можете попробовать добавить оператор $where в качестве дополнительного параметра, затем отправить один запрос, где условие оценивается как ложь, и другой — где как истина. Например:

Если между ответами есть разница, это может указывать на то, что выражение JavaScript в разделе $where выполняется.

Извлечение имен полей

Если вы внедрили оператор, позволяющий выполнять JavaScript, вы можете использовать метод keys() для извлечения имен полей данных. Например, вы можете отправить следующую полезную нагрузку:

Это исследует первое поле данных в объекте пользователя и возвращает первый символ имени поля. Данное обстоятельство позволяет извлекать имя поля по символам.

Извлечение данных с использованием операторов

В качестве альтернативы вы можете извлекать данные с помощью операторов, которые не позволяют выполнять JavaScript. Например, вы можете использовать оператор $regex для извлечения данных по символам.

Рассмотрим уязвимое приложение, которое принимает имя пользователя и пароль в теле запроса POST. Например:

Вы можете начать с проверки, обрабатывается ли оператор $regex, следующим образом:

Если ответ на этот запрос отличается от получаемого при отправке неправильного пароля, это указывает на то, что приложение может быть уязвимо. Вы можете использовать оператор $regex для извлечения данных по символам. Например, следующая полезная нагрузка проверяет, начинается ли пароль с a:

Timing based инъекции

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

Чтобы провести инъекцию на основе времени:

  1. Загрузите страницу несколько раз, чтобы определить базовое время загрузки.

  2. Вставьте нагрузку с задержкой (timing-based payload) во ввод. Такая нагрузка вызывает намеренную задержку в ответе при выполнении. Например, {"$where": "sleep(5000)"} вызывает намеренную задержку в 5000 мс при успешной инъекции.

  3. Определите, загружается ли ответ медленнее. Это указывает на успешную инъекцию.

Следующие полезные нагрузки на основе времени вызовут задержку, если пароль начинается с буквы a:

Предотвращение NoSQL Injection

Подход к предотвращению атак NoSQL Injection зависит от конкретной используемой технологии NoSQL. Поэтому рекомендуется изучить документацию по безопасности для выбранной вами базы данных NoSQL. Тем не менее, следующие общие рекомендации также помогут:

  • Очищайте и валидируйте пользовательский ввод, используя список разрешённых символов.

  • Вставляйте пользовательский ввод с использованием параметризованных запросов вместо конкатенации пользовательского ввода непосредственно в запрос.

  • Чтобы предотвратить инъекцию операторов, применяйте список разрешённых ключей.

Last updated