flawopen.com/Уязвимости/Загрязнение прототипа JavaScript
Загрязнение прототипа JavaScript
Представьте мастерскую, где каждое новое устройство сверяется с общей книгой правил, если собственных настроек недостаточно. Если сотрудник запишет туда правило, заданное злоумышленником, другие устройства могут принять его за значение по умолчанию. При загрязнении прототипа небезопасная динамическая запись может изменить прототип объекта, иногда Object.prototype. Эффект не обязательно глобален: последствия возникают, только если код приложения позднее доверяет унаследованному свойству.
__proto__, или путь constructor.prototype, ведущий к небезопасной записиMap или словари без прототипаОсновные понятия и термины подсистемы
- Object.prototype
- Общий прототип, наследуемый обычными объектами. Его загрязнение может затронуть многие объекты, но не все объекты JavaScript и не все realm.
- __proto__
- Устаревший accessor обычных объектов для чтения или изменения прототипа. В тексте JSON такая запись изначально является обычным именем свойства.
- Рекурсивное глубокое объединение
- Функция, которая обходит вложенные ключи и записывает их в другой объект. Она опасна, если недоверенные ключи выбирают свойства прототипа или небезопасные пути.
- CWE-1321
- Недостаточно контролируемое изменение атрибутов прототипа объекта. Слабость — неконтролируемая мутация; дальнейшие последствия зависят от доступного поведения приложения.
Пошаговая схема атаки
Недоверенные свойства на входе
Endpoint принимает ключи объекта, заданные атакующим. Само чтение JSON с собственным свойством __proto__ не изменяет прототип.
Небезопасная динамическая запись
Рекурсивное объединение или setter пути следует этим ключам и выполняет динамическую запись без проверки каждого сегмента.
Изменение прототипа цели или общего прототипа
В зависимости от цели и порядка записи операция меняет прототип цели или достигает общего прототипа, например Object.prototype. Это разные результаты.
Gadget определяет последствия
Позднее gadget читает унаследованное значение в чувствительной операции. Последствия зависят от gadget; нельзя заранее утверждать обход аутентификации или выполнение кода.
Механизм и первопричина
Поиск свойств в JavaScript проходит по цепочке прототипов. Уязвимое объединение или setter пути может позволить недоверенным ключам, например __proto__ или constructor.prototype, направить запись в прототип. Собственное поле __proto__, созданное JSON.parse, само по себе не является загрязнением: другой код должен обработать его небезопасным присваиванием. Изменение прототипа цели затрагивает эту цель; изменение Object.prototype затрагивает обычные объекты, которые его наследуют. Для последствий нужен отдельный gadget, использующий унаследованное свойство.
// УЯЗВИМО: ключи атакующего достигают рекурсивной записи
function merge(target, source) {
for (const key in source) {
const value = source[key];
if (value && typeof value === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], value);
} else {
target[key] = value;
}
}
return target;
}
// JSON.parse создаёт собственное поле __proto__; риск возникает при небезопасном объединении.
const input = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, input);
// ЗАЩИЩЕНО: копировать только поля, разрешённые этим endpoint
const PROFILE_KEYS = new Set(['displayName', 'theme']);
function parseProfile(input) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('profile must be an object');
}
const profile = Object.create(null);
for (const key of Object.keys(input)) {
if (!PROFILE_KEYS.has(key)) continue;
const value = input[key];
if (typeof value !== 'string' || value.length > 80) {
throw new TypeError('invalid profile field');
}
profile[key] = value;
}
return profile;
}
Последовательность атаки и эксплуатации
- 1Входные данные: запрос содержит неожиданный ключ, например
{"__proto__": {"admin": true}}. Разбор JSON сам по себе не меняет прототип. - 2Небезопасная запись: универсальное объединение или setter пути обрабатывает ключ без allowlist.
- 3Доступ к прототипу: в зависимости от пути выполнения запись меняет прототип цели или достигает общего прототипа.
- 4Gadget последствий: загрязнение влияет на чувствительное решение только если последующий код доверяет унаследованному свойству. Проверьте конкретный gadget и путь авторизации.
Инженерная защита и правила предотвращения
- ✓Предпочитайте allowlist схемы: создавайте целевой объект только из полей, разрешённых endpoint. Если универсальная запись пути неизбежна, отклоняйте сегменты прототипа на каждом уровне.
- ✓Используйте безопасные словари: храните произвольные ключи в
Mapили словареObject.create(null); проверяйте данные перед передачей API, ожидающим обычные объекты. - ✓Проверяйте собственные свойства: для чувствительных проверок используйте
Object.hasOwn(record, key); не позволяйте унаследованным значениям разрешать действия. - ✓Используйте защиту runtime как дополнительный слой: Node.js
--disable-proto=throwубирает один путь, но не блокируетconstructor.prototype. Заморозка встроенных объектов может нарушить polyfills и зависимости; проверьте совместимость и не заменяйте этим безопасные записи.
Частые вопросы
Нет. JSON.parse() создаёт собственное свойство данных __proto__ и не вызывает унаследованный setter. Риск появляется при последующем слиянии или присваивании, вызывающем setter. См. Вызывает ли JSON.parse() Prototype Pollution?
Это может запретить запись в сам замороженный Object.prototype, но не мешает setter изменить прототип отдельного объекта и не защищает все остальные прототипы. Также может сломать библиотеки, расширяющие встроенные объекты. Используйте только как дополнительную защиту, а не исправление обработки данных.
Нет. Типы удаляются во время выполнения. Значение Record<string, string> всё ещё может содержать ключ __proto__; проверяйте недоверенные ключи и безопасно создавайте объекты.
- MDN — загрязнение прототипа JavaScript
- OWASP — руководство по предотвращению загрязнения прототипа
- CWE-1321 — Недостаточно контролируемое изменение атрибутов прототипа объекта
- MDN — аксессор Object.prototype.__proto__