Опасная уязвимость в популярной библиотеке Sequelize

Привет, Хабр! Данная статья будет интересна тем, кто уже использует библиотеку Sequelize или же только собирается с ней работать. Под катом мы расскажем, чем встроенный функционал operatorAliases может быть вреден и как избежать утечки из собственной базы данных.

Что такое Sequelize, где он используется и для чего?

Sequelize — это ORM-библиотека на Node.js для Postgres, MySQL, MariaDB, SQLite и Microsoft SQL Server, которая осуществляет сопоставление таблиц в бд и отношений между ними с классами. При использовании Sequelize мы можем не писать SQL-запросы, а должны работать с данными как с обычными объектами. Она имеет надежную поддержку транзакций, отношения, активную и отложенную загрузку, репликацию чтения и многое другое.

Что за опция operatorAliases, и в чем заключается ее опасность?

По умолчанию Sequelize использует операторы символов. Использование Sequelize без символьных псевдонимов, конечно, повышает безопасность. И хотя отсутствие строковых псевдонимов делает инъекцию операторов крайне маловероятной, мы всегда должны корректно проверять и очищать данные, вводимые пользователем.

А сама опция operatorAliases позволяет установить, будут ли доступны операторы-псевдонимы. Вот как выглядит пример активации в коде:

Когда все работает правильно

Рассмотрим код демо-приложения. Файл модели user.model.js содержит:

Видно, что в таблице пользователей есть три поля, и они все имеют строковой тип данных.

Файл контроллера auth.controller.js содержит:

В коде используется метод findOne к модели User. И метод findOne возвращает первую строку из базы данных, согласно переданному условию запроса. В данном случае, приложение получает от пользователя данные username и password, применяет их в запрос к таблице users.

Сгенерированный в данном случае запрос будет выглядеть примерно так:
SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = :username AND `users`.`password` = :password LIMIT 1;

Поскольку используется ORM для формирования запросов, банальная SQL-инъекция не сработает. Если в базе не найдется совпадений по пользователю, приложение вернет ошибку User Not found. В случае совпадений вводных данных на уровне базы данных приложение сравнит введенный пароль с паролем, хранящимся в базе, и либо вернет ошибку «Invalid Password!» при несовпадении, либо токен авторизации в случае успеха. Алгоритм проверки вводных данных выбран специально для тестирования уязвимости.

Таблица будет содержать такие данные:



Авторизация работает исправно.

А что же с операторами и псевдонимами?


Псевдонимы обозначаются символом “$”, синтаксис псевдонимов схож с MongoDB. Нам доступны операторы поиска, сравнения и многие другие. Не смотря на строгую типизацию данных в модели, передача данных от пользователя в ORM в формате JSON влечет за собой нормализацию. И тут начинается самое интересное!

Когда беда стучится в DOM

Групповая атака (Batching attack)

Такого вида запрос к приложению инициирует запрос к базе:
Executing (default): SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` IN (‘admin’, ‘more’, ‘much more’) AND `users`.`password` = ‘wrong pass’ LIMIT 1;

При помощи этой атаки можно за один запрос к серверу проверить на валидность много логинов на конкретный пароль и наоборот. Атака работает в последней на декабрь 2020 версии библиотеки (6.3.5) и при выключенной опции operatorsAliases.

Атака преобразования типов данных

Если передать приложению данные вида…

… то, ничего в нашем случае не произойдет. Логическое условие в базу данных хоть и будет правильным, проверка пароля в исследуемом приложении на уровне приложения и сравнение разных типов данных не может вернуть true.

Атака при помощи операторов сравнения

По данным на скришоте будет сгенерирован запрос в базу вида:
SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = ‘admin’ AND `users`.`password` != ‘aaa’ LIMIT 1;

База данных возвращает данные, так как есть совпадение username = admin и пароля, не равного “aaa”. Для того чтобы пройти процесс авторизации полностью, нам необходимо достать пароль.

Атака операторов регулярных выражений и поиска в строке

Получить исходные данные можно, используя псевдонимы операторов поиска $like или операторов для работы с регулярными выражениями $regexp.

Когда символ не сойдется, возникнет ошибка, что пользователь не найден.

Если символ сойдется, будет ошибка о неверном пароле. В базу выполняется запрос вида:
SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = ‘admin’ AND `users`.`password` LIKE ‘E%’ LIMIT 1;

Таким образом, посимвольно можно восстановить данные из таблицы.

Атака сравнения столбцов в таблице

Есть интересный псевдоним $col, который позволяет сравнить поле с полем.

Запрос в базу будет:
SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = ‘admin’ AND `users`.`password` = `aaaaa` LIMIT 1;

Выполнит в базу следующий запрос:
SELECT `id`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = ‘admin’ AND `users`.`password` = `password` LIMIT 1;
Тем самым выполнив логическое условие на уровне базы данных.

Избавляемся от уязвимостей

  • Необходимо установить последнюю версию библиотеки Sequalize, где была убрана поддержка псевдонимов.

    (Источник)
  • Отключить использование псевдонимов, установив значение operatorAliases: false.
  • Тщательно проверять типы и их значения данных от пользователя до их использования в ORM.

Спасибо за интерес!

Let’s block ads! (Why?)

Read More

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *