Bola Idor in Express with Hmac Signatures
Bola Idor in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) in an Express API that uses HMAC signatures can occur when signature verification is applied only to the request body or selected fields, while the resource identifier in the URL path or query string is not validated against the token or signature context. For example, an endpoint like /api/users/123/profile may accept an HMAC-signed JSON payload that proves the caller has rights to some data, but if the server does not bind the userId=123 path parameter to the claims inside the signed payload, an attacker can change the ID to another user (e.g., /api/users/456/profile) while keeping the same valid signature on an unrelated body. Because the signature covers only parts of the request, the server may still treat the request as authenticated and authorized, leading to unauthorized access to or modification of other users’ resources.
In Express, this often happens when middleware verifies an HMAC over the request body but does not re-check the relationship between the verified claims (e.g., subject or scope) and the dynamic route parameters. Consider an endpoint that accepts a signed object representing a transfer instruction; if the signature is validated but the server does not ensure that the resource being acted upon (identified via path or query parameters) is included in or derivable from the signed claims, BOLA occurs. Attack patterns include changing numeric IDs, UUIDs, or other references in URLs, leveraging predictable IDs, or exploiting endpoints that return different data based on path parameters without confirming ownership through the signature context.
Moreover, if the HMAC is computed over a subset of headers and body but excludes the path, an attacker can replay a valid signed request to a different resource path if the server does not validate the target path as part of the signed context. For instance, a POST to /api/accounts/789/transactions with a valid HMAC might be accepted even when the account ID in the path does not match any account identifier bound to the signer’s identity in the signed payload. This mismatch between the signed context and the resource identifier enables horizontal privilege escalation, a classic BOLA scenario. Because the signature does not protect the full authorization boundary, the server may treat the request as legitimate and proceed with actions on the target resource.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To mitigate BOLA when using HMAC signatures in Express, bind the resource identifier to the signed payload and validate it during verification. Instead of verifying the signature only over the body, include the path parameters (or a canonical representation of the request target) in the data that is signed. On verification, ensure that the identifier in the URL is present in the signed data and matches the claims (e.g., subject or resource scope). Below is an example that computes the HMAC over a canonical string that combines method, path, and body, and then verifies that the path parameter appears in the payload used for verification.
const crypto = require('crypto');
function getSignatureBase(method, path, body) {
// Canonical representation: method, path, and sorted JSON body
const sortedBody = Object.keys(body || {}).sort().map(k => JSON.stringify([k, body[k]])).join(',');
return `${method.toUpperCase()}|${path}|${sortedBody}`;
}
function verifyHmac(req, res, next) {
const signature = req.headers['x-hmac-signature'];
if (!signature) return res.status(401).json({ error: 'Missing signature' });
const base = getSignatureBase(req.method, req.path, req.body);
const expected = crypto.createHmac('sha256', process.env.HMAC_SECRET).update(base).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
app.post('/api/users/:userId/profile', verifyHmac, (req, res) => {
// At this point, the signature covers method, path (including userId), and body
const claimedUserId = req.body.userId;
const paramUserId = req.params.userId;
if (!claimedUserId || claimedUserId !== paramUserId) {
return res.status(403).json({ error: 'User ID mismatch' });
}
// Proceed with update for the user identified by paramUserId
res.json({ ok: true });
});
This approach ensures the path parameter is part of the signed context, so tampering with the user ID in the URL will break the signature. For cases where the client cannot include the ID in the body, the server can reconstruct the canonical string from the incoming request and verify the signature before checking that the path parameter matches a resource the signer is allowed to access. Additionally, include a per-request nonce or timestamp in the signed base to prevent replay attacks across different resources.
Another concrete pattern is to embed the resource identifier inside the payload and enforce strict checks after signature validation. For example, require the payload to contain a resource field that must equal the path parameter, and reject the request if they differ. Below is a concise Express route demonstrating this pattern with HMAC verification and resource binding.
app.put('/api/accounts/:accountId', verifyHmac, (req, res) => {
const payload = req.body;
// Ensure the signed payload explicitly references the path parameter
if (!payload.resource || payload.resource !== `account:${req.params.accountId}`) {
return res.status(403).json({ error: 'Resource binding missing or mismatched' });
}
// Apply updates only to the claimed and verified account
// ... update logic
res.json({ ok: true });
});
These patterns align the signature coverage with the authorization boundary, preventing attackers from swapping identifiers in the URL while keeping a valid signature. By including the full request target (method, path, body) or at least the resource identifier in the signed data, and by validating that the path parameter matches the signed claims, you effectively mitigate BOLA in Express APIs that rely on HMAC signatures.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |