0x_Введение
Практически все знают, что такое SQL-инъекции, практически каждый хоть раз да и находил их, и дампил через них базы с каких-нибудь уже задроченных(или не совсем задроченных) сайтов. На эту тему написано очень много статей, но NoSQL-инъекции не получили такое широкое освещение, по ним значительно меньше информации, большая часть которой предоставлена на английском языке, а ведь MongoDB с которой мы и будем сегодня работать входит в ТОП-10 самых используемых баз данных на 2022 год [1].
Как только объявили конкурс, статья “Вскрываем сайты через кривое API” [2], была одной из первых, там автор на практических примерах показал если тыкнуть так и так, то получится вот так и так, однако там ни слова не было о NoSQL-injection, и о том почему оно все так работает, хотя это ни что иное как NoSQL-injection в его статье, именно эта статья и натолкнула меня на мысль раскрыть данную тему.
1x_Разница между SQL и NoSQL
Существует два основных типа баз данных: реляционные(SQL) и нереляционные(NoSQL).
В реляционных базах данных (MySQL, MariaDB, PostgreSQL, MSSQL) вся информация упорядочена в виде таблиц и столбцов. То есть таблицы и столбцы уже заранее прописаны в базе данных, их названия, тип данных, который они будут хранить, все прописано уже заранее, чтобы получить информацию из реляционных баз данных используется язык SQL, неправильное использование которого и становится причиной появления SQL-инъекций.
В нереляционных базах данных (MongoDB, ElasticSearch, CouchDB, Berkeley DB, MemcacheDB, Redis, Riak) информация хранится немного по-другому. Использование этих БД позволяет улучшить: производительность, масштабируемость и удобство в работе. На картинке выше вы можете видеть 4 вида нереляционных баз данных:
- База типа ключ-значение удобна для хранения кэша или пользовательских сессий.
- Колоночные подходят для хранения логов, аналитических данных и прочего.
- Графовые используются в основном для алгоритмов рекомендации, маршрутизации и т.д.
- Документо-ориентированные базы данных. Эти базы данных уже более универсальны, здесь хранятся документы, документ это набор нескольких пар ключ-значение, данные хранятся в таких стандартах как XML, YAML, JSON. Данные документы группируются в коллекции, в результате чего мы получаем определенную логическую иерархию, коллекция это как таблица в реляционных базах данных.
Тут я не буду описывать особенности каждого типа NoSQL баз данных, я лишь дал примерное описание, что бы вы понимали, где и для чего используется каждый из них. Если вам интересна информация по какому-то определенному типу, вы можете это спокойно нагуглить. И тут возникает вопрос, а на какой конкретный тип баз данных мы будем совершать атаки? Если вы посмотрите повнимательнее на картинку выше, то увидите, что документо-ориентированный тип больше всего похож на реляционные базы данных, тут есть JSON объект в котором хранится определенная информация о пользователе, вот именно с этим типом мы и будем работать. Одним из самых ярких представителей данного типа является MongoDB, хоть конечно и есть другие представители, удобство использование этой базы данных заставляет разработчиков выбирать именно ее.
2x_Использование MongoDB в разработке
Что бы красиво и эффективно ломать, нужно понимать как все построено, все-таки тупым топором не так удобно рубить дерево, как острым. В веб-приложении с использованием SQL-баз данных при авторизации к базе летит примерно такой SQL запрос:
Само собой в адекватном приложении передаваемые параметры проверяются, как-то фильтруются и т.д. это лишь примерно. В то время, как в NoSQL базах данных, данный запрос выглядел бы примерно следующим образом:
Причина возникновения NoSQL-injection, аналогична причине возникновения SQL-injection, плохая фильтрация входных данных. На ютубе можно найти кучу обучающих видео по работе с NodeJS, и почему-то в львиной доле этих видео используется именно MongoDB & Mongoose, горе кодеры насмотревшись этих мануалов, начинают использовать именно эту базу данных для хранения информации о пользователях, даже и не подозревая, что использование кода из этих обучающих видео делает их веб-приложения уязвимыми. Данное поведение может тянуться очень долго и кочевать из одного проекта в другой. В данных приложениях примерно вот такая авторизация.
Обычно логин и пароль просто проверяются на длину и вообще их присутствие в получаемых данных, о фильтрации как в данном примере просто забывают. Код данного приложения вы можете найти в источниках [3]. Код очень простой, даже если вы не ас в NodeJS, то будет достаточно примерно минут 15, чтобы понять, что к чему. Из входных данных берется login и password (req.body.login & req.body.password), они вставляются в JSON-объект, поиск которого совершается в базе, результат данного поиска мы получаем в колбэк функции которая передается в функцию find вторым аргументом, и так мы получаем err и users, если массив users не пустой, то мы получает сообщение о том, что мы авторизовались, если нет, то получаем сообщение о том что логин или пароль неверный. Пост данные отправляемые на сайт не всегда могут быть представлены в виде JSON документа, это может быть обычный raw “login=0xUser&password=pass123”.
Тут может появиться вопрос, а где собственно уязвимость? В чем она заключается? Вот тут мы и подошли к самой сути NoSQL-инъекции. Мы можем видеть, как передается JSON-объект в функцию поиска, сам объект и является критерием поиска в базе данных, подобно SQL-injection, где используются специальные символы для раннего закрытия команды, передаваемой к базе, то есть преобразования критерия поиска, мы также преобразуем JSON-объект, передаваемый к базе данных. Давайте же попробуем обойти авторизацию, и залогиниться под обычным пользователем, не зная его пароль, а потом и узнаем пароль, все это на тестовом веб приложении, которое я собрал выше. Для отправки запросов нашему приложению я буду использовать PostMan, весьма удобное приложение для отправки запросов. Я не буду объяснять, что к чему в PostMan, интерфейс и так юзер-френдли.
Как видите все работает правильно, с верным пассом получаем сообщение о том, что вошли, с неверным пассом соответствующее сообщение о том, что логин или пароль неверный. Теперь давайте же попробуем обойти авторизацию.
Как мы можем видеть, вместо “password” мы передаем JSON-объект (JSON в JSON`е), в данном объекте присутствует оператор “$gt” (Greater than), которому соответствует пустая строка. Обращаясь к базе данных MongoDB, мы как бы говорим найди строку где “login” = “0xUser” и пароль больше чем пустая строка, а поскольку пароль у пользователя точно больше чем пустая строка, данный запрос вернет пользователя из бд, в результате чего, мы и оказывается авторизованными. И также во втором примере присутствует оператор “$exists” равный значению true, то есть если пароль существует вообще. Все существующие операторы есть на сайте MongoDB [4], конкретнее по навигации с боку Reference -> Operators.
Основные операторы которые мы будем использовать при атаке на веб-приложения:
- {$gt: 0}
- {$exists: true}
- {$regex: “тут регулярка”}
Что собственно делает этот скрипт? Он в цикле проходит массив символов, по очереди подставляя каждый в пост данные, а если конкретнее то в регулярку, согласно которой если пароль начинается также как подставляемые данные, то база вернет пользователя, если база возвращает пользователя то мы оказываемся авторизованы, как в примерах выше, по полученному ответу от сервера мы смотрим авторизовались или нет, если авторизовались записывает этот символ и снова бежим по циклу, результат будет примерно такой.
Ну само собой знаки доллара уже лишние и мы можем останавливать скрипт, вот мы и получили пароль пользователя.
3x_А я сейчас покажу, откуда на сайт готовилось нападение
В соседней статье был пример с атакой на два сайта coinbazar и handypick, если мы попробуем атаковать данные сайты этим пейлоудом, то увидим, что ничего не выходит.
На этом моменте вопрос “какого хера?” напрашивается сам собой, тут все очень просто. Если сайт не хавает данное выражение, то это значит, что над паролем проводятся какие-то определенные манипуляции, то есть он хэшируется, я решил зарегать новый аккаунт и посмотреть, что за JSON файл мы получаем в ответ (файл с этим json прикреплен).
Как и ожидалось, пароль хэшируется с помощью bcrypt, а значит стандартный вектор атаки нам просто не подходит, поскольку что бы мы не отправили оно будет завернуто в bcrypt и будет сравниваться с паролем в базе. Однако, как и было описано в соседней статье, мы можем изменить любые данные пользователя благодаря определенному запросу. Возникает вопрос, как так, почему при авторизации мы не можем эксплуатировать уязвимость, а в том месте можем, все просто, если при авторизации с входными данными совершаются манипуляции, то при реквесте на изменение, никаких манипуляций с входными данными нет, и код на этом роуте выглядит примерно следующим образом:
Поскольку там требуется ID пользователя, который нам неизвестен, но рас уж при регистрации нам так свободно возвращают полный JSON документ, то там, где показывается информация о других пользователях, также должен возвращаться JSON документ, мои поиски не продлились долго, стоило только зайти на главную страницу, и мы можем видеть вот такой подгон:
Просто отслеживаем трафик с главной страницы, кому как удобно, я использовал панель разработчика, встроенную в браузере Ctrl+Shift+J и просто вбил в поиск первый логин, который есть на главной странице, вот и результат.
Не, ну это определенно подарок, в запросе мы получаем JSON файл с конкретной информацией по каждому итему в списке на главной странице (файл с этим json прикреплен), вместе с их id в базе, только вот нам надо отсюда не _id и id, которые являются id-шниками итемов на главной, а именно user_id. Теперь просто берем этот user_id и меняем у пользователя пароль, потом логинимся с этим паролем. Запрос в PostMan должен быть примерно следующий:
В пост данных мы указываем user_id из JSON файла, вместе с паролем в формате bcrypt, поскольку именно в таком формате пароли хранятся в базе данных (yvvu!wG6xE!s = $2a$10$wnHh0ZhkGIMshtolQwUKfuQvqTFF0mT9SgZ5yEqP491fETrEGKgOa), в хидерах мы добавляем параметр token, _id, id. _id и id это user_id пользователя, токен я взял из запроса который отправлял из личного кабинета для изменения имени. Изменил я пароль на первом аккаунте, том что на скрине в JSON-документе (Abukoech). Если мы посмотрим на полученный в ответ JSON-объект по ближе, то можем найти информацию о KYC пользователя, вместе с сканом документов. Ну да, ну да, можете еще сканы доков оттуда вытащить и продать кому-нибудь. Этот JSON-объект содержит полную информацию о пользователе, за исключением пароля, то есть по сути, мы можем полностью сдампить базу данных если у нас будет информация о всех id пользователей.
Теперь пробуем авторизоваться в этом аккаунте с помощью пароля который мы отправили. Ужас, нас просит ввести 2FA-код.
Вырубаем эту херню аналогичным образом, что и с паролем. И успешно входим в аккаунт.
Вуаля, вот мы и попали в аккаунт этого сникерса (Black Lives Matter), бабки которого мы можем успешно вывести на свой счет. Но тут копейки, примерно 300 рублей, если пошараебиться так по другим аккаунтам, то цифра там может быть значительно больше.
4x_Заключение
Данная уязвимость по сути своей является очень простой, и существует по причине плохой фильтрации данных. Если при просмотре запросов от какого-либо сайта вы увидите в возвращаемых данных параметр _id в JSON-объекте, то скорее всего вы имеете дело с MongoDB, поскольку в данной базе все записи имеют такой идентификатор. Если поковыряете разные эндпоинты на сайте, то скорее всего найдете уязвимое место. Хоть эта уязвимость и является очень простой, она несет большую опасность и может стать причиной утечки данных с сайта.
5x_ЛИТЕРАТУРА
1. https://db-engines.com/en/ranking
2. https://xss.pro/threads/64297/
3. https://www.sendspace.com/file/hkjku8
4. https://www.mongodb.com/docs/manual/reference/operator/query/
5. https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection
