В этой первой части серии публикаций, состоящей из двух частей, мы реконструируем NoSQL-инъекцию и рассмотрим основы ее устранения. Во второй части мы рассмотрим серверный JavaScript и атаки слепого внедрения на базы данных NoSQL.

Если вы неправильно проверяете или не экранируете ввод, управляемый пользователем, вы можете обнаружить, что злоумышленники выполняют динамические запросы к вашим базам данных SQL и NoSQL.

Однако вектор атаки различается из-за различий между этими двумя типами баз данных. Например, следующее не повлияет на базы данных NoSQL (даже если ввод злонамеренных пользователей не был очищен / экранирован):

db.users.findOne({username: username, password: password});

Точно так же, если злоумышленник подозревает, что «admin» действителен для базы данных SQL, он может использовать следующее для поля имени пользователя:

admin’ — 

Эти примеры являются высокоуровневыми иллюстрациями того, как злоумышленник может попытаться изменить исходный запрос SQL. Но поскольку структура языка запросов баз данных NoSQL (таких как MongoDB) отличается, эти запросы не имеют таких же отрицательных эффектов, как в случае с базой данных SQL.

Анатомия внедрения NoSQL

В типичном приложении вы можете рассчитывать получить комбинацию имени пользователя и пароля либо с помощью обработчика формы HTML, либо через вызов AJAX, при котором запрос отправляет информацию в формате JSON. Для веб-приложения ExpressJS вам понадобится следующее промежуточное ПО для последнего типа запросов:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded());

Для аутентификации пользователя маршрут входа в систему POST и запрос, отправленный из браузера в базу данных, будут выглядеть примерно так:

app.post(‘/login’, function(req, res) {
User.find({ username: req.body.username, password: req.body.password }, function(err, users) {
res.status(200).send(users); });
});

Модель User выполняет запрос к базе данных MongoDB на соответствие полям имени пользователя и пароля. Они заселены в req.body как первоклассные граждане. Злоумышленники могут воспользоваться логикой сопоставления аутентификации и использовать операторы MongoDB для возврата действительного объекта user:

{“username”:{“$gt”: “”}, “password”:{“$gt”: “”}}

Поскольку для req.body.username задан объект {«$ gt»: «»} (который не является допустимым MongoDB, работающим с соответствующими документами, в которых заполнено поле имени пользователя), это работает. Вы можете проверить это следующим образом:

curl -X POST -H “Content-Type: application/json” — data ‘{“username”:{“$gt”: “”} , “password”:{“$gt”: “”}}’ http://127.0.0.1:31337/login

Атаки с использованием NoSQL-инъекций могут привести к обходу механизмов аутентификации пользователей, но их также можно использовать для реализации таких угроз, как атаки типа «отказ в обслуживании» (DOS). Например, кто-то может внедрить сложные операторы RegEx / сопоставления, которые приводят к загрузке ЦП, например, к полному сканированию таблицы. Также существуют проблемы, когда регулярное выражение, предоставленное злоумышленником, совпадает со многими записями, предоставляя злоумышленнику конфиденциальную информацию, которую он может впоследствии использовать.

Смягчение атак с использованием инъекций NoSQL

Чтобы смягчить атаки инъекций NoSQL, вы должны либо:

  1. Подтвердите ввод пользователя
  2. Избегайте проблемных записей, предоставленных пользователем

Вы также можете воспользоваться преимуществами библиотек (таких как sequelize), которые предоставляют вам подготовленные операторы, которые вы можете включать в свои запросы.

Давайте посмотрим, как мы можем исправить продемонстрированную выше уязвимость NoSQL-инъекций:

app.post(‘/login’, function(req, res) {
  User.find({ username: String(req.body.username),
  password: String(req.body.password) },
  function(err, users) {
    res.status(200).send(users);
  });
});

Если, однако, наше приложение ожидает, что имя пользователя и пароли будут строками (а мы получаем только данные строкового типа), тогда все в порядке. Однако, если кто-то предоставляет объект, например, object {«$ gt»: «»}, и мы пытаемся преобразовать объект как строку, тогда:

console.log(String({“$gt”: “”}));
// result is: ‘[object Object]’

Есть много разных способов проверить вводимые пользователем данные и подтвердить, что это именно тот тип, которого мы ожидаем. Таким образом, важно отслеживать, как запросы MongoDB выполняются в отношении входных данных, которые они соответствуют.