HIGH dictionary attackexpress

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:

IndicatorThresholdAction
Failed login attempts per IP> 10 in 5 minutesFlag for investigation
Attempts per username> 5 in 10 minutesAccount lockout
Geographic anomaliesMultiple countries in 1 hourImmediate 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.

Frequently Asked Questions

How can I test if my Express application is vulnerable to dictionary attacks?
Use middleBrick's free scanner to analyze your authentication endpoints. It tests for missing rate limiting, weak password policies, and exposed credential validation logic. The scanner simulates dictionary attack patterns and provides specific findings with severity levels and remediation steps.
What's the difference between rate limiting and account lockout for preventing dictionary attacks?
Rate limiting controls the number of requests from an IP address across all users, while account lockout specifically targets suspicious activity on individual user accounts. Rate limiting prevents volumetric attacks, while account lockout stops credential stuffing attempts. Both should be implemented together for comprehensive protection.