Rate Limiting Bypass in Adonisjs
How Rate Limiting Bypass Manifests in Adonisjs
Rate limiting bypass in Adonisjs applications often occurs through specific framework behaviors and middleware misconfigurations. The most common attack pattern involves exploiting the default request ID generation mechanism used by Adonisjs's rate limiting middleware.
By default, Adonisjs's @adonisjs/rate-limiter middleware uses the client's IP address as the request identifier. Attackers can bypass this by rotating through different IP addresses using proxy services, cloud instances, or residential proxy networks. This is particularly effective against APIs that don't implement proper client authentication alongside rate limiting.
Another Adonisjs-specific bypass vector involves the redis store configuration. If your application uses Redis for rate limiting but doesn't configure proper key prefixes, multiple applications or environments might share the same rate limiting namespace. An attacker could potentially trigger rate limits in one environment that affect another, creating denial-of-service conditions.
Code that appears secure but contains bypass vulnerabilities often looks like this:
const { BaseController } = require('@adonisjs/lucid/src/Controllers/Http/Controller');
const RateLimit = use('Adonis/Addons/RateLimiter');
class UserController extends BaseController {
async login({ request, response }) {
await RateLimit.limit('login-attempts').perMinute(5).allow(request.ip());
// Authentication logic here
}
}
This implementation is vulnerable because it only rate limits by IP. A sophisticated attacker can rotate through thousands of IP addresses, each getting 5 attempts per minute, effectively bypassing the intended protection.
Rate limiting bypass can also occur through race conditions in Adonisjs middleware execution. If your application has multiple rate limiting rules that execute asynchronously, an attacker might trigger all limits simultaneously before they're properly enforced.
Adonisjs-Specific Detection
Detecting rate limiting bypass in Adonisjs applications requires examining both the runtime behavior and the middleware configuration. The first step is to audit your middleware stack in start/kernel.js to understand how rate limiting is applied across your routes.
Look for patterns like this in your middleware configuration:
const globalMiddleware = [
'Adonis/Middleware/BodyParser',
'Adonis/Middleware/Cors',
'Adonis/Middleware/RateLimiter',
];
Without proper client identification, this global rate limiting is easily bypassed. You should verify that your rate limiting middleware uses more than just IP addresses for request identification.
middleBrick's scanning approach specifically targets Adonisjs rate limiting vulnerabilities by testing multiple bypass techniques:
- IP rotation through proxy headers and multiple connections
- Authentication bypass attempts to see if unauthenticated endpoints have different rate limits than authenticated ones
- Concurrent request testing to identify race condition vulnerabilities
- API key rotation testing if your application uses API keys for rate limiting
The scanner examines your Adonisjs application's response patterns to detect inconsistent rate limiting behavior. For example, if an endpoint returns different rate limit headers or error messages for similar request patterns, this indicates potential bypass opportunities.
middleBrick also analyzes your Adonisjs middleware configuration files to identify weak rate limiting rules. The scanner looks for:
RateLimit.limit('endpoint').perMinute(60).allow(request.ip())
This pattern is flagged as vulnerable because it only uses IP-based identification. The scanner recommends stronger identification methods like combining IP with user agent, API keys, or authenticated user IDs.
For Adonisjs applications using Redis, middleBrick checks for proper namespace isolation and key prefix configuration to prevent cross-application rate limiting interference.
Adonisjs-Specific Remediation
Securing Adonisjs applications against rate limiting bypass requires implementing multi-factor request identification and proper middleware configuration. The most effective approach combines IP-based limiting with application-specific identifiers.
Here's a secure Adonisjs rate limiting implementation:
const { BaseController } = require('@adonisjs/lucid/src/Controllers/Http/Controller');
const RateLimit = use('Adonis/Addons/RateLimiter');
class SecureController extends BaseController {
async secureEndpoint({ request, auth }) {
const userId = auth.user?.id || 'guest';
const ip = request.ip();
const userAgent = request.header('User-Agent') || 'unknown';
const requestId = `${userId}:${ip}:${userAgent.substring(0, 20)}`;
await RateLimit.limit('secure-endpoint')
.perMinute(10)
.allow(requestId);
// Your endpoint logic here
}
}
This implementation creates a composite identifier that combines user identity, IP address, and a truncated user agent string. Even if an attacker rotates IP addresses, they would need to also rotate user agents and potentially user accounts to fully bypass rate limiting.
For Adonisjs applications using API keys, implement rate limiting at the API key level:
class ApiController extends BaseController {
async apiEndpoint({ request, auth }) {
const apiKey = request.header('x-api-key');
const ip = request.ip();
const requestId = `${apiKey}:${ip}`;
await RateLimit.limit('api-endpoint')
.perMinute(100)
.allow(requestId);
// API logic here
}
}
This approach ensures that rate limits are tied to specific API keys rather than just IP addresses, preventing bypass through IP rotation.
Configure your Adonisjs Redis store with proper namespace isolation:
const redisConfig = {
connection: process.env.REDIS_URL || 'redis://localhost:6379',
keyPrefix: 'myapp:' + process.env.NODE_ENV + ':rate-limit:',
db: 0,
port: 6379,
host: '127.0.0.1',
password: process.env.REDIS_PASSWORD,
family: 4,
};
The key prefix ensures that rate limiting data from different environments or applications doesn't interfere with each other.
For production Adonisjs applications, implement sliding window rate limiting rather than fixed window limiting to prevent burst attacks that exploit window boundaries:
await RateLimit.limit('sliding-window')
.perMinute(60)
.slidingWindow()
.allow(requestId);
This configuration distributes rate limiting enforcement more evenly across time, making it harder for attackers to find timing-based bypass opportunities.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |