Авторские курсы Михаила Тарасова

NoSQL injection через nested массивы — Mongo любит странные payload’ы с `$[]` и `$where`

NoSQL injection via nested arrays — Mongo likes weird payloads

Слушай, бро, если ты думаешь, что NoSQL — это безопасная альтернатива SQL, у меня для тебя плохие новости. MongoDB и его братья-NoSQL’ники жрут payload’ы ещё охотнее, чем MySQL после ' OR 1=1--. Сегодня разберём, как через nested массивы и операторы $[] с $where можно превратить “безопасный” NoSQL-запрос в твой личный backdoor для дампа базы.
Точка входа
Что вижу: API endpoint, который принимает JSON с массивами или nested объектами, типа:

Или URL-параметры с квадратными скобками в стиле PHP: username$ne=admin&password$regex=.*.
Как поймал:

Альтернатива для массивов:

Чем пахнет: Authentication Bypass 9/10, если бэкенд не валидирует операторы. При работе с массивами через $[] — ещё и Mass Assignment + Privilege Escalation, потому что можешь модифицировать все элементы массива сразу, включая приватные поля.
Че почем
Эксплойт 1: Bypass аутентификации через $ne
Классика жанра. Если приложение строит запрос типа db.users.findOne({username: req.body.username, password: req.body.password}), подсовываем:

Результат: возвращается первый юзер в коллекции (обычно это admin). Почему? Потому что запрос превращается в:

Усиленная версия для таргетирования конкретного аккаунта:

Проверяем список популярных username’ов и байпасим пароль через $ne.
Эксплойт 2: Mass update через $[] — меняем всё
Допустим, есть API для обновления профиля:

Backend использует updateOne с $[] для обновления всех элементов:

Атака: Инжектим оператор через nested path:

Если валидация слабая, запрос превратится в:

Теперь все элементы в tags заменены на admin, плюс добавили поле role с правами суперадмина.
Вариант для nested массивов (когда массив внутри массива):

Если arrayFilters не валидируется, можно засетить статус “passed” всем студентам с оценкой >= 0 (то есть всем).
Эксплойт 3: Blind injection через $where + timing
Самый злобный вариант. $where позволяет выполнять произвольный JavaScript внутри запроса. Если приложение не санитизирует input и использует $where, можно тянуть данные посимвольно.
Setup: Допустим, есть поиск юзеров:

Payload для bypass:

Blind extraction паролей через timing:

Автоматизируем через Python:

Эксплойт 4: Exfiltration через $regex — быстрее, чем timing
Если timing-based атаки слишком медленные, используем $regex для boolean-based blind injection:

Если залогинило — пароль начинается с a. Если нет — пробуем другую букву.
Автоматизация через Burp Intruder:

Для извлечения всего пароля посимвольно:

Обход защиты
Трюк 1: Null byte для игнорирования условий
MongoDB иногда обрубает запрос после null byte (%00):

Трюк 2: PHP array syntax для URL-based injection
PHP автоматически парсит paramkey=value в ассоциативный массив. Используем:

Если backend тупо пихает $_POST в MongoDB query — bypass готов.
Трюк 3: Operator smuggling через Content-Type conversion
Если GET-параметры не работают, конвертируем в POST + JSON:

Используй Burp extension “Content Type Converter” для автоматизации.
Доказательство
Успешный bypass через $ne:

Если в ответе видишь "role": "administrator" после payload’а {"username": {"$ne": null}} — это твой golden ticket на $3k-$10k bounty.

Советы
3 вектора для добивания
1. Ищи $lookup для cross-collection queries: Если endpoint использует aggregation pipeline, можно инжектить $lookup для чтения из других коллекций:

Результат: дампишь коллекцию admin_secrets через легитимный endpoint.
2. Error-based injection через $where: Если приложение показывает ошибки БД, можно экстрактить данные через exceptions:

Ответ вернёт полный объект юзера в ошибке.
3. Fuzzing через wordlist полей: Многие NoSQL базы не требуют схемы. Bruteforce имена полей:

Ищи поля типа isAdmin, role, permissions, apiKey.
План атаки (если зацепка есть)
1. Fingerprinting: Определяем, что используется MongoDB. Признаки: ObjectId в _id (507f1f77bcf86cd799439011), ошибки типа MongoError, headers с X-Powered-By: Express.
2. Тестируем operators: Пробуем $ne, $gt, $regex, $where в каждом input-поле. Используем Burp Intruder с payload list:

3. Bypass auth: Если нашли уязвимый login endpoint — инжектим {"username": {"$ne": null}, "password": {"$ne": null}}. Проверяем response на Set-Cookie или JWT.
4. Escalation через nested arrays: Ищем endpoints с update operations. Тестируем $[] и $identifier для mass assignment. Payload:

5. Data exfiltration: Если есть $where или $regex — автоматизируем извлечение через скрипт. Для timing-based используем sleep(), для boolean-based — $regex.
6. Profit: Если нашли RCE через $where + mapReduce(), можно выполнить системные команды:

Это уже critical-level уязвимость на $15k+.
Бонусный лайфхак: Если MongoDB за WAF’ом, попробуй Unicode-encoding для bypass:

Некоторые WAF’ы не декодят Unicode в JSON body.
Если всё плохо: NoSQL injection не работает, операторы заблокированы — ищи GraphQL endpoints или REST API, которые проксируют в Mongo. Там может быть тот же баг, но через другую точку входа. Или иди учить Rust, бро — там хоть borrow checker спасёт от твоих страданий с десериализацией.

Мои курсы