Bola Idor in Sails with Hmac Signatures
Bola Idor in Sails with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper ownership or authorization checks between a subject (e.g., a user) and a target object. In Sails.js, this commonly manifests when record-level access control is missing on controller actions that accept user-supplied identifiers such as :id. When Hmac Signatures are used for request authentication, BOLA can still be present if the signature is valid but the application does not verify that the authenticated subject is allowed to access the specific resource identified by the supplied ID.
Consider a Sails API that uses an Hmac signature to verify request integrity and origin. The client includes an Hmac-Signature header computed over selected canonical elements (e.g., HTTP method, URL path, timestamp, and a stable JSON body). This mechanism can ensure the request has not been tampered with and can authenticate the client. However, if the endpoint then directly uses an :id parameter from the request (path or body) to fetch and return or modify a record without confirming that the record belongs to the requesting subject, the endpoint is vulnerable to BOLA. For example, an attacker who knows another user’s record ID can issue a validly signed GET /user/:id or PATCH /user/:id request and obtain or alter data they should not access. The presence of Hmac Signatures does not mitigate missing ownership checks; it only ensures the request was issued by a known client, not that the client is authorized for that specific object.
In Sails, this often arises when developer focus is on securing transport and replay resistance via Hmac while neglecting consistent authorization checks across controllers and policies. If a policy or controller action assumes a valid Hmac signature implies proper authorization, BOLA is likely. Attack patterns include iterating over plausible IDs (enumeration), accessing sibling tenant data in multi-tenant setups, or leveraging predictable IDs to infer business metrics. Even when using UUIDs, which reduce predictability, BOLA persists if authorization is not enforced. The OWASP API Security Top 10 lists Broken Object Level Authorization as a prevalent risk, and Sails applications using Hmac Signatures must treat signature validity and object ownership as separate concerns.
Hmac Signatures-Specific Remediation in Sails — concrete code fixes
To remediate BOLA in Sails while using Hmac Signatures, ensure that every data access operation validates both the request signature and the subject’s authorization for the target object. Authorization must be tied to the business ownership or tenant relationship, not merely to the presence of a valid signature. Below are concrete patterns and code examples to implement this correctly.
1. Hmac signature verification middleware
Use a custom hook or policy to verify the Hmac signature before the request reaches the controller. This example computes the signature over selected canonical elements and compares it in constant time.
// config/hmac.js
const crypto = require('crypto');
module.exports.hmacVerifier = (req, res, next) => {
const signature = req.headers['x-hmac-signature'];
const timestamp = req.headers['x-request-timestamp'];
const nonce = req.headers['x-request-nonce'];
if (!signature || !timestamp || !nonce) {
return res.badRequest('Missing authentication headers');
}
// Prevent replay: ensure timestamp is within an acceptable window (e.g., 5 minutes)
const now = Date.now();
const requestTime = parseInt(timestamp, 10);
if (Math.abs(now - requestTime) > 5 * 60 * 1000) {
return res.status(401).send('Request expired');
}
const method = req.method.toUpperCase();
const path = req.path; // e.g., '/user/123'
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
const canonicalString = `${method}\n${path}\n${timestamp}\n${nonce}\n${body}`;
const key = Buffer.from(sails.config.custom.hmacSecret, 'base64');
const expected = crypto.createHmac('sha256', key).update(canonicalString).digest('base64');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
return next();
});
};
2. Controller action with ownership check
After signature verification, enforce that the requesting user can only access their own records. Assuming a user is identified via an API key or token (not shown here), fetch the record and compare ownership before proceeding.
// api/controllers/UserController.js
module.exports = {
async findOne(req, res) {
const userId = req.param('id');
const requesterId = req.user.id; // resolved by an auth policy earlier
if (!userId || !requesterId) {
return res.badRequest('Missing user identifier');
}
// BOLA mitigation: ensure the requested user matches the authenticated user
if (userId !== requesterId) {
return res.status(403).send('Forbidden: access to this resource denied');
}
const user = await User.findOne(userId);
if (!user) {
return res.notFound();
}
return res.ok(user);
},
async update(req, res) {
const userId = req.param('id');
const requesterId = req.user.id;
if (userId !== requesterId) {
return res.status(403).send('Forbidden: access to this resource denied');
}
const updates = req.allParams();
const updated = await User.updateOne(userId).set(updates);
if (!updated) {
return res.notFound();
}
return res.ok(updated);
}
};
3. Policy-based authorization with sails-hook-bolicies
For larger applications, centralize authorization using policies that validate object-level permissions. This policy ensures that a user can only act on their own profile regardless of the presence of a valid Hmac signature.
// api/policies/ensure-owns-record.js
module.exports = async function ensureOwnsRecord(req, res, proceed) {
const model = req.options.model || req.options.controller;
const recordId = req.param('id') || (req.body && req.body.id);
const userId = req.user?.id;
if (!model || !recordId || !userId) {
return res.status(400).send('Insufficient parameters for ownership check');
}
const record = await sails.helpers.models.findOne(model, recordId);
if (!record) {
return res.notFound();
}
// Example: record must have ownerId matching requester
if (!record.ownerId || record.ownerId !== userId) {
return res.status(403).send('Forbidden: you do not own this resource');
}
return proceed();
};
4. Example OpenAPI spec snippet to document ownership expectations
While middleBrick scans OpenAPI specs and runtime behavior, explicitly document ownership constraints in your spec to align design and implementation.
paths:
/user/{id}:
get:
summary: Get current user profile
parameters:
- name: id
in: path
required: true
schema:
type: string
security:
- hmacAuth: []
responses:
'200':
description: User profile
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'403':
description: Forbidden — cannot access another user’s resource
5. Testing for BOLA with valid Hmac Signatures
Validate that a valid Hmac signature does not bypass authorization. Use the CLI to confirm findings and ensure your remediation works as expected.
# Scan the endpoint to verify BOLA detection and remediation effectiveness
middlebrick scan https://api.example.com/user/123
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 |