Brute Force Attack in Strapi with Basic Auth
Brute Force Attack in Strapi with Basic Auth — how this specific combination creates or exposes the vulnerability
A brute force attack against Strapi using HTTP Basic Authentication leverages the predictable, per-request nature of Basic Auth to iteratively test username and password combinations. In Strapi, the default admin panel and potentially custom public endpoints that are configured for Basic Auth do not inherently enforce account lockout, exponential backoff, or other anti-automation controls. An attacker can send a high volume of credential pairs to the Basic Auth protected endpoint, parsing each HTTP 401 response to learn that a username exists while a 200 or 403 indicates a potential valid credential.
Because Basic Auth transmits credentials in an easily decoded Base64 format (not encryption) with each request, interception risks compound when combined with brute force. If TLS is misconfigured or terminated inconsistently, credentials may be exposed further. Strapi’s permission system may inadvertently expose admin routes or content API routes with Basic Auth, and if rate limiting is not enforced at the edge or application layer, the unauthenticated attack surface remains open for aggressive guessing. The scanner’s 12 checks include Authentication and Rate Limiting, which can detect missing protections such as login attempt throttling, absence of progressive delays, or weak lockout policies that would otherwise mitigate brute force attempts.
In practice, an attacker might target /admin or a custom API route like /api/accounts that uses Basic Auth in Strapi. Without request throttling, account lockout, or CAPTCHA, each request provides feedback via HTTP status codes, enabling rapid iteration over common passwords and credential lists. The scanner’s authentication tests simulate such probing to highlight whether responses reveal account enumeration or permit unchecked guessing. Even when Basic Auth is used only for machine-to-machine scenarios, the absence of IP allowlists or MFA increases the likelihood of successful compromise.
Basic Auth-Specific Remediation in Strapi — concrete code fixes
To mitigate brute force risk when using Basic Auth in Strapi, combine server-side hardening with infrastructure protections. Begin by enforcing strict rate limiting and account lockout at the API level. Strapi does not provide built-in brute force protection for Basic Auth endpoints, so you must implement custom middleware or leverage your hosting environment.
1. Custom Rate Limiting Middleware
Create a custom middleware in Strapi to track failed attempts per IP or API key and introduce progressive delays or temporary bans. The following example uses an in-memory store for simplicity; in production, use Redis or a persistent store.
// src/middlewares/rate-limit-basic-auth/config.js
module.exports = {
settings: {
maxAttempts: 5,
blockDuration: 15 * 60 * 1000, // 15 minutes
keyExtractor: (ctx) => ctx.ip, // or extract a header if behind a proxy
},
};
// src/middlewares/rate-limit-basic-auth/index.js
const { settings } = require('./config');
const attempts = new Map();
module.exports = (config, { strapi }) => ({
async beforestrapi(ctx, next) {
if (ctx.path !== '/admin' && !ctx.path.startsWith('/admin/')) {
return next();
}
const key = settings.keyExtractor(ctx);
const record = attempts.get(key) || { count: 0, firstAttemptAt: Date.now(), blockedUntil: null };
if (record.blockedUntil && Date.now() < record.blockedUntil) {
ctx.status = 429;
ctx.body = { error: 'Too many attempts. Try again later.' };
return;
}
await next();
// If authentication fails, increment count
if (ctx.status === 401) {
record.count += 1;
if (record.count >= settings.maxAttempts) {
record.blockedUntil = Date.now() + settings.blockDuration;
}
attempts.set(key, record);
} else if (ctx.status === 200) {
attempts.delete(key); // reset on success
}
},
});
2. Example Basic Auth Route with Secure Credential Verification
Ensure your Basic Auth handler validates credentials securely and avoids timing attacks where possible. Below is a simplified custom route in Strapi that demonstrates safe comparison and logging for diagnostics without exposing sensitive information in responses.
// src/api/auth-basic-auth/config/routes.js
module.exports = {
routes: [
{
method: 'POST',
path: '/login-basic',
handler: 'auth-basic-auth.login',
config: {
policies: [],
},
},
],
};
// src/api/auth-basic-auth/controllers/auth-basic-auth.js
const bcrypt = require('bcrypt');
module.exports = {
async login(ctx) {
const { authorization } = ctx.request.header;
if (!authorization || !authorization.startsWith('Basic ')) {
return ctx.unauthorized('Invalid authentication method');
}
const base64 = authorization.split(' ')[1];
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
const [username, password] = decoded.split(':');
if (!username || !password) {
return ctx.unauthorized('Invalid credentials format');
}
const user = await strapi.entityService.findOne('api::user.user', username, {
populate: [],
});
if (!user) {
// Delay to mitigate timing differences
await bcrypt.hash(password, 10);
return ctx.unauthorized('Invalid credentials');
}
const passwordMatch = await bcrypt.compare(password, user.password_hash);
if (!passwordMatch) {
return ctx.unauthorized('Invalid credentials');
}
ctx.send({ ok: true, username: user.username });
},
};
3. Infrastructure and Deployment Best Practices
Use a reverse proxy or load balancer to enforce global rate limits and IP allowlists for Basic Auth endpoints. Configure TLS correctly to protect credentials in transit, and avoid using Basic Auth over HTTP. Rotate credentials regularly and prefer short-lived tokens or API keys for machine-to-machine access where feasible. Combine these measures with continuous scanning via the middleBrick CLI or GitHub Action to detect regressions in authentication and rate limiting configurations before deployment.