FlawOpen

flawopen.com/Уязвимости/Загрязнение прототипа JavaScript

Загрязнение прототипа JavaScript

Высокая серьёзность CWE-1321 Цепочка поставок и runtime
Простое объяснение — общая книга правил

Представьте мастерскую, где каждое новое устройство сверяется с общей книгой правил, если собственных настроек недостаточно. Если сотрудник запишет туда правило, заданное злоумышленником, другие устройства могут принять его за значение по умолчанию. При загрязнении прототипа небезопасная динамическая запись может изменить прототип объекта, иногда Object.prototype. Эффект не обязательно глобален: последствия возникают, только если код приложения позднее доверяет унаследованному свойству.

Цель:JavaScript на сервере или в браузере, который объединяет недоверенные ключи или записывает заданные пользователем пути свойств
Вектор:Недоверенные ключи, например __proto__, или путь constructor.prototype, ведущий к небезопасной записи
Последствия:Зависит от доступного gadget: изменение логики, отказ в обслуживании, XSS или в некоторых серверных случаях выполнение кода; это не происходит автоматически
Защита:Проверяйте данные по схеме или allowlist; для недоверенных ключей используйте Map или словари без прототипа

Основные понятия и термины подсистемы

Object.prototype
Общий прототип, наследуемый обычными объектами. Его загрязнение может затронуть многие объекты, но не все объекты JavaScript и не все realm.
__proto__
Устаревший accessor обычных объектов для чтения или изменения прототипа. В тексте JSON такая запись изначально является обычным именем свойства.
Рекурсивное глубокое объединение
Функция, которая обходит вложенные ключи и записывает их в другой объект. Она опасна, если недоверенные ключи выбирают свойства прототипа или небезопасные пути.
CWE-1321
Недостаточно контролируемое изменение атрибутов прототипа объекта. Слабость — неконтролируемая мутация; дальнейшие последствия зависят от доступного поведения приложения.

Пошаговая схема атаки

Шаг 1

Недоверенные свойства на входе

Endpoint принимает ключи объекта, заданные атакующим. Само чтение JSON с собственным свойством __proto__ не изменяет прототип.

Шаг 2

Небезопасная динамическая запись

Рекурсивное объединение или setter пути следует этим ключам и выполняет динамическую запись без проверки каждого сегмента.

Шаг 3

Изменение прототипа цели или общего прототипа

В зависимости от цели и порядка записи операция меняет прототип цели или достигает общего прототипа, например Object.prototype. Это разные результаты.

Шаг 4

Gadget определяет последствия

Позднее gadget читает унаследованное значение в чувствительной операции. Последствия зависят от gadget; нельзя заранее утверждать обход аутентификации или выполнение кода.

Механизм и первопричина

Поиск свойств в JavaScript проходит по цепочке прототипов. Уязвимое объединение или setter пути может позволить недоверенным ключам, например __proto__ или constructor.prototype, направить запись в прототип. Собственное поле __proto__, созданное JSON.parse, само по себе не является загрязнением: другой код должен обработать его небезопасным присваиванием. Изменение прототипа цели затрагивает эту цель; изменение Object.prototype затрагивает обычные объекты, которые его наследуют. Для последствий нужен отдельный gadget, использующий унаследованное свойство.

utils.js (Уязвимое рекурсивное объединение)Уязвимый код
// УЯЗВИМО: ключи атакующего достигают рекурсивной записи
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);
utils.js (Разбор профиля по allowlist)Защищённый код
// ЗАЩИЩЕНО: копировать только поля, разрешённые этим 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;
}

Последовательность атаки и эксплуатации

Инженерная защита и правила предотвращения

Частые вопросы

Загрязняет ли JSON.parse() прототип самостоятельно?

Нет. JSON.parse() создаёт собственное свойство данных __proto__ и не вызывает унаследованный setter. Риск появляется при последующем слиянии или присваивании, вызывающем setter. См. Вызывает ли JSON.parse() Prototype Pollution?

Исправляет ли проблему Object.freeze(Object.prototype)?

Это может запретить запись в сам замороженный Object.prototype, но не мешает setter изменить прототип отдельного объекта и не защищает все остальные прототипы. Также может сломать библиотеки, расширяющие встроенные объекты. Используйте только как дополнительную защиту, а не исправление обработки данных.

Защищён ли TypeScript?

Нет. Типы удаляются во время выполнения. Значение Record<string, string> всё ещё может содержать ключ __proto__; проверяйте недоверенные ключи и безопасно создавайте объекты.

Другие темы и разборы по безопасности:Полный каталог безопасности →