HIGH bola idorstrapibasic auth

Bola Idor in Strapi with Basic Auth

Bola Idor in Strapi with Basic Auth — how this specific combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper access controls such that one user can view or modify the resource of another user. In Strapi, content types are typically exposed as REST or GraphQL endpoints, and each record is identified by a primary key (e.g., /articles/42). When Basic Auth is used, Strapi can authenticate the request but does not automatically enforce object-level ownership or tenant boundaries if the developer does not add explicit checks. This creates a BOLA vector: an authenticated user can iterate through numeric IDs and access records they should not see or edit.

Consider a Strapi API with a content type article and a route GET /articles/:id. If the route handler returns the article whenever a valid Basic Auth credential is provided, but does not verify that the authenticated user owns or is allowed to access that specific article ID, the endpoint is vulnerable. An attacker with valid credentials (e.g., a low-privilege account) can change :id to enumerate other users' data. Strapi’s default behavior does not apply per-user scoping unless explicitly configured, so this is a developer responsibility.

Basic Auth transmits credentials in an encoded (not encrypted) header; while this is not inherently unsafe over HTTPS, it does not prevent BOLA. The vulnerability is about authorization, not authentication. Even with Basic Auth providing a username and password, Strapi does not automatically scope database queries to the authenticated user’s records. Without additional middleware or policy logic, an attacker can systematically request /articles/1, /articles/2, and so on to harvest sensitive information (e.g., private notes, PII, internal references).

In practice, BOLA with Basic Auth in Strapi can manifest in update endpoints as well. A PUT /articles/:id that only checks authentication but not ownership allows a user to modify another user’s article by guessing IDs. This is a common root cause in APIs that rely on opaque IDs without verifying relationships. The risk is compounded if the API exposes predictable numeric IDs and lacks rate limiting or per-request authorization checks.

To detect this with a tool like middleBrick, an unauthenticated scan would not find the issue, but an authenticated scan using Basic Auth credentials can exercise the endpoints and observe whether different users can access or modify each other’s resources. The scanner evaluates whether responses for different IDs return data when they should be restricted, and whether authorization checks are consistently applied across CRUD operations.

Basic Auth-Specific Remediation in Strapi — concrete code fixes

Remediation centers on ensuring every data access is scoped to the authenticated user. Strapi policies and services provide the hooks to implement these checks. Below are concrete, working examples for Strapi v4 (the current major version as of this writing).

1. Scoping find queries by user

Assume you have a user collection and an article content type, where each article has a relation to a user (owner). Define a policy that adds a user filter to queries so users only see their own articles.

// src/policies/is-owner-or-public.js
module.exports = async (ctx, next) => {
  const { user } = ctx.state.aptee; // authenticated user from auth provider
  if (!user) {
    ctx.throw(401, 'Unauthorized');
  }
  // If querying for a specific entry by ID, ensure ownership
  if (ctx.params.id) {
    const entry = await strapi.entityService.findOne('api::article.article', ctx.params.id);
    if (!entry || entry.user.id !== user.id) {
      ctx.throw(403, 'Forbidden: You do not own this resource');
    }
  }
  // For list queries, automatically scope to the authenticated user
  ctx.query.filters = { ...(ctx.query.filters || {}), user: { id: user.id } };
  await next();
};

Register this policy in the role/permission editor for the relevant controller and apply it to the routes that handle article access.

2. Scoping create/update/delete by user

When creating or updating, validate that the submitted ID (if any) matches the authenticated user, or set the user server-side.

// In a controller action or lifecycle hook
async update(ctx) {
  const { id } = ctx.params;
  const { user } = ctx.state.aptee;
  const entry = await strapi.entityService.findOne('api::article.article', id);
  if (!entry || entry.user.id !== user.id) {
    ctx.throw(403, 'Forbidden: Cannot update another user’s resource');
  }
  // Proceed with update
  return await strapi.entityService.update('api::article.article', id, { data: ctx.request.body });
}

For creation, set the user relation automatically instead of trusting client input:

async create(ctx) {
  const { user } = ctx.state.aptee;
  // Do not allow client to set user; enforce server-side
  const article = await strapi.entityService.create('api::article.article', {
    data: {
      ...ctx.request.body,
      user: [{ id: user.id }], // or { id: user.id } depending on relation type
    },
  });
  return article;
}

3. Using middleware or core controllers with ownership checks

If you use Strapi’s default REST routes, you can extend the core controllers to add ownership validation. Example for the ArticleController:

// src/extensions/article/controllers/article.js
'use strict';
const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::article.article', ({ strapi }) => ({
  async findOne(ctx) {
    const { user } = ctx.state.aptee;
    const { id } = ctx.params;
    const entry = await strapi.db.query('api::article.article').findOne({
      where: { id, user: { id: user.id } },
      populate: { user: true },
    });
    if (!entry) {
      return ctx.notFound();
    }
    return entry;
  },
}));

Ensure your authentication provider populates ctx.state.aptee (or the relevant state key) with the user object derived from Basic Auth credentials. This guarantees that every route can safely assume a user identity and enforce ownership at the data layer.

These changes ensure that even with Basic Auth, access to each object is restricted to its rightful owner, mitigating BOLA risks. Combine this with HTTPS to protect credentials in transit, and consider adding logging for suspicious access patterns.

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

Does Basic Auth alone prevent BOLA in Strapi?
No. Basic Auth only verifies identity; it does not enforce object-level permissions. Without scoping queries to the authenticated user’s records, BOLA remains possible.
Can middleBrick detect BOLA with Basic Auth credentials?
Yes. An authenticated scan using Basic Auth credentials can exercise endpoints and reveal whether users can access or modify resources they should not, helping identify BOLA issues.