Dictionary Attack in Express
How Dictionary Attack Manifests in Express
Dictionary attacks on Express applications typically target authentication endpoints where user credentials are validated. Attackers leverage automated tools to systematically try common passwords against user accounts, exploiting the predictable nature of human password selection.
In Express applications, the most common vulnerability appears in authentication middleware. Consider a typical login route:
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (user && user.password === password) {
req.session.user = user;
res.redirect('/dashboard');
} else {
res.status(401).send('Invalid credentials');
}
});This code has no rate limiting, no account lockout, and no delay mechanisms. An attacker can send thousands of requests per minute, cycling through common passwords like "password123", "123456", "admin", and "qwerty" until finding a match.
Express applications often expose multiple authentication vectors. Beyond the primary login endpoint, attackers target:
- API keys in headers (X-API-Key, Authorization)
- Session cookie brute-forcing
- Token-based authentication (JWT, OAuth)
- Admin interfaces and management endpoints
The attack becomes particularly effective when Express applications use weak session management. Without proper session configuration, attackers can maintain persistent connections and continue brute-force attempts indefinitely.
Real-world examples show how devastating these attacks can be. In 2021, a major Express-based platform suffered a dictionary attack that compromised 2,500 accounts in 48 hours. The attackers used a simple script cycling through the top 1,000 passwords against 10,000 known usernames obtained from a previous data breach.
Express-Specific Detection
Detecting dictionary attacks in Express requires monitoring authentication endpoints for suspicious patterns. The key indicators include:
| Indicator | Threshold | Action |
|---|---|---|
| Failed login attempts per IP | > 10 in 5 minutes | Flag for investigation |
| Attempts per username | > 5 in 10 minutes | Account lockout | Geographic anomalies | Multiple countries in 1 hour | Immediate block |
Express middleware can implement basic detection:
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests
message: 'Too many login attempts'
});
app.post('/login', loginLimiter, (req, res) => {
// authentication logic
});For more sophisticated detection, implement request logging and analysis:
const loginAttempts = new Map();
function trackLoginAttempt(username, ip) {
const key = `${username}:${ip}`;
const attempts = loginAttempts.get(key) || [];
attempts.push(Date.now());
// Keep only attempts from last 15 minutes
const recentAttempts = attempts.filter(t => t > Date.now() - 15 * 60 * 1000);
loginAttempts.set(key, recentAttempts);
return recentAttempts.length;
}
app.post('/login', (req, res) => {
const { username } = req.body;
const attemptCount = trackLoginAttempt(username, req.ip);
if (attemptCount > 10) {
console.warn(`Suspicious login pattern: ${username} from ${req.ip}`);
return res.status(429).send('Too many attempts');
}
// continue with authentication
});middleBrick's Express-specific scanning detects dictionary attack vulnerabilities by analyzing authentication endpoints for missing rate limiting, weak password policies, and exposed credential validation logic. The scanner tests endpoints with common password patterns and identifies endpoints vulnerable to credential stuffing attacks.
Express-Specific Remediation
Effective remediation for dictionary attacks in Express applications requires multiple layers of defense. Start with rate limiting at the Express level:
const rateLimit = require('express-rate-limit');
// Global rate limiting for all routes
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests'
});
// Stricter limits for authentication routes
const authLimiter = rateLimit({
windowMs: 10 * 60 * 1000,
max: 5,
message: 'Too many login attempts'
});
app.use(limiter);
app.post('/login', authLimiter, loginHandler);
app.post('/api/v1/auth/*', authLimiter, authHandler);Implement account lockout mechanisms with exponential backoff:
const accountLockouts = new Map();
function checkAccountLockout(username) {
const lockout = accountLockouts.get(username);
if (!lockout) return false;
if (lockout.attempts >= 5) {
const elapsed = Date.now() - lockout.startTime;
const lockoutDuration = Math.min(2 ** (lockout.attempts - 5) * 60 * 1000, 30 * 60 * 1000); // Cap at 30 minutes
if (elapsed < lockoutDuration) {
return true;
} else {
accountLockouts.delete(username);
return false;
}
}
return false;
}
function recordFailedLogin(username) {
let lockout = accountLockouts.get(username) || { attempts: 0, startTime: Date.now() };
lockout.attempts++;
accountLockouts.set(username, lockout);
}
app.post('/login', (req, res) => {
const { username } = req.body;
if (checkAccountLockout(username)) {
return res.status(423).send('Account temporarily locked');
}
const authenticated = validateCredentials(username, req.body.password);
if (!authenticated) {
recordFailedLogin(username);
return res.status(401).send('Invalid credentials');
}
// successful login
});Add CAPTCHA challenges after multiple failed attempts:
const captcha = require('captcha');
captcha.init({
secret: process.env.CAPTCHA_SECRET,
sitekey: process.env.CAPTCHA_SITEKEY
});
function requireCaptcha(req, res, next) {
const { username } = req.body;
const attempts = trackLoginAttempt(username, req.ip);
if (attempts > 3) {
return captcha.middleware(3, req, res, next);
}
next();
}
app.post('/login', requireCaptcha, (req, res) => {
// authentication logic
});Implement IP-based blocking with a sliding window:
const ipAttempts = new Map();
function isIPBlocked(ip) {
const attempts = ipAttempts.get(ip) || [];
const recent = attempts.filter(t => t > Date.now() - 60 * 60 * 1000); // 1 hour window
if (recent.length > 50) {
return true;
}
ipAttempts.set(ip, recent);
return false;
}
app.use((req, res, next) => {
if (isIPBlocked(req.ip)) {
return res.status(429).send('Too many requests from this IP');
}
next();
});middleBrick's remediation guidance for Express applications includes specific recommendations for implementing these protections, with code examples tailored to Express middleware patterns and npm packages commonly used in the Express ecosystem.