HIGH bola idorkoaapi keys

Bola Idor in Koa with Api Keys

Bola Idor in Koa with Api Keys — how this specific combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA) occurs when an API fails to enforce authorization checks at the object level, allowing one user to read or modify another user's resources. Using API keys in a Koa application can inadvertently enable BOLA when the key is treated as a sufficient authorization proof without verifying that the resource being accessed belongs to the key owner.

In Koa, API keys are often passed via headers (e.g., x-api-key) and used to identify an integration or service account. If the endpoint resolves the key to a tenant or user ID but then directly uses user-supplied identifiers (e.g., :id in the route) to locate a resource without validating ownership, the authorization boundary collapses. For example, an endpoint like /orgs/:orgId/members/:memberId might use the API key to identify the org but then allow any memberId to be supplied, enabling horizontal privilege escalation across members of the same org.

Consider a route that fetches a user profile by ID without tying the lookup to the authenticated key holder:

const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();

// In-memory stores for example purposes
const apiKeysToOrg = {
  'key-org-a': 'org-a',
  'key-org-b': 'org-b',
};
const usersByOrg = {
  'org-a': [{ id: 'u1', name: 'alice' }, { id: 'u2', name: 'bob' }],
  'org-b': [{ id: 'u3', name: 'carol' }],
};

router.get('/users/:id', async (ctx) => {
  const apiKey = ctx.request.header['x-api-key'];
  const org = apiKeysToOrg[apiKey];
  if (!org) {
    ctx.status = 401;
    return;
  }
  const userId = ctx.params.id;
  // BOLA: No check that the user belongs to org derived from apiKey
  const user = usersByOrg[org].find((u) => u.id === userId);
  if (!user) {
    ctx.status = 404;
    return;
  }
  ctx.body = user;
});

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

Here, an attacker with a valid API key for org-a can request /users/u3 and receive data if the route does not confirm that u3 is part of org-a. The API key proves access to the org but does not prove access to the specific user, creating an authorization bypass.

BOLA with API keys becomes more likely when identifiers are predictable (e.g., sequential IDs) and when APIs expose nested resources without validating path ownership. This maps to common attack patterns such as OWASP API Top 10 2027 — Broken Access Control and can be discovered during middleBrick scans that compare spec definitions with runtime behavior, highlighting routes where resource ownership is not enforced.

Api Keys-Specific Remediation in Koa — concrete code fixes

To prevent BOLA in Koa when using API keys, tie every resource access check to the identity derived from the key and enforce ownership for each object-level operation. Avoid relying on the key alone to authorize actions on arbitrary IDs.

Remediation pattern: resolve the key to an owner (tenant or user set), then ensure any user-supplied object ID is a member of that owner’s scope before proceeding.

Corrected example with explicit ownership validation:

const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();

const apiKeysToOrg = {
  'key-org-a': 'org-a',
  'key-org-b': 'org-b',
};
const usersByOrg = {
  'org-a': [{ id: 'u1', name: 'alice' }, { id: 'u2', name: 'bob' }],
  'org-b': [{ id: 'u3', name: 'carol' }],
};

// Middleware to attach org and allowedUserIds based on API key
async function authByKey(ctx, next) {
  const apiKey = ctx.request.header['x-api-key'];
  const org = apiKeysToOrg[apiKey];
  if (!org) {
    ctx.status = 401;
    ctx.body = { error: 'invalid_api_key' };
    return;
  }
  const allowedUserIds = usersByOrg[org] ? usersByOrg[org].map((u) => u.id) : [];
  ctx.state.org = org;
  ctx.state.allowedUserIds = allowedUserIds;
  await next();
}

// Use the middleware and enforce ownership
router.get('/users/:id', authByKey, (ctx) => {
  const userId = ctx.params.id;
  if (!ctx.state.allowedUserIds.includes(userId)) {
    ctx.status = 403;
    ctx.body = { error: 'access_denied' };
    return;
  }
  const user = usersByOrg[ctx.state.org].find((u) => u.id === userId);
  ctx.body = user;
});

// Example for modifying a user within the allowed scope
router.patch('/users/:id', authByKey, (ctx) => {
  const userId = ctx.params.id;
  if (!ctx.state.allowedUserIds.includes(userId)) {
    ctx.status = 403;
    ctx.body = { error: 'access_denied' };
    return;
  }
  // perform update on usersByOrg[ctx.state.org] ...
  ctx.body = { message: 'updated' };
});

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

Key practices:

  • Always resolve the API key to an owner or scope, and derive an allowlist of resource IDs that belong to that owner.
  • For each route that accepts an object identifier, assert that the identifier exists within the allowed set derived from the key.
  • Use consistent IDs (e.g., UUIDs) to avoid enumeration and ensure that path parameters are validated against ownership rather than trusting client-supplied positions in arrays.

These steps reduce the risk of BOLA by ensuring that API keys are not used as a blanket authorization token and that object-level permissions remain enforced.

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

Can API keys alone prevent BOLA if I use scopes?
Scopes can reduce impact but do not prevent BOLA on their own. You must still validate that each object ID belongs to the scope derived from the key; otherwise, predictable IDs can be traversed horizontally.
How can I test my Koa endpoints for BOLA with API keys?
Use a black-box scan such as middleBrick to submit requests with different IDs while rotating API keys. Verify that access is denied when a key does not own the target resource and that the endpoint enforces ownership checks consistently.