Bola Idor in Loopback with Dynamodb
Bola Idor in Loopback with Dynamodb — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API exposes object identifiers (IDs) and fails to enforce that the requesting user is authorized to access the specific resource. In a LoopBack application backed by DynamoDB, this risk arises from a mismatch between the exposed DynamoDB key schema and the access controls enforced by the application.
LoopBack is a highly flexible framework that encourages modeling data as models with datasource definitions. When integrating DynamoDB, developers often define a model with a key such as id (string) and map it to a DynamoDB primary key. If route or remote method definitions rely solely on this key to locate records — for example, using a URL like /api/users/12345 — the ID 12345 is directly interpreted as a DynamoDB key value. Without additional ownership checks, any authenticated or even unauthenticated caller who can guess or enumerate valid IDs can read, update, or delete records they should not access.
DynamoDB’s schema-less design and primary key structure amplify this risk. A common pattern uses a composite primary key (partition key and sort key). For example, a table might use userId as the partition key and infoId as the sort key. If the application exposes only the sort key (e.g., /api/profiles/info123) and does not verify that the authenticated user’s ID matches the partition key in the query, an attacker can modify the sort key to access another user’s profile by iterating through known values. This is a classic BOLA/IDOR pattern.
Consider a LoopBack model configured with the DynamoDB connector:
{
"name": "userProfile",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"id": { "type": "string", "id": 1 },
"userId": { "type": "string" },
"displayName": { "type": "string" }
},
"validations": [],
"acls": [],
"methods": {}
}
If a remote method is defined as findById(userId, profileId, cb) and internally performs a DynamoDB GetItem using only profileId as the key, the service does not verify that the authenticated user’s ID matches the requested userId. An attacker who knows or can guess valid profileId values can iterate through them to access other users’ profiles.
Moreover, DynamoDB’s lack of native relational constraints means there is no automatic referential integrity. Developers must enforce ownership in application logic. If this enforcement is inconsistent — for example, checking ownership on create but not on read or update — BOLA vulnerabilities emerge. The combination of LoopBack’s flexible remote method invocation and DynamoDB’s key-based access patterns makes it essential to explicitly validate that the authenticated subject has the correct partition key (or equivalent ownership attribute) for every operation.
Real-world exploitation patterns include horizontal privilege escalation (user A accessing user B’s data) and vertical escalation (lower-privilege roles accessing admin resources) when role attributes are stored in DynamoDB items but not validated at the data access layer. Because DynamoDB queries are fast and costs are request-based, attackers can perform extensive enumeration if rate limiting is not properly configured.
Dynamodb-Specific Remediation in Loopback — concrete code fixes
Remediation centers on ensuring that every DynamoDB operation includes the authenticated subject’s identifier as part of the key expression, and that remote method arguments are validated against the request context.
First, model your DynamoDB table with a composite key that includes the user identifier. For example, use userId as the partition key and a user-owned entity ID as the sort key. This structure enables efficient queries scoped to a single user.
In LoopBack, define a model JSON that reflects this key design and use a remote method that explicitly requires the user ID from the token or session, never from user-supplied path parameters alone:
{
"name": "userProfile",
"base": "PersistedModel",
"idInjection": false,
"properties": {
"userId": { "type": "string", "required": true },
"profileId": { "type": "string", "id": 1 },
"displayName": { "type": "string" }
},
"validations": [],
"acls": [],
"methods": {}
}
Implement a remote method that resolves the authenticated user ID from the context and constructs a DynamoDB query with both key attributes:
UserProfile.remoteMethod('findByProfile', {
accepts: [{ arg: 'profileId', type: 'string', http: { source: 'path' } }],
returns: { arg: 'result', type: 'object' },
http: { path: '/:profileId', verb: 'get' }
});
UserProfile.findByProfile = function(profileId, next) {
const ctx = this;
// Assume getCurrentUser returns { userId: 'user-uuid' }
const currentUser = ctx.getCurrentUser();
if (!currentUser || currentUser.id !== ctx.req.user?.sub) {
return next(new Error('Unauthorized'));
}
const params = {
TableName: 'UserProfiles',
Key: {
userId: currentUser.sub,
profileId: profileId
}
};
ctx.app.models.DynamoDB.get(params, function(err, data) {
if (err) return next(err);
return next(null, data.Item);
});
};
This pattern ensures that the partition key is derived from the authenticated subject, not from the caller-supplied path parameter. Even if an attacker guesses another user’s profileId, the query will either return an empty result or a mismatch error because the partition key does not align with the requester’s user ID.
For update and delete operations, apply the same pattern. Use a remote method that accepts the entity’s composite key and validates ownership before invoking DynamoDB actions:
UserProfile.remoteMethod('updateProfile', {
accepts: [
{ arg: 'profileId', type: 'string', http: { source: 'path' } },
{ arg: 'patch', type: 'object', http: { source: 'body' } }
],
returns: { arg: 'success', type: 'boolean' },
http: { path: '/:profileId', verb: 'patch' }
});
UserProfile.updateProfile = function(profileId, patch, next) {
const ctx = this;
const currentUser = ctx.getCurrentUser();
const params = {
TableName: 'UserProfiles',
Key: {
userId: currentUser.sub,
profileId: profileId
},
UpdateExpression: 'set #name = :val',
ExpressionAttributeNames: { '#name': 'displayName' },
ExpressionAttributeValues: { ':val': patch.displayName }
};
ctx.app.models.DynamoDB.update(params, function(err) {
if (err) return next(err);
return next(null, true);
});
};
Additionally, enforce scope-based access control by storing user roles or groups in DynamoDB and evaluating them within the method. Avoid relying on client-supplied identifiers for access decisions. Combine these practices with rate limiting and monitoring to reduce enumeration risks. Tools like middleBrick can help detect such authorization inconsistencies by scanning your endpoints and comparing runtime behavior against your OpenAPI specification.
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 |