Api Rate Abuse in Express
How Api Rate Abuse Manifests in Express
Rate abuse in Express applications typically emerges through inadequate rate limiting controls that allow attackers to overwhelm endpoints with excessive requests. In Express, this vulnerability often appears in authentication endpoints, password reset flows, and API routes handling sensitive operations.
The most common Express-specific manifestation occurs when developers rely solely on middleware like express-rate-limit without considering distributed architectures or shared resources. For example, a password reset endpoint might look like this:
app.post('/api/reset-password', async (req, res) => {
const { email } = req.body;
// No rate limiting on this critical endpoint
const user = await User.findOne({ email });
if (user) {
await sendResetEmail(user);
}
res.status(200).json({ message: 'If email exists, reset link sent' });
});This pattern creates multiple attack vectors. An attacker can trigger password reset emails for arbitrary accounts, causing account lockout through email flooding. The lack of rate limiting on this endpoint means an attacker can submit thousands of requests per minute, potentially overwhelming your email service or database.
Another Express-specific scenario involves improper use of req.ip for rate limiting. When applications run behind proxies or load balancers, req.ip may return the proxy's IP address rather than the client's, allowing attackers to bypass rate limits entirely:
// Vulnerable - doesn't account for proxy headers
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});Express applications are particularly susceptible to rate abuse in microservices architectures where multiple instances handle requests. Without centralized rate limiting, an attacker can distribute requests across instances, effectively bypassing per-instance limits. This becomes critical in Express applications using clustering or when deployed behind multiple containers.
Business logic endpoints in Express are also frequent targets. Consider an e-commerce application with a price validation endpoint:
app.post('/api/validate-discount', (req, res) => {
const { productId, discountCode } = req.body;
// No rate limiting on discount validation
const isValid = validateDiscount(productId, discountCode);
res.json({ valid: isValid });
});Attackers can abuse this endpoint to discover valid discount codes through brute force, especially if the endpoint provides different response times or error messages for valid versus invalid codes.
Express-Specific Detection
Detecting rate abuse in Express applications requires both manual code review and automated scanning. middleBrick's Express-specific detection focuses on identifying unprotected endpoints and analyzing rate limiting configurations.
middleBrick scans Express applications by sending controlled request bursts to endpoints and measuring response patterns. For authentication endpoints, it tests for rate limiting bypass by varying request headers, IP addresses, and payload patterns. The scanner identifies Express-specific vulnerabilities like:
- Endpoints missing
express-rate-limitmiddleware - Improper
req.iphandling behind proxies - Rate limiting configurations with excessive thresholds
- Missing rate limits on sensitive operations (password reset, email verification)
The scanning process includes testing Express's built-in middleware stack. For example, middleBrick verifies whether applications properly configure trust proxy settings:
// What middleBrick checks for
app.set('trust proxy', 1); // Trust first proxy
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests from this IP'
});middleBrick also tests for distributed rate limiting vulnerabilities by simulating requests from multiple IP addresses and analyzing whether the application maintains consistent rate limiting across instances. This is particularly important for Express applications deployed in containerized environments.
For LLM/AI security integration, middleBrick's Express-specific scanning includes testing AI endpoints for rate abuse patterns. Many Express applications now serve AI models through endpoints like /api/chat or /api/generate. These endpoints are prime targets for rate abuse as attackers can exploit them to consume API credits or cause denial of service:
app.post('/api/chat', async (req, res) => {
const { prompt } = req.body;
// No rate limiting on AI endpoint
const response = await aiModel.generate(prompt);
res.json({ response });
});middleBrick's active probing tests these endpoints with rapid sequential requests, checking for proper rate limiting and identifying potential cost exploitation vectors.
Express-Specific Remediation
Remediating rate abuse in Express requires a layered approach combining proper middleware configuration, distributed rate limiting, and careful consideration of proxy setups. The foundation is proper express-rate-limit configuration:
const rateLimit = require('express-rate-limit');
// Configure for production with proxy support
app.set('trust proxy', 1);
// Global rate limiter for most endpoints
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests
standardHeaders: true, // Return rate limit info
legacyHeaders: false, // Disable X-RateLimit headers
message: 'Too many requests from this IP'
});
// Apply globally
app.use(globalLimiter);
// Stricter limits for sensitive endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 attempts for auth
skipSuccessfulRequests: true // Only limit failed attempts
});
// Apply to auth routes
app.post('/api/login', authLimiter, loginHandler);
app.post('/api/reset-password', authLimiter, resetPasswordHandler);For distributed Express applications, implement Redis-based rate limiting to share state across instances:
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
const distributedLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rateLimit:'
}),
windowMs: 15 * 60 * 1000,
max: 100
});
app.use(distributedLimiter);Express applications serving AI endpoints require special consideration for cost-based rate limiting. Implement token-based limits that consider both request count and computational cost:
const aiLimiter = rateLimit({
store: new RedisStore({ client: redisClient }),
keyGenerator: (req) => {
// Consider user ID and endpoint for AI endpoints
return `ai:${req.user?.id || req.ip}:${req.path}`;
},
windowMs: 60 * 60 * 1000, // 1 hour
max: 100, // Max 100 requests
handler: (req, res, next) => {
// Custom handler to return cost information
res.status(429).json({
error: 'Rate limit exceeded',
message: 'You have exceeded your AI request quota for this hour'
});
}
});
app.post('/api/chat', aiLimiter, async (req, res) => {
const { prompt } = req.body;
// Track cost and implement secondary limits if needed
const cost = await estimateCost(prompt);
if (cost > MAX_COST_PER_REQUEST) {
return res.status(400).json({ error: 'Request too expensive' });
}
const response = await aiModel.generate(prompt);
res.json({ response });
});For comprehensive protection, combine rate limiting with request validation and circuit breaking patterns. Express middleware can implement these additional safeguards:
// Request size limiting
app.use(express.json({ limit: '10kb' }));
// Custom middleware for business logic rate limiting
function businessLogicLimiter(maxRequests) {
const requests = new Map();
return (req, res, next) => {
const now = Date.now();
const key = req.user?.id || req.ip;
if (!requests.has(key)) {
requests.set(key, []);
}
const userRequests = requests.get(key);
userRequests.push(now);
// Remove requests older than 1 minute
while (userRequests.length > 0 && userRequests[0] < now - 60000) {
userRequests.shift();
}
if (userRequests.length > maxRequests) {
return res.status(429).json({
error: 'Too many operations',
message: `You can only perform ${maxRequests} operations per minute`
});
}
next();
};
}
// Apply to business logic endpoints
app.post('/api/transfer', businessLogicLimiter(10), transferHandler);