Prototype Pollution in Express
How Prototype Pollution Manifests in Express
Prototype Pollution in Express applications typically occurs when user-controlled data flows through req.body, req.query, or req.params and gets merged into objects without proper sanitization. Express's body parsing middleware (like express.urlencoded() or body-parser) can inadvertently create prototype pollution vectors when handling nested objects.
The most common Express-specific pattern involves the mergeParams option in route definitions. When mergeParams: true is enabled, URL parameters get merged into the req.params object, potentially allowing attackers to overwrite prototype properties. Consider this vulnerable route:
app.get('/api/users/:id', function(req, res) {
const userId = req.params.id;
const queryParams = req.query;
const userData = { id: userId, ...queryParams };
// userData now contains merged properties, including potential prototype pollution
res.json(userData);
});An attacker could exploit this by requesting /api/users/123?__proto__.isAdmin=true, which would set the isAdmin property on the Object prototype, affecting all objects in the application.
Another Express-specific vector is through template rendering engines. When using engines like Pug or EJS with user input, prototype pollution can lead to template injection. For example:
app.get('/profile', (req, res) => {
res.render('profile', req.query); // req.query could contain __proto__ properties
});Middleware chaining in Express creates additional attack surfaces. If a middleware modifies req.body or req.params without validation, and subsequent middleware trusts these properties, prototype pollution can propagate through the request lifecycle. This is particularly dangerous in applications using Express's app.use() for global middleware.
Express-Specific Detection
Detecting Prototype Pollution in Express requires examining both the application code and runtime behavior. middleBrick's scanner specifically targets Express applications by analyzing request handling patterns and identifying vulnerable code paths.
middleBrick scans for Express-specific indicators including:
- Body parser configurations that enable extended parsing (allowing nested objects)
- Routes with
mergeParams: truethat merge URL parameters - Template rendering with user-controlled data
- Middleware chains that modify request objects without validation
- Dynamic property access patterns like
req[dynamicKey]
The scanner actively tests Express endpoints by sending payloads containing __proto__, constructor, and other prototype pollution patterns. It then analyzes responses for indicators of successful exploitation, such as unexpected property values or application behavior changes.
For manual detection in Express applications, look for these patterns in your codebase:
// Vulnerable: direct merge without validation
const userData = { ...req.body };
// Vulnerable: dynamic property access
const key = req.query.key;
obj[key] = req.query.value;
// Vulnerable: template rendering with user data
res.render('template', req.query);
// Vulnerable: mergeParams without validation
app.get('/api/:id', { mergeParams: true }, (req, res) => {
const merged = { ...req.params, ...req.query };
});middleBrick's OpenAPI analysis also helps identify prototype pollution risks by examining parameter schemas and detecting when complex objects are accepted without proper validation constraints.
Express-Specific Remediation
Remediating Prototype Pollution in Express requires a multi-layered approach. The most effective strategy combines input validation, safe object handling, and Express-specific configurations.
First, configure body parsers securely:
// Secure body parser configuration
app.use(express.urlencoded({
extended: false, // Disallow nested objects
limit: '10kb',
parameterLimit: 100
}));
app.use(express.json({
limit: '10kb',
strict: true // Only accept arrays and objects
}));Second, implement prototype pollution protection middleware:
function prototypePollutionProtection(req, res, next) {
const pollutionKeys = ['__proto__', 'constructor', 'prototype'];
function sanitize(obj) {
if (typeof obj !== 'object' || obj === null) return;
for (const key of Object.keys(obj)) {
if (pollutionKeys.includes(key)) {
delete obj[key];
} else if (typeof obj[key] === 'object') {
sanitize(obj[key]);
}
}
}
sanitize(req.body);
sanitize(req.query);
sanitize(req.params);
next();
}
app.use(prototypePollutionProtection);Third, avoid dangerous patterns in route handlers:
// Secure: explicit property assignment
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
// Only allow specific properties
});
// Secure: validation before use
app.get('/api/items/:id', (req, res) => {
const id = req.params.id;
$/.test(id)) { // ObjectId validation
return res.status(400).json({ error: 'Invalid ID format' });
}
// Safe to use id
});For template rendering, always explicitly define the data context:
app.get('/profile', (req, res) => {
const allowedFields = ['name', 'email', 'bio'];
const sanitizedData = {};
for (const field of allowedFields) {
if (req.query[field]) {
sanitizedData[field] = req.query[field];
}
}
res.render('profile', sanitizedData); // Only allowed fields passed
});Finally, use middleBrick's continuous monitoring to ensure prototype pollution protections remain effective as your Express application evolves. The scanner can be integrated into your CI/CD pipeline to fail builds if new prototype pollution vulnerabilities are introduced.