Prototype Pollution in Express with Hmac Signatures
Prototype Pollution in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Prototype pollution in Express applications becomes particularly concerning when Hmac signatures are used to validate object integrity or authorization tokens. In this context, an attacker can supply crafted query or body parameters that modify the prototype of shared objects (for example, Object.prototype), and if the server later constructs Hmac signatures over the manipulated object, the signature may still verify successfully despite unexpected properties. This occurs because signature generation often iterates over object keys, and a polluted prototype introduces inherited enumerable properties that get included in the signature input, changing the hashed payload in ways the server may not explicitly validate.
Consider an Express route that accepts JSON input and creates an Hmac signature using a shared secret:
const crypto = require('crypto');
function buildHmac(data) {
const hmac = crypto.createHmac('sha256', 'super-secret');
hmac.update(JSON.stringify(data));
return hmac.digest('hex');
}
app.post('/api/merge', express.json(), (req, res) => {
const received = req.body;
const expected = buildHmac(received);
const provided = req.headers['x-hmac'];
if (provided !== expected) {
return res.status(401).send('Invalid signature');
}
// further processing
res.json({ merged: Object.assign({}, received) });
});
If the client sends {__proto__: { admin: true }} and the server uses JSON.stringify naively, some JSON parsers may produce an object where __proto__ modifies the prototype chain before signature verification, depending on the runtime and parsing behavior. The Hmac signature computed over the polluted object may differ from what the client provides, causing rejection, but in other scenarios the server might compute the signature after pollution has occurred, leading to a situation where an attacker can influence properties that are included in the signature without changing the Hmac value they must match. This can expose logic that trusts signature integrity while not strictly isolating own properties from inherited ones.
Within the 12 security checks run by middleBrick, this pattern is flagged under BOLA/IDOR and Unsafe Consumption checks because the server trusts object shapes derived from external input. The scan detects that Hmac signature comparisons may involve properties introduced via prototype pollution, which can bypass authorization assumptions. Such findings align with OWASP API Top 10 A01:2023 broken object level authorization and highlight how signature schemes alone do not prevent object state manipulation if the data fed into signing is not cleaned.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
Remediation focuses on ensuring that Hmac signatures are computed over a canonical, pollution‑free representation and that object construction never relies on prototype chain properties. Prefer explicit property whitelisting and strict parsing to avoid inherited keys being included in the signature or used in business logic.
1. Use a strict JSON reviver and whitelist allowed keys
const crypto = require('crypto');
const allowed = new Set(['action', 'resource', 'value', 'timestamp']);
function sanitize(input) {
const seen = new Set();
return JSON.parse(input, (key, value) => {
if (key === '' || allowed.has(key)) {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return undefined; // break cycles
seen.add(value);
}
return value;
}
return undefined;
});
}
function buildHmac(data) {
const hmac = crypto.createHmac('sha256', 'super-secret');
hmac.update(JSON.stringify(data, Object.keys(data).sort()));
return hmac.digest('hex');
}
2. Validate own properties only using Object.prototype.hasOwnProperty and avoid Object.assign with polluted sources
app.post('/api/merge', express.json(), (req, res) => {
const raw = req.body;
const clean = {};
for (const key of Object.keys(raw)) {
if (Object.prototype.hasOwnProperty.call(raw, key)) {
clean[key] = raw[key];
}
}
const expected = buildHmac(clean);
const provided = req.headers['x-hmac'];
if (provided !== expected) {
return res.status(401).send('Invalid signature');
}
// safe merge using clean only
const merged = Object.assign({}, clean);
res.json({ merged });
});
3. Use a canonical serialization for signing (sorted keys, no undefined)
function buildHmac(data) {
const keys = Object.keys(data).sort();
const canonical = {};
for (const k of keys) canonical[k] = data[k];
const hmac = crypto.createHmac('sha256', 'super-secret');
hmac.update(JSON.stringify(canonical));
return hmac.digest('hex');
}
These patterns ensure that Hmac signature generation excludes inherited prototype properties and that comparisons are performed over a deterministic object shape. When integrating with CI/CD, the middleBrick GitHub Action can be configured to fail builds if risk scores exceed your threshold, while the CLI enables quick local verification with middlebrick scan <url>. For ongoing assurance, the Pro plan provides continuous monitoring that can schedule regular scans and deliver Slack or Teams alerts when object-level integrity risks are detected.