Prototype pollution là khi kẻ tấn công ghi được vào Object.prototype, làm mọi object trong tiến trình đột nhiên có thêm thuộc tính đó.
Điểm vào phổ biến là các hàm merge/clone/set-path đệ quy xử lý dữ liệu do user gửi:
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
merge(target[key], source[key]) // key = '__proto__' -> writes to Object.prototype
} else {
target[key] = source[key]
}
}
}
merge({}, JSON.parse('{"__proto__":{"isAdmin":true}}'))
console.log(({}).isAdmin) // trueCác nguồn hay gặp: body JSON, query string parse thành object lồng nhau (?a[__proto__][x]=1), file config, và constructor.prototype thay cho __proto__.
Vì sao hậu quả nặng:
- Bypass phân quyền: code kiểm tra if (user.isAdmin) trên object không có field đó vẫn nhận true từ prototype.
- Bypass kiểm tra tồn tại: mọi obj.anything đều khác undefined, làm hỏng nhánh logic mặc định.
- RCE: cần thêm một gadget — đoạn code đọc thuộc tính không tồn tại rồi đưa vào chỗ nguy hiểm. Ví dụ kinh điển là làm bẩn options.shell hoặc env mà child_process.spawn đọc tới, hoặc làm bẩn tuỳ chọn của template engine (Handlebars, Pug) để chèn code vào template biên dịch. Phía client, gadget trong thư viện dẫn tới DOM XSS.
Phòng chống:
- Chặn key __proto__, constructor, prototype khi duyệt input, và không đệ quy vào key kế thừa (Object.hasOwn).
- Dùng Object.create(null) hoặc Map cho object kiểu từ điển nhận dữ liệu ngoài.
- Object.freeze(Object.prototype) lúc khởi động — hiệu quả và rẻ nếu ứng dụng không sửa prototype.
- Validate schema (zod, ajv với additionalProperties: false) trước khi merge; chỉ lấy đúng các field khai báo.
- Ưu tiên thư viện merge đã vá (lodash bản mới) thay vì tự viết.