Prototype Pollution → RCE через гаджет в EJS
Админ-панель SecureShop рекурсивно сливает JSON-патч предпочтений с in-memory объектом, что позволяет загрязнить Object.prototype. Превью дашборда через EJS читает опции компиляции через цепочку прототипов — это превращает prototype pollution в RCE-гаджет и даёт чтение LAB_FLAG.
hardnodejsPro
Задача
# Prototype Pollution → RCE через gadget в шаблонизаторе
## Контекст
SecureShop — интернет-магазин с админ-панелью. Помимо управления каталогом и пользователями, админам доступна тонкая настройка собственного дашборда: заголовок страницы, текст футера, имя пользователя в приветствии. Бэкенд получает JSON-патч и рекурсивно сливает его с in-memory объектом предпочтений, чтобы клиент мог менять только нужные поля, не перезаписывая остальные.
Когда админ открывает превью дашборда, сервер берёт текущие предпочтения и рендерит фрагмент HTML через шаблонизатор. Текст шаблона зашит в коде — пользователь не контролирует его напрямую.
На первый взгляд поверхность атаки минимальна: значения внутри шаблона корректно экранируются, а тело шаблона недоступно извне.
## Уязвимость
В приложении две независимые проблемы складываются в одну цепочку.
1. **Рекурсивный merge без фильтра ключей.** Функция, объединяющая JSON-патч с предпочтениями, перебирает все ключи входного объекта и пишет их в целевой объект. Если в JSON встречается ключ `__proto__`, JSON-парсер сохраняет его как обычное собственное свойство, а merge на следующей итерации заходит «внутрь» этого ключа — то есть начинает писать прямо в `Object.prototype`. Это классический prototype pollution: любой обычный объект в процессе теперь наследует загрязнённые свойства.
2. **Шаблонизатор читает опции компиляции через прототип.** При вызове рендера с двумя аргументами шаблонизатор внутри обращается к объекту опций и читает из него имя выходной переменной, имя локалов, функцию экранирования и т.п. — обычным доступом по точке. Если объект опций пуст, эти обращения «проваливаются» в `Object.prototype` и возвращают то, что туда положил атакующий. Прочитанные значения шаблонизатор подставляет **дословно** в тело генерируемой JavaScript-функции — то есть атакующий инжектит произвольный JS-код в скомпилированный шаблон.
Каждая из проблем по отдельности кажется безобидной: «слить ключи» и «прочитать опцию по умолчанию» — рутинные операции. Вместе они превращают POST-эндпоинт настроек админа в RCE-примитив: загрязнённый прототип становится **гаджетом**, который шаблонизатор вшивает в свой compiled output.
## Точки входа
- Форма входа `/login` — стандартная авторизация SecureShop. Чтобы достичь админских эндпоинтов, нужен аккаунт с ролью `admin`.
- Эндпоинт `POST /admin/preferences` — принимает JSON-патч предпочтений админа и рекурсивно сливает его с in-memory объектом. Требует роль admin.
- Эндпоинт `GET /admin/dashboard-preview` — рендерит фрагмент дашборда через шаблонизатор, читая текущие предпочтения. Тот же admin guard.
## Цель
Прочитать значение переменной окружения `LAB_FLAG` процесса сервера и получить его в теле HTTP-ответа.
## Что вы научитесь делать
- Распознавать prototype pollution как класс уязвимости и понимать, чем он опаснее обычного «лишнего ключа в объекте».
- Строить gadget chain: pollution → читающая прототип библиотека → RCE-сток.
- Понимать, почему изолированные проверки безопасности (escape пользовательских данных, фиксированный шаблон, проверка ролей) не помогают, если архитектура содержит непротестированный путь от ввода до прототипа.