Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy Brute Force & Rate Limiting
Beginner · 15 min

Brute Force & Rate Limiting

Learn the attacks that exploit missing rate limits and implement layered defenses against automated credential attacks.

1 Brute Force Attack Types

Authentication endpoints without rate limiting are vulnerable to several automated attack types that systematically guess credentials.

Classic brute force: Trying all possible passwords for a known username. Slow but exhaustive — works against short or simple passwords.

Dictionary attack: Testing a wordlist of common passwords (rockyou.txt has 14 million entries). Fast and effective against human-chosen passwords.

Credential stuffing: Using breach databases (billions of pairs). Exploits password reuse across sites.

Password spraying: One common password (like "Winter2024!") tested against many accounts. Avoids per-account lockout. Often successful in corporate environments with predictable password policies.

Reverse brute force: Fix the password, enumerate usernames — useful when a common default password is suspected.

Without rate limiting, a typical server can handle thousands of login attempts per second, making all of these attacks trivially fast.

2 Defenses: Rate Limiting, Lockout, and CAPTCHA

Layer multiple defenses to protect authentication endpoints from automated attacks.

Rate limiting (Express.js):

const rateLimit = require("express-rate-limit");

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 10,                    // max 10 attempts per window
  skipSuccessfulRequests: true,
  handler: (req, res) => {
    res.status(429).json({ error: "Too many login attempts. Try again in 15 minutes." });
  }
});

app.post("/login", loginLimiter, handleLogin);

Progressive delays (account lockout):

DELAYS = [0, 1, 2, 4, 8, 16, 32]  # Seconds of delay per failure count

def login_with_delay(username, password):
    failures = get_failure_count(username)
    if failures >= len(DELAYS):
        raise AccountLocked()
    time.sleep(DELAYS[failures])
    if not verify(username, password):
        increment_failure_count(username)
        raise InvalidCredentials()

Defense checklist:

  • Rate limit: 5-10 attempts per 15 minutes per IP
  • Progressive delays and account lockout after threshold
  • CAPTCHA after 3-5 failed attempts
  • Alert on password spraying patterns
  • Check against HaveIBeenPwned for breached passwords at registration

Knowledge Check

0/3 correct
Q1

Why does password spraying evade per-account lockout policies?

Q2

What HTTP status code should a rate-limited login return?

Q3

Which defense specifically protects against credential stuffing attacks?

Code Exercise

Add Login Rate Limiting

The login endpoint has no rate limiting. Add an in-memory rate limiter that blocks IPs after 5 failed attempts in 15 minutes.

javascript