Инъекции и разграничение доступа

Что это такое

Инъекция — это помещение недоверенного ввода туда, где его интерпретируют: в SQL-запрос, команду оболочки, фильтр LDAP, шаблон, — так что ввод читается как инструкции. Нарушение разграничения доступа — это когда система правильно отвечает на вопрос «кто вы?» и затем не задаёт «а позволено ли вам это видеть?». Вместе это два класса, которые передают атакующему данные, на которые он никогда не имел права.

Почему это важно

Это недостатки, превращающие обычную функцию в утечку. Инъекция появляется всякий раз, когда разработчик собирает запрос или команду соединением строк, — это естественно написать и почти всегда неправильно. Разграничение доступа рушится на уровне объекта: проверка есть на странице списка, но нет на эндпоинте детали, поэтому смена числа в URL показывает чужой счёт. И то и другое дёшево предотвратить и дорого обнаружить постфактум.

Ключевые идеи

Никогда не собирайте запрос конкатенацией

Используйте параметризованные запросы или привязку значений в ORM. Тогда ввод передаётся как данные и не может изменить структуру оператора.

// Неверно: ввод становится частью текста запроса
db.query(`SELECT * FROM orders WHERE id = '${req.params.id}'`);

// Верно: драйвер передаёт значение отдельно от оператора
db.query("SELECT * FROM orders WHERE id = $1", [req.params.id]);

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

Спрашивайте «может ли этот пользователь видеть эту строку?» на уровне объекта

Авторизация — для каждого объекта, не для маршрута. Эндпоинт списка, фильтрующий по арендатору, не защищает эндпоинт детали; каждому нужна своя проверка относительно ресурса.

Сломано    GET /invoices/1042   -> загружает счёт 1042, проверки владельца нет
Исправлено GET /invoices/1042   -> загрузить счёт; если invoice.accountId !==
                                   session.accountId -> 404

Возвращайте «не найдено», а не «запрещено», там где само существование записи чувствительно.

Запрещайте по умолчанию, разрешайте явно

Новые эндпоинты должны отказывать при ошибке. Фреймворк, который обслуживает любое действие, пока правило не скажет иначе, сольёт первый же эндпоинт, добавленный в спешке.

Проверяйте ввод на границе, кодируйте в точке использования

Проверка отбрасывает то, что вообще не должно было прийти; экранирование или параметризация защищают место использования значения. Делать только одно — распространённое половинчатое исправление: allow-list формата id не защищает шаблон, который его отрисовывает.

Проверяйте ту авторизацию, которая действительно нужна

Ролей недостаточно. «Любой залогиненный пользователь» — это роль, а не авторизация: вопрос всегда в том, может ли именно этот пользователь выполнить это действие над этим объектом.

Практика

Найдите и исправьте один запрос, собранный конкатенацией

Поищите в кодовой базе строки запросов, собранные соединением, и переведите одну из них на параметризованный вызов.

Готово, когда: запрос больше не содержит интерполяции, а тест со вводом, содержащим символ кавычки, возвращает нормальный результат, а не ошибку.

Проверьте один эндпоинт на авторизацию уровня объекта

Возьмите эндпоинт детали и обратитесь к нему с идентификатором, принадлежащим другому аккаунту, из своей действительной сессии.

Готово, когда: ответ — «не найдено» или ошибка авторизации, а не данные другого пользователя, — либо дыра описана вместе с исправлением.

Частые ошибки

  • Запросы, собранные строками — ввод пользователя меняет структуру оператора; исправить: параметризуйте или используйте привязку в ORM.
  • Авторизация маршрута, но не объекта — смена id в URL открывает запись другого арендатора; исправить: проверяйте владельца при каждой загрузке объекта.
  • Доверие идентификатору от клиента — клиент присылает тот id аккаунта, который хочет; исправить: выводите арендатора из сессии, а не из запроса.
  • Разрешение по умолчанию — новый эндпоинт наследует доступ; исправить: запрещайте по умолчанию и требуйте явного правила.
  • Проверка только на границе — значение всё равно где-то ниже отрисовывается сырым; исправить: кодируйте ещё и в точке использования.

Что читать дальше