Один JSON — два разных смысла: как расхождения парсеров открывают доступ к API
Ситуация, когда средство защиты и защищаемый сервис видят одни и те же данные по-разному, — классический сценарий обхода контроля доступа. В разобранном на Хабре примере шлюз проверил JSON и пропустил запрос, а сервис за ним выдал пользователю права администратора. Формально оба компонента отработали корректно: ошибок не возникло, исключений не было. Проблема в том, что каждый из них по-своему интерпретировал содержимое одного и того же тела запроса. Это делает расхождения JSON-парсеров полноценным классом уязвимостей, а не курьёзом реализации.
Подробности
Корень проблемы — в отсутствии единой строгой семантики для ряда конструкций JSON. Спецификация описывает формат, но оставляет пространство для трактовки в нескольких местах, и разные парсеры заполняют его по-разному.
Первое — дублирующиеся ключи. Если в объекте встречается ключ, указанный дважды, одни реализации сохраняют первое значение, другие — последнее. Шлюз, проверяющий права по первому вхождению, и сервис, читающий последнее, увидят разные роли при абсолютно идентичном байте в запросе. Именно так «user» на входе превращается в «admin» на выходе.
Второе — обработка чисел. Большие целые, числа с плавающей точкой, экспоненциальная запись и потеря точности при приведении типов приводят к тому, что идентификатор или сумма, проверенные одним компонентом, воспринимаются другим иначе. Аналогичные расхождения возникают при работе с Unicode-экранированием в ключах: \u0075ser и user для одного парсера одно и то же, для другого — разные ключи.
Третье — нормализация и валидация. Компоненты могут по-разному относиться к лишним пробелам, порядку ключей, нестандартным типам и глубине вложенности. Всё это создаёт зазор между тем, что проверяет защитный механизм, и тем, что реально исполняет бизнес-логика.
Практическая опасность в том, что такие расхождения позволяют обходить авторизацию, подменять идентификаторы объектов и обходить ограничения на массовое присвоение полей (mass assignment). Уязвимость не имеет отдельного CVE, поскольку resides не в конкретном продукте, а в архитектуре взаимодействия компонентов — и потому особенно коварна.
Что делать
Первое и главное — обеспечить единообразие разбора. Все компоненты цепочки обработки запроса должны использовать один и тот же парсер с одинаковыми настройками строгости, либо валидация должна происходить ровно один раз — на входе, с передачей дальше уже проверенной и нормализованной структуры.
Второе — запретить неоднозначные конструкции на уровне валидации. Дублирующиеся ключи, нестандартное Unicode-экранирование в именах полей, избыточная точность чисел и несоответствия схеме должны отклоняться с кодом 400, а не «доинтерпретироваться». Строгая схема (JSON Schema) с additionalProperties: false существенно сокращает поверхность атаки.
Третье — не доверять данным, прошедшим через шлюз, как уже проверенным. Внутренние сервисы должны самостоятельно валидировать критичные поля, особенно связанные с правами доступа и идентификаторами. Принцип нулевого доверия применим и внутри периметра.
Четвёртое — включить в тестирование проверки на «парсер-дифференциал»: отправлять запросы с дублирующимися ключами, эквивалентными Unicode-представлениями и граничными числами, сравнивая поведение шлюза и бэкенда. Регулярный фаззинг JSON-входа помогает выявлять такие расхождения до эксплуатации.
Итог: расхождение в интерпретации JSON — это не баг одного компонента, а разрыв в контракте между ними. Единая строгая валидация, отказ от неоднозначных конструкций и взаимная проверка на всех уровнях закрывают этот класс атак.
Источник: Хабр — Информационная безопасность

