Broken Authentication with Basic Auth
How Broken Authentication Manifests in Basic Auth
Broken authentication in Basic Auth implementations typically stems from credential mismanagement and improper handling of the Authorization header. When credentials are transmitted as Base64-encoded username:password pairs, several attack vectors become available to malicious actors.
The most common manifestation occurs when developers fail to validate the Authorization header properly. Consider this vulnerable Node.js Express middleware:
app.use((req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const [username, password] = credentials.split(':');
// Missing credential validation!
next();
} else {
res.status(401).send('Missing credentials');
}
});This code accepts any Authorization header without validating credentials against a user store, allowing any Base64-encoded string to bypass authentication.
Another critical issue is credential leakage through error messages. When authentication fails, applications often return detailed error responses:
if (username !== 'admin' || password !== 'secret') {
res.status(401).json({
error: 'Authentication failed',
reason: 'Invalid username or password'
});
}These detailed messages help attackers enumerate valid usernames through timing attacks or brute force attempts.
Session fixation attacks also plague Basic Auth implementations. Since Basic Auth uses stateless credentials rather than session tokens, attackers can pre-generate valid Authorization headers and trick users into authenticating with compromised credentials.
Credential stuffing becomes particularly effective against Basic Auth endpoints. Attackers use databases of known username:password combinations from previous breaches, exploiting the fact that many users reuse credentials across services. A simple Python script demonstrates this attack:
import requests
import base64
import time
def try_credentials(url, username, password):
creds = base64.b64encode(f"{username}:{password}".encode()).decode()
headers = {'Authorization': f'Basic {creds}'}
try:
response = requests.get(url, headers=headers, timeout=5)
return response.status_code == 200
except:
return False
# Credential stuffing attack
with open('common_passwords.txt') as f:
passwords = f.read().splitlines()
for password in passwords:
if try_credentials('https://api.example.com/protected', 'admin', password):
print(f"Found valid password: {password}")
break
time.sleep(1) # Rate limiting bypassMan-in-the-middle attacks become trivial when Basic Auth is used over HTTP instead of HTTPS. The Base64 encoding provides no security—it's merely encoding, not encryption. Network sniffers can easily decode credentials from intercepted traffic.
Basic Auth-Specific Detection
Detecting broken authentication in Basic Auth implementations requires examining both the authentication mechanism and the surrounding security controls. Here's how to systematically identify vulnerabilities:
Authorization Header Analysis
First, verify that the Authorization header is properly validated. Using curl, you can test for authentication bypass:
# Test with empty credentials
curl -v -H "Authorization: Basic " https://api.example.com/protected
# Test with invalid Base64
curl -v -H "Authorization: Basic invalidbase64" https://api.example.com/protected
# Test with valid Base64 but invalid format
curl -v -H "Authorization: Basic YWJj" https://api.example.com/protectedA properly implemented system should reject all three attempts with 401 Unauthorized responses.
Credential Validation Testing
Test for timing attacks by measuring response times for valid vs invalid credentials:
import requests
import time
url = 'https://api.example.com/protected'
def measure_response_time(username, password):
creds = base64.b64encode(f"{username}:{password}".encode()).decode()
headers = {'Authorization': f'Basic {creds}'}
start = time.time()
response = requests.get(url, headers=headers, timeout=5)
elapsed = time.time() - start
return response.status_code, elapsed
# Test with valid and invalid credentials
valid_code, valid_time = measure_response_time('admin', 'correct-password')
invalid_code, invalid_time = measure_response_time('admin', 'wrong-password')
print(f"Valid: {valid_time:.4f}s, Invalid: {invalid_time:.4f}s")Significantly different response times can indicate information leakage through timing analysis.
middleBrick Scanning
middleBrick's authentication scanning specifically targets Basic Auth vulnerabilities. The scanner automatically detects Basic Auth endpoints and tests for:
| Check | Description | Severity |
|---|---|---|
| Credential Validation | Tests if any Base64 string is accepted | Critical |
| Rate Limiting | Checks for brute force protection | High |
| Transport Security | Verifies HTTPS usage | High |
| Error Information Leakage | Analyzes error responses for sensitive data | Medium |
To scan with middleBrick CLI:
npx middlebrick scan https://api.example.com/protected
The scanner returns a security score with specific findings about authentication weaknesses, including whether the endpoint accepts invalid credentials or lacks rate limiting.
Automated Credential Testing
Automated tools can systematically test Basic Auth implementations:
import base64
import itertools
def generate_test_vectors():
# Empty credentials
yield ''
# Invalid Base64
yield 'invalid'
# Valid Base64 with invalid format
yield base64.b64encode('invalid-format'.encode()).decode()
# Common weak credentials
weak_passwords = ['123456', 'password', 'admin', '']
for pwd in weak_passwords:
yield base64.b64encode(f"admin:{pwd}".encode()).decode()
# Test each vector
for vector in generate_test_vectors():
headers = {'Authorization': f'Basic {vector}'}
response = requests.get('https://api.example.com/protected', headers=headers)
if response.status_code == 200:
print(f"Bypass detected with vector: {vector}")This approach helps identify implementations that accept malformed or empty credentials.
Basic Auth-Specific Remediation
Remediating broken authentication in Basic Auth requires implementing proper credential validation, secure transmission, and rate limiting. Here are specific code-level fixes:
Proper Credential Validation
Replace vulnerable authentication middleware with secure implementations:
const bcrypt = require('bcrypt');
const users = require('./user-database'); // { username: { hash, salt } }
function basicAuthMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
try {
const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const [username, password] = credentials.split(':');
if (!username || !password) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
const user = users[username];
if (!user) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
const isValid = bcrypt.compareSync(password, user.hash);
if (!isValid) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
req.user = { username };
next();
} catch (error) {
console.error('Authentication error:', error);
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
}
app.use('/api/protected', basicAuthMiddleware, protectedRoutes);This implementation validates credentials against a secure user database, uses bcrypt for password hashing, and provides consistent error responses to prevent timing attacks.
Rate Limiting Implementation
Prevent brute force attacks with rate limiting:
const rateLimit = require('express-rate-limit');
const authRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per windowMs
message: 'Too many authentication attempts, please try again later.',
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const [username] = credentials.split(':');
return username || req.ip;
}
return req.ip;
}
});
// Apply rate limiting to authentication endpoints
app.use('/api/auth', authRateLimiter);
app.use('/api/protected', basicAuthMiddleware, protectedRoutes);This rate limiter tracks authentication attempts by username or IP address, preventing credential stuffing attacks.
Transport Layer Security
Enforce HTTPS for Basic Auth endpoints:
function enforceHttps(req, res, next) {
if (req.secure || process.env.NODE_ENV === 'development') {
return next();
}
if (req.header('x-forwarded-proto') === 'https') {
return next();
}
res.status(403).json({
error: 'HTTPS required',
message: 'Basic Auth must be used over HTTPS to protect credentials'
});
}
// Apply HTTPS enforcement to protected routes
app.use('/api/protected', enforceHttps, basicAuthMiddleware, protectedRoutes);This middleware ensures Basic Auth credentials are never transmitted over unencrypted channels.
Secure Error Handling
Implement consistent error responses to prevent information leakage:
function basicAuthMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
try {
const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
const [username, password] = credentials.split(':');
if (!username || !password) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
const user = users[username];
if (!user) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
const isValid = bcrypt.compareSync(password, user.hash);
if (!isValid) {
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
req.user = { username };
next();
} catch (error) {
// Always return 401 for authentication failures
return res.status(401).set('WWW-Authenticate', 'Basic realm="Secure Area"').end();
}
}Consistent error responses prevent attackers from distinguishing between invalid usernames and invalid passwords.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |