Credential Stuffing & Brute Force
Credential Stuffing & Brute Force at a glance
Overview
Credential stuffing uses collections of username/password pairs, often from previous breaches, to test for account reuse. Password spraying tries a small set of common passwords across many accounts to avoid triggering account lockouts. Brute force targets single accounts with many guesses. Modern defenses combine per-account and per-IP throttling, device and behavior signals, and step-up MFA to reduce success rates.
Where it occurs
Authentication vulnerabilities typically appear in login, API token, and password handling endpoints, as well as in password reset and OAuth flows susceptible to account enumeration or user deception.
Impact
Successful credential stuffing results in account takeover, data exfiltration, fraud, and lateral attacks. Even failed attempts can increase support load, generate alerts, and indicate targeted campaigns against your customers.
Prevention
Prevent this vulnerability with layered defenses including adaptive authentication, rate limits, lockouts, short-lived tokens, credential breach detection, and normalized responses to block enumeration and large-scale credential abuse.
Examples
Switch tabs to view language/framework variants.
Express, login endpoint allows unlimited attempts (no rate limit)
Public login accepts username/password with no throttling or lockout, enabling credential stuffing attacks.
const express = require('express');
const app = express();
app.use(express.json());
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.findUserByEmail(email);
if (!user) return res.status(401).send('invalid');
const ok = await verifyPassword(user, password);
if (!ok) return res.status(401).send('invalid');
res.json({ token: issueToken(user) });
});- Line 6: No throttling or lockout, unlimited attempts permitted
Without rate limits automated attackers can test millions of credential pairs quickly.
const rateLimit = require('express-rate-limit');
app.use('/login', rateLimit({ windowMs: 15*60*1000, max: 10 }));
app.post('/login', async (req, res) => {
// same as before but with rate limiting and additional device challenge
});- Line 1: Apply rate limiting to login routes to slow automated attacks
Rate limits slow attackers and make credential lists less effective; combine with IP reputation and device telemetry.
Engineer Checklist
-
Apply per-IP and per-account rate limits with adaptive policies
-
Implement progressive delays and temporary lockouts for repeated failures
-
Require step-up MFA for high-risk sign-ins and new devices
-
Normalize error messages and timing to prevent oracles
-
Block or warn on known breached credentials and enforce strong password policies
-
Monitor aggregated failure patterns and block credential lists across the fleet
End-to-End Example
An attacker runs a credential stuffing campaign against /login using 5M leaked credentials. The app has no per-account lockouts and only basic IP throttling that attackers bypass with a botnet.
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return res.status(401).send('invalid credentials');
}
// Bug: no rate limiting or account lockout
const token = generateToken(user.id);
res.json({ token });
});const rateLimit = require('express-rate-limit');
const accountLockout = new Map(); // track failed attempts by email
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests per IP
message: 'Too many login attempts'
});
app.post('/login', loginLimiter, async (req, res) => {
const { email, password } = req.body;
// Check account lockout
const failedAttempts = accountLockout.get(email) || 0;
if (failedAttempts >= 5) {
return res.status(429).send('Account temporarily locked');
}
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
accountLockout.set(email, failedAttempts + 1);
return res.status(401).send('invalid credentials');
}
// Success - clear lockout and issue token
accountLockout.delete(email);
const token = generateToken(user.id);
res.json({ token });
});Discovery
This vulnerability is discovered by testing login endpoints for missing or weak rate limiting, account lockout mechanisms, CAPTCHA challenges, and MFA requirements that would prevent automated credential testing at scale.
-
1. Test rate limiting presence
httpAction
Submit multiple rapid login attempts from a single IP to check for rate limiting
Request
POST https://app.example.com/loginHeaders:Content-Type: application/jsonBody:{ "email": "test@example.com", "password": "wrongpassword" }Response
Status: 200Body:{ "note": "After 20 rapid attempts in 10 seconds, all requests still return 401 with no 429 rate limit response, confirming no IP-based throttling" }Artifacts
http_status response_time_log -
2. Test account lockout mechanism
httpAction
Submit 10 failed login attempts for a single account to check for account-level lockouts
Request
POST https://app.example.com/loginHeaders:Content-Type: application/jsonBody:{ "email": "victim@example.com", "password": "wrongpass" }Response
Status: 200Body:{ "note": "All 10 attempts return 401 with identical response times (~200ms), no lockout or progressive delays observed" }Artifacts
http_status timing_analysis -
3. Verify credential acceptance
httpAction
Test a few known leaked credentials from public breach databases to confirm successful authentication is possible
Request
POST https://app.example.com/loginHeaders:Content-Type: application/jsonBody:{ "email": "known@leaked.com", "password": "Password123" }Response
Status: 200Body:{ "note": "Valid credentials return 200 with authentication token, confirming the vulnerability can lead to account takeover" }Artifacts
http_response_body session_token -
4. Assess distributed attack feasibility
analysisAction
Test from multiple IPs to verify no coordinated attack detection across distributed sources
Request
ANALYSIS N/A - Analysis stepResponse
Status: 200Body:{ "note": "Requests from 5 different IP addresses show no cross-IP correlation or blocking, confirming distributed credential stuffing is feasible" }Artifacts
multi_ip_test_results
Exploit steps
An attacker exploits this by using automated tools to test large collections of username/password combinations from data breaches against login endpoints, taking advantage of password reuse to compromise accounts at scale, often distributing requests across multiple IPs to evade basic rate limiting.
-
1. Acquire credential database
Obtain leaked credentials
analysisAction
Download 5M email:password combinations from public breach databases and underground forums
Request
ANALYSIS N/A - Analysis stepResponse
Status: 200Body:{ "note": "Successfully compiled credential list containing 5,248,392 unique email:password pairs from various historical breaches" }Artifacts
credential_database breach_sources_list -
2. Deploy distributed infrastructure
Setup botnet proxy network
analysisAction
Configure attack infrastructure using residential proxy network with 10,000+ IP addresses across multiple countries to evade IP-based blocking
Request
ANALYSIS N/A - Analysis stepResponse
Status: 200Body:{ "note": "Successfully established distributed testing infrastructure with IP rotation every 50 requests" }Artifacts
proxy_network_config ip_pool -
3. Execute credential stuffing campaign
Automated login testing
httpAction
Run automated credential testing at 500 requests/second distributed across the proxy network
Request
POST https://app.example.com/loginHeaders:Content-Type: application/jsonBody:{ "email": "<EMAIL>", "password": "<PASSWORD>" }Response
Status: 200Body:{ "note": "After 3 hours of testing 1.5M credentials, achieved 2,847 successful logins (0.19% hit rate), obtaining valid authentication tokens for compromised accounts" }Artifacts
successful_logins_list auth_tokens attack_metrics -
4. Exploit compromised accounts
Monetize account access
analysisAction
Use compromised accounts to extract personal data, make fraudulent purchases, or resell account access
Request
ANALYSIS N/A - Analysis stepResponse
Status: 200Body:{ "note": "Compromised 43 premium accounts, 284 accounts with stored payment methods, and extracted $127K in unauthorized transactions before detection" }Artifacts
compromised_account_inventory fraud_transaction_log
Specific Impact
The attacker compromises multiple customer accounts, exfiltrates private data and performs fraudulent actions. A number of high-value accounts without MFA are accessed, resulting in financial loss and reputational damage.
Operationally the incident requires forced password resets, MFA enforcement, review of API logs, and potential notification to affected users and regulators.
Fix
Add layered defenses: rate limits, device and risk checks, and step-up MFA. Monitor patterns that indicate credential lists and block or sinkhole offending IP clusters. Rotate tokens and force reauth for sessions created during the attack window.
Detect This Vulnerability in Your Code
Sourcery automatically identifies credential stuffing & brute force vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free