HIGH bola idorrestifydynamodb

Bola Idor in Restify with Dynamodb

Bola Idor in Restify 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 authenticated subject is authorized to access or manipulate the specific resource. In a Restify service that uses DynamoDB as the persistence layer, this typically arises when an endpoint like /users/:userId/profile accepts a user-controlled userId parameter, looks up the corresponding DynamoDB item, and returns it without verifying that the requesting identity matches :userId. Because the authorization check is missing or misaligned, an attacker can enumerate or manipulate IDs to access other users' data.

DynamoDB amplifies the risk in two practical ways. First, primary keys (partition key and optional sort key) are often directly exposed as identifiers in APIs; if these keys are predictable (e.g., USER#12345) or sequential, attackers can iterate through valid-looking keys. Second, DynamoDB permissions attached to the service or application identity may be overly permissive, allowing read, update, or delete actions across a broad set of items. When the application layer does not enforce ownership or tenant boundaries, the combination of a permissive DynamoDB policy and missing ownership validation creates a BOLA path that bypasses intended access controls.

Consider a typical Restify handler that retrieves a user profile directly from DynamoDB using a userId from the URL. If the handler does not compare the authenticated subject (e.g., from a JWT or session) with the userId used in the query, an attacker can supply any userId and retrieve or modify data they should not see. In multi-tenant setups, similar issues arise when tenant or organization identifiers are not validated against the requester’s affiliations. Even when the endpoint uses a different logical ID (such as an internal UUID), if the mapping between that ID and the requester’s permissions is not checked, BOLA persists. Real-world attack patterns include changing numeric IDs, tampering with path parameters, or leveraging exposed keys from prior responses to chain unauthorized requests.

Because middleBrick performs black-box scanning without credentials, it can detect these authorization gaps by observing whether responses differ across IDs that should be isolated. The scanner checks whether an endpoint enforces proper access boundaries and highlights cases where object-level authorization is missing, misapplied, or overly dependent on client-supplied identifiers alone.

Dynamodb-Specific Remediation in Restify — concrete code fixes

To fix BOLA in a Restify service backed by DynamoDB, enforce ownership or tenant checks in every handler that accesses a specific item, and avoid exposing raw DynamoDB keys directly as API identifiers when possible. Use the authenticated subject to scope queries, and ensure DynamoDB requests include filters that align with the requester’s permissions.

Principle: Authorize based on the authenticated subject, not only the resource ID

Instead of trusting the userId from the URL, derive the subject from the authentication token and use it in the DynamoDB query key condition. This ensures that even if an attacker modifies the URL, the query will not return another user’s data.

// Example: Restify handler with subject-based DynamoDB query
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

async function getUserProfile(request, reply) {
  const userIdFromToken = request.user.sub; // from JWT or session
  const requestedUserId = request.params.userId;

  if (userIdFromToken !== requestedUserId) {
    reply.code(403).send({ error: 'Forbidden: cannot access other user data' });
    return;
  }

  const params = {
    TableName: process.env.USERS_TABLE,
    Key: {
      pk: `USER#${userIdFromToken}`, // use authenticated subject
      sk: `PROFILE`
    }
  };

  try {
    const { Item } = await ddb.get(params).promise();
    if (!Item) {
      reply.code(404).send({ error: 'Not found' });
      return;
    }
    reply.send(Item);
  } catch (err) {
    request.log.error(err);
    reply.code(500).send({ error: 'Internal server error' });
  }
}

Principle: Scope queries with partition key filters derived from identity

When data is stored with composite keys (e.g., PK/SK), ensure the partition key includes a tenant or user context and that the query uses that context rather than a raw ID supplied by the client.

// Example: Tenant-aware DynamoDB query in Restify
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

async function listUserPosts(request, reply) {
  const subject = request.user.sub; // authenticated subject like 'tenant1|user123'
  const tenantId = subject.split('|')[0];

  const params = {
    TableName: process.env.POSTS_TABLE,
    IndexName: 'gsi_user',
    KeyConditionExpression: 'pk = :pk AND begins_with(sk, :sk)',
    ExpressionAttributeValues: {
      ':pk': `USER#${subject}`, // or tenant-specific partition key
      ':sk': 'POST#'
    }
  };

  try {
    const { Items } = await ddb.query(params).promise();
    reply.send(Items);
  } catch (err) {
    request.log.error(err);
    reply.code(500).send({ error: 'Internal server error' });
  }
}

Principle: Avoid ID mapping tables that can be traversed without authorization

If you maintain a mapping table that links public-facing IDs to DynamoDB keys, ensure each lookup enforces access control. Do not allow an ID mapping lookup to bypass ownership checks.

// Example: Secure mapping lookup with ownership verification
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

async function getPostById(request, reply) {
  const subject = request.user.sub;
  const publicId = request.params.publicId;

  // Fetch mapping entry
  const mapParams = {
    TableName: process.env.MAPPING_TABLE,
    Key: { publicId }
  };

  const { Item } = await ddb.get(mapParams).promise();
  if (!Item || Item.owner !== subject) {
    reply.code(403).send({ error: 'Forbidden or mapping not found' });
    return;
  }

  // Fetch actual data using mapped keys
  const dataParams = {
    TableName: process.env.POSTS_TABLE,
    Key: {
      pk: Item.pk,
      sk: Item.sk
    }
  };

  const { Item: post } = await ddb.get(dataParams).promise();
  reply.send(post || { error: 'Not found' });
}

These patterns ensure that DynamoDB operations are constrained by the authenticated identity and tenant context, preventing BOLA across user or tenant boundaries. middleBrick can validate that such controls are present by checking whether endpoints require and validate ownership rather than relying solely on client-provided IDs.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Why does exposing DynamoDB keys in URLs increase BOLA risk?
Because predictable or directly exposed keys allow attackers to enumerate and access other users' items when the application layer does not verify that the requesting identity matches the key used in the request.
Does using DynamoDB fine-grained IAM policies alone prevent BOLA?
Not reliably. IAM policies should scope access to the identity's own items, but they must be combined with application-level checks; otherwise a compromised or misconfigured identity could still access other objects if the API does not validate ownership per request.