Прототипное загрязнение (prototype pollution) — класс уязвимостей,
возникающих при неконтролируемом изменении цепочки прототипов объектов в
JavaScript. Поскольку большинство объектов наследуются от
Object.prototype, добавление или модификация свойств на
уровне прототипа приводит к их появлению во всех объектах
приложения.
Типичный вектор атаки связан с присвоением значений по ключам:
__proto__constructor.prototypeprototypeПри некорректной обработке входных данных возможно выполнение кода вида:
obj["__proto__"]["polluted"] = "value";
Результат — появление свойства polluted во всех
объектах, даже тех, которые не имели к нему отношения.
Особенность уязвимости заключается в том, что она часто проявляется не напрямую, а через цепочки преобразований данных: merge-операции, глубокое копирование, десериализацию JSON и автоматическое построение объектов.
Joi представляет собой библиотеку для описания и проверки структур данных. Основная задача — гарантировать соответствие входных объектов заданной схеме.
Типовой сценарий использования:
const schema = Joi.object({
username: Joi.string().alphanum().required(),
age: Joi.number().min(0).max(120)
});
const result = schema.validate(input);
Joi работает на уровне описания структуры, а не на уровне прямого манипулирования прототипами. Однако взаимодействие с объектами JavaScript в процессе валидации и трансформации данных создаёт потенциальные точки риска при неправильной интеграции.
При валидации сложных структур Joi допускает глубокие схемы:
const schema = Joi.object({
settings: Joi.object({
theme: Joi.string(),
config: Joi.object()
})
});
Если результат валидации напрямую используется для сборки новых объектов без фильтрации ключей, появляется риск переноса опасных свойств.
JavaScript допускает любые строковые ключи:
const input = JSON.parse('{"__proto__": {"admin": true}}');
Если валидированный объект используется в дальнейшем без фильтрации ключей, например при merge-операциях:
Object.assign(target, validatedData);
или:
lodash.merge(target, validatedData);
возможно распространение нежелательных свойств на прототип.
Joi поддерживает режимы:
unknown(true) — разрешает неизвестные поляunknown(false) — запрещает лишние поляРежим unknown(true) в сочетании с отсутствием фильтрации
ключей после валидации может привести к сохранению потенциально опасных
свойств:
const schema = Joi.object({
name: Joi.string()
}).unknown(true);
При таком подходе любые дополнительные поля сохраняются в результате.
Сама библиотека Joi не изменяет прототипы объектов и не выполняет операций, которые напрямую ведут к prototype pollution. Однако она участвует в следующих процессах:
Именно на этапе интеграции с бизнес-логикой возникает риск.
const config = schema.validate(userInput).value;
globalConfig = Object.assign(globalConfig, config);
Если config содержит специальные ключи, они могут
попасть в глобальные структуры.
merge(appState, validatedInput);
Без очистки ключей __proto__ и constructor
возможно загрязнение цепочки прототипов.
const obj = {};
for (const key in validatedInput) {
obj[key] = validatedInput[key];
}
Цикл for...in может обходить свойства, унаследованные
через прототип, если объект уже был загрязнён.
Joi.object({
name: Joi.string()
}).required().unknown(false);
Запрет дополнительных ключей снижает вероятность передачи неожиданных свойств.
Joi позволяет задавать строгие схемы для ключей:
Joi.object().pattern(
/^[a-zA-Z0-9]+$/,
Joi.string()
);
Это исключает использование специальных ключей вроде
__proto__.
После выполнения валидации целесообразно применять очистку:
__proto__constructorprototypedelete sanitized.__proto__;
delete sanitized.constructor;
const safeObject = Object.create(null);
Такой объект не наследует свойства Object.prototype, что
снижает эффект загрязнения.
Некоторые реализации merge позволяют игнорировать опасные ключи:
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === "__proto__" || key === "constructor") continue;
target[key] = source[key];
}
}
Результаты Joi следует рассматривать как данные, требующие дополнительного уровня защиты перед использованием в:
В связке:
JSON.parse → Joi.validate → merge
возможна цепочка, в которой опасные ключи проходят через этапы обработки, если отсутствует фильтрация.
Особенно критично это при обработке внешних API или пользовательского ввода.
При глубокой валидации:
Joi.object({
a: Joi.object({
b: Joi.object({
c: Joi.string()
})
})
});
Joi рекурсивно проходит по структуре, но не интерпретирует ключи как опасные сами по себе. Опасность возникает только при последующем использовании результата без очистки.
Prototype pollution становится значимой проблемой в следующих окружениях:
В таких системах загрязнение прототипа может влиять на множество независимых компонентов одновременно.