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 ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |