JavaScript Internals (521 Qs)•Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)
How do you prevent prototype pollution attacks in JavaScript?
Prototype pollution is a security vulnerability where attackers inject properties into Object.prototype, affecting all objects in the application.
Vulnerable code:
javascript
function merge(target, source) {
for (let key in source) {
target[key] = source[key];
}
return target;
}
// Attack payload
const malicious = JSON.parse('{"__proto__": {"polluted": "yes"}}');
merge({}, malicious);
console.log({}.polluted); // "yes" - all objects are polluted!
Prevention 1: Use Object.create(null):
javascript
// Create objects without prototype
const safeObj = Object.create(null);
safeObj.__proto__ = { polluted: 'yes' };
console.log(safeObj.polluted); // undefined
// For configuration objects
const config = Object.create(null);
config.apiUrl = 'https://api.example.com';
Prevention 2: Check for dangerous keys:
javascript
function safeMerge(target, source) {
const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
for (let key in source) {
if (dangerousKeys.includes(key)) {
continue; // Skip dangerous keys
}
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
Prevention 3: Use Map instead of objects:
javascript
const safeMap = new Map();
safeMap.set('__proto__', 'value');
// No pollution risk
Prevention 4: Freeze Object.prototype:
javascript
Object.freeze(Object.prototype);
Object.freeze(Object);
// Now pollution attempts will fail
Object.prototype.polluted = 'no';
console.log({}.polluted); // undefined
Prevention 5: Validate object paths:
javascript
function setDeepProperty(obj, path, value) {
const parts = path.split('.');
const dangerous = ['__proto__', 'constructor', 'prototype'];
// Validate each part of the path
if (parts.some(part => dangerous.includes(part))) {
throw new Error('Invalid property path');
}
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!(parts[i] in current)) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
}
Prevention 6: Use libraries with protection:
javascript
// Use lodash's merge with customizer
const _ = require('lodash');
function safeMergeCustomizer(objValue, srcValue, key) {
const dangerous = ['__proto__', 'constructor', 'prototype'];
if (dangerous.includes(key)) {
return objValue; // Keep original value
}
}
const result = _.mergeWith({}, source, safeMergeCustomizer);
Prevention 7: Schema validation:
javascript
const Ajv = require('ajv');
const ajv = new Ajv();
const schema = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
},
additionalProperties: false // Reject unknown properties
};
const validate = ajv.compile(schema);
function safeProcess(data) {
if (!validate(data)) {
throw new Error('Invalid data');
}
return data;
}
Prevention 8: JSON.parse with reviver:
javascript
function safeJSONParse(text) {
return JSON.parse(text, (key, value) => {
const dangerous = ['__proto__', 'constructor', 'prototype'];
if (dangerous.includes(key)) {
return undefined; // Filter out dangerous keys
}
return value;
});
}
const safe = safeJSONParse('{"__proto__": {"polluted": "yes"}}');
console.log({}.polluted); // undefined