Broken Access Control in Sails with Hmac Signatures
Broken Access Control in Sails with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Broken Access Control (BOLA/IDOR) in Sails when Hmac Signatures are used incorrectly can allow an authenticated or unauthenticated attacker to access or modify resources that should be restricted. Sails is a Node.js MVC framework that does not enforce authorization or ownership checks by default; it relies on developer code to apply access rules. When Hmac Signatures are used for request authentication, a common pattern is to sign a subset of request parameters (e.g., userId, resourceId, timestamp) and verify the signature server-side before processing the request. If the signature is verified but the application then uses user-supplied identifiers (e.g., req.param('id')) directly to load a record without confirming that the authenticated subject is allowed to act on that specific resource, the Hmac layer does not prevent horizontal privilege escalation or unauthorized object access.
For example, consider an endpoint like GET /api/account/:accountId that expects a signature covering accountId. An attacker who can obtain or guess another user’s accountId could replay a valid request with a different accountId if the server does not enforce that the authenticated principal owns that accountId. The signature may still validate because the attacker can include the target accountId in the signed payload if the signing key is leaked or if the signature does not bind the request to a specific subject. Additionally, if the signature is computed over only partial parameters and the server trusts other parameters taken from the request path or body, an attacker can modify those unchecked parameters to escalate privileges (e.g., changing role flags). This is a BOLA/IDOR issue: the server fails to ensure the requesting identity matches the resource identity. The risk is compounded when endpoints expose sensitive data or perform state-changing operations without re-checking ownership after signature verification. Even with Hmac Signatures protecting integrity and authenticity of the request, the application must enforce granular access control, ensuring that every data access respects the subject’s permissions and that identifiers are not malleable by the client.
Real-world patterns from API security testing show that these mistakes map to OWASP API Top 10 A01:2023 broken access control and can be discovered in scans that test unauthenticated and authenticated attack surfaces. Attack patterns such as IDOR via tampered object references or horizontal privilege escalation are observed when signature verification does not enforce resource ownership. In Sails, this often surfaces in controllers that load models using req.params without scoping to the requesting user or service identity. For example, using Waterline ORM without a where clause that restricts by userId can return records belonging to other users. The presence of Hmac Signatures should not reduce rigor; instead, it requires precise binding of the signature scope to authorization decisions and a clear separation between integrity checks and authorization checks.
Hmac Signatures-Specific Remediation in Sails — concrete code fixes
To remediate Broken Access Control when using Hmac Signatures in Sails, bind the signature to the authenticated subject and enforce ownership checks on every resource access. Do not trust path or body parameters after signature verification; scope all queries to the identity derived from the authentication context (e.g., from the signature payload or session). Below are concrete, working examples that demonstrate secure patterns.
Example 1: Signing and verifying a request with user context
Sign a canonical string that includes userId, a timestamp, and the intended resourceId, then verify on the server. Use crypto.createHmac with a strong algorithm (e.g., sha256) and a per-service secret stored securely. Always verify the timestamp to prevent replay.
// utils/signature.js
const crypto = require('crypto');
const SECRET = process.env.HMAC_SECRET; // store securely, rotate periodically
const EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
function buildHmac(payload) {
const sorted = Object.keys(payload)
.sort()
.map(k => `${k}:${payload[k]}`)
.join('|');
const hmac = crypto.createHmac('sha256', SECRET);
hmac.update(sorted);
return hmac.digest('hex');
}
function verifyHmac(payload, receivedSignature) {
const expected = buildHmac(payload);
const timingSafe = crypto.timingSafeEqual
? crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature))
: expected === receivedSignature;
// Optional: enforce expiry
const now = Date.now();
const isFresh = now - payload.timestamp < EXPIRY_MS;
return timingSafe && isFresh;
}
module.exports = { buildHmac, verifyHmac };
Client example (pseudo-request):
const payload = {
userId: 'u_123',
accountId: 'a_456',
timestamp: Date.now(),
method: 'GET',
path: '/api/account/a_456'
};
const signature = buildHmac(payload); // send signature in header x-hmac-signature
Example 2: Sails controller with ownership check after signature verification
In the controller, after verifying the Hmac, load the resource scoped to the identity extracted from the payload (not solely from request params). This ensures that even if the attacker changes the URL parameter, the query will return empty unless the resource belongs to the authenticated subject.
// api/controllers/AccountController.js
const { verifyHmac } = require('../utils/signature');
module.exports = {
show: async function (req, res) {
const { signature, timestamp, userId, accountId } = req.allParams();
const payload = { userId, accountId, timestamp, method: req.method, path: req.path };
if (!verifyHmac(payload, signature)) {
return res.unauthorized('Invalid or expired signature');
}
// Enforce ownership: scope by authenticated userId from payload, not only route param
const account = await Account.findOne({
id: accountId,
userId: userId // ensures the account belongs to the subject encoded in the signature
});
if (!account) {
return res.notFound('Account not found or access denied');
}
return res.ok(account);
}
};
Additional measures: use the Pro plan’s continuous monitoring to detect anomalies in signature usage, and leverage the GitHub Action to fail builds if risk scores drop due to missing ownership checks. For advanced setups, the MCP Server can integrate these checks into your AI coding assistant to flag insecure patterns during development.