Api Rate Abuse with Basic Auth
How Api Rate Abuse Manifests in Basic Auth
API rate abuse in Basic Auth environments creates a particularly insidious attack vector because the authentication mechanism itself becomes the primary target. Attackers exploit the predictable nature of Basic Auth headers to launch credential stuffing attacks at scale, rapidly cycling through username/password combinations while simultaneously bombarding the API with requests.
The fundamental problem stems from Basic Auth's stateless design. Each request contains the full Base64-encoded credentials in the Authorization header, making it trivial for attackers to automate credential rotation. A typical attack pattern involves:
- Brute force credential cycling: Attackers send thousands of requests per minute, each with different username:password combinations
- Credential stuffing: Using stolen username/password pairs from data breaches, attackers test these credentials across multiple services
- Enumeration attacks: The predictable 401/403 response codes allow attackers to map valid usernames even without correct passwords
- Resource exhaustion: Rapid-fire requests overwhelm authentication middleware, causing legitimate requests to fail
- Timing attacks: Attackers measure response times to infer whether credentials are valid or if accounts exist
Consider this vulnerable Node.js/Express endpoint:
app.post('/api/data', (req, res) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).json({ error: 'Missing Basic Auth' });
}
const [username, password] = Buffer.from(auth.slice(6), 'base64')
.toString('utf8').split(':');
// NO RATE LIMITING - critical vulnerability
if (username === 'admin' && password === 'password123') {
return res.json({ data: 'sensitive information' });
}
res.status(401).json({ error: 'Invalid credentials' });
});An attacker can exploit this with a simple script:
const axios = require('axios');
const fs = require('fs');
const commonPasswords = fs.readFileSync('passwords.txt', 'utf8').split('\n');
const usernames = ['admin', 'user', 'test', 'demo'];
async function attack() {
for (const user of usernames) {
for (const pass of commonPasswords) {
const credentials = Buffer.from(`${user}:${pass}`).toString('base64');
try {
const response = await axios.post('https://target.com/api/data', {
headers: { Authorization: `Basic ${credentials}` }
});
console.log(`SUCCESS: ${user}:${pass}`);
} catch (error) {
// Continue regardless of failure
}
}
}
}
attack();The absence of rate limiting means this script can send thousands of requests per second, potentially finding valid credentials in minutes rather than hours or days.
Basic Auth-Specific Detection
Detecting rate abuse in Basic Auth systems requires understanding both the authentication mechanism's characteristics and the specific attack patterns it enables. The key indicators include:
Authentication Header Analysis
Basic Auth headers follow a predictable pattern: Basic <base64-encoded-credentials>. This predictability allows for specific detection techniques:
// Detect Basic Auth header patterns
const basicAuthPattern = /^Basic\s+[A-Za-z0-9+/=]+$/;
function isBasicAuthHeader(header) {
return basicAuthPattern.test(header);
}Credential Rotation Detection
Rate abuse often involves rapid credential changes. Detection systems should flag when:
- Multiple unique credentials are used from the same IP address
- Credential changes occur faster than human typing speed (typically >5 changes/minute)
- Same credentials are used across multiple endpoints in rapid succession
Response Pattern Analysis
Basic Auth systems often reveal information through response patterns. A secure implementation should return identical responses for all authentication failures. Deviations indicate potential vulnerabilities:
// Secure response - constant time and content
function authenticateBasicAuth(credentials) {
const [username, password] = Buffer.from(credentials, 'base64').toString('utf8').split(':');
// Constant-time comparison to prevent timing attacks
const valid = secureCompare(username, 'expectedUser') &&
secureCompare(password, 'expectedPass');
// Always return same response time and content
return valid ? successResponse() : failureResponse();
}middleBrick Detection Capabilities
middleBrick's black-box scanning approach specifically tests Basic Auth endpoints for rate abuse vulnerabilities. The scanner:
- Attempts rapid authentication with varied credentials to detect rate limiting absence
- Analyzes response times to identify timing attack vulnerabilities
- Tests for credential enumeration through response analysis
- Checks for consistent failure responses that prevent information leakage
The scanner provides a security score (0-100) with specific findings for Basic Auth implementations, including whether rate limiting is properly enforced and if authentication responses leak information.
Basic Auth-Specific Remediation
Remediating rate abuse in Basic Auth systems requires implementing multiple layers of protection. The most effective approach combines rate limiting, credential management, and response hardening.
Rate Limiting Implementation
Rate limiting should be applied at multiple levels:
// Node.js Express rate limiting for Basic Auth
const rateLimit = require('express-rate-limit');
// Limit total authentication attempts
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // limit each IP to 10 auth attempts
message: 'Too many authentication attempts'
});
// Limit per-credential attempts
const credentialLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 3, // 3 attempts per credential set
keyGenerator: (req) => {
const auth = req.headers.authorization;
return auth ? auth.slice(6) : req.ip; // Use credentials as key
}
});
app.use('/api/auth', authLimiter);
app.use('/api/sensitive', credentialLimiter);
Credential Hardening
Basic Auth's simplicity makes it vulnerable to credential-based attacks. Implement these protections:
// Secure Basic Auth middleware
function secureBasicAuthMiddleware(users) {
const failedAttempts = new Map();
const lockouts = new Map();
return (req, res, next) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).json({ error: 'Authentication required' });
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString('utf8');
const [username, password] = credentials.split(':');
// Check for account lockout
if (lockouts.has(username)) {
const remaining = lockouts.get(username) - Date.now();
if (remaining > 0) {
return res.status(429).json({
error: 'Account locked',
retryAfter: Math.ceil(remaining / 1000)
});
}
lockouts.delete(username);
}
// Validate credentials with constant-time comparison
const user = users.find(u => u.username === username);
if (user && secureCompare(user.password, password)) {
// Reset failed attempts on success
failedAttempts.set(username, 0);
req.user = user;
return next();
}
// Increment failed attempts
const attempts = (failedAttempts.get(username) || 0) + 1;
failedAttempts.set(username, attempts);
// Lock out after 5 failed attempts
if (attempts >= 5) {
lockouts.set(username, Date.now() + 15 * 60 * 1000);
return res.status(429).json({
error: 'Account locked',
retryAfter: 900
});
}
// Always return same response
res.status(401).json({ error: 'Invalid credentials' });
};
}
function secureCompare(a, b) {
let result = 0;
for (let i = 0; i < a.length; ++i) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
Response Hardening
Prevent information leakage through consistent responses:
// Always return same response time and content
function authenticateAndRespond(req, res, users) {
const startTime = Date.now();
const auth = req.headers.authorization;
// Simulate constant processing time
const processingTime = 200; // milliseconds
if (!auth || !auth.startsWith('Basic ')) {
return delayResponse(res, startTime, processingTime, {
error: 'Authentication required'
});
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString('utf8');
const [username, password] = credentials.split(':');
// Constant-time validation
const valid = users.some(user =>
user.username === username && secureCompare(user.password, password)
);
// Always return same response structure
delayResponse(res, startTime, processingTime, {
success: valid,
message: valid ? 'Authenticated' : 'Invalid credentials'
});
}
function delayResponse(res, startTime, targetTime, data) {
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, targetTime - elapsed);
setTimeout(() => {
res.status(200).json(data);
}, remaining);
}
middleBrick Integration
Integrate middleBrick into your development workflow to continuously monitor Basic Auth security:
This GitHub Action automatically scans your API endpoints before merging, ensuring rate abuse vulnerabilities are caught early in the development process.