Timing Attack with Basic Auth
How Timing Attack Manifests in Basic Auth
Timing attacks in Basic Auth exploit the fact that authentication systems often take different amounts of time to process valid versus invalid credentials. When an attacker sends a request with a username and password, the server's response time can leak whether the username exists, even without knowing the password.
// Vulnerable Basic Auth implementation
func authenticateBasicAuth(username, password string) bool {
user, err := db.FindUserByUsername(username)
if err != nil {
return false // User not found
}
// Timing leak: password comparison takes longer if user exists
return comparePasswords(user.PasswordHash, password)
}
The vulnerability occurs because FindUserByUsername returns quickly for non-existent users (database query fails fast), while existing users trigger a full password hash comparison. An attacker can measure response times across many requests to enumerate valid usernames.
Consider this timing pattern:
Request 1: admin / wrongpass → 150ms (user exists, hash comparison)
Request 2: unknown / wrongpass → 5ms (user doesn't exist, early return)
Even small timing differences (100ms vs 5ms) are exploitable when attackers can send thousands of requests. Network jitter can be mitigated by statistical analysis over multiple attempts.
Another variant targets the password comparison itself:
// Vulnerable constant-time comparison
func comparePasswords(a, b string) bool {
if len(a) != len(b) {
return false // Immediate return on length mismatch
}
// Byte-by-byte comparison stops at first mismatch
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
This implementation leaks information through timing: a password with a correct first character takes longer to fail than one with an incorrect first character. An attacker can use this to brute-force passwords character by character.
Basic Auth-Specific Detection
Detecting timing attacks in Basic Auth requires both passive monitoring and active probing. For passive detection, use timing analysis tools that measure response distributions:
#!/usr/bin/env python3
import requests
import time
import statistics
def measure_timing(url, username, password, samples=100):
timings = []
for _ in range(samples):
start = time.time()
r = requests.get(url, auth=(username, password))
elapsed = time.time() - start
timings.append(elapsed)
mean = statistics.mean(timings)
stddev = statistics.stdev(timings)
return mean, stddev
# Compare timing for existing vs non-existing user
existing_user_timing = measure_timing("https://api.example.com/protected", "admin", "wrongpass")
non_existing_user_timing = measure_timing("https://api.example.com/protected", "nonexistent", "wrongpass")
print(f"Existing user: {existing_user_timing[0]:.3f}s ± {existing_user_timing[1]:.3f}s")
print(f"Non-existing: {non_existing_user_timing[0]:.3f}s ± {non_existing_user_timing[1]:.3f}s")
For automated detection, middleBrick's black-box scanning includes timing analysis for Basic Auth endpoints. The scanner sends requests with varying credentials and analyzes response time distributions to identify potential timing leaks.
middleBrick specifically tests:
- Username enumeration through timing analysis (valid vs invalid usernames)
- Password comparison timing (early exit vs constant-time comparison)
- Database query timing differences
- Network-level timing consistency
The scanner generates a timing attack risk score based on statistical significance of timing differences observed across multiple test vectors. Results include specific recommendations for the Basic Auth implementation being tested.
Basic Auth-Specific Remediation
Fixing timing attacks in Basic Auth requires constant-time operations throughout the authentication flow. Start with constant-time user lookup:
// Secure Basic Auth implementation
func authenticateBasicAuth(username, password string) bool {
// Always perform dummy hash comparison
dummyHash := []byte("dummyhash")
// Use constant-time comparison for all paths
validUser := false
user, err := db.FindUserByUsername(username)
if err == nil {
validUser = comparePasswords(user.PasswordHash, password)
}
// Always compare dummy hash to prevent timing leaks
dummyValid := comparePasswords(dummyHash, "dummy")
// Use constant-time boolean combination
return constantTimeSelect(validUser, dummyValid)
}
// Constant-time boolean OR
func constantTimeSelect(a, b bool) bool {
if a {
return true
}
return b
}
// Constant-time password comparison
func comparePasswords(a, b []byte) bool {
if len(a) != len(b) {
// Always perform comparison even if lengths differ
minLen := len(a)
if len(b) < minLen {
minLen = len(b)
}
result := 0
for i := 0; i < minLen; i++ {
result |= int(a[i] ^ b[i])
}
for i := minLen; i < len(a); i++ {
result |= int(a[i])
}
for i := minLen; i < len(b); i++ {
result |= int(b[i])
}
return result == 0
}
result := 0
for i := 0; i < len(a); i++ {
result |= int(a[i] ^ b[i])
}
return result == 0
}
For Go applications, use the crypto/subtle package which provides constant-time comparisons:
import "crypto/subtle"
func secureCompare(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
In Python, use hmac.compare_digest for constant-time comparison:
import hmac
def authenticate_basic_auth(username, password):
# Always fetch user (or dummy user) to equalize timing
user = get_user_by_username(username) or get_dummy_user()
# Constant-time comparison
valid_password = hmac.compare_digest(
user.password_hash,
hash_password(password)
)
return valid_password
Additional protections include adding random delays to mask timing variations and using rate limiting to slow down timing attacks. However, proper constant-time implementation is the fundamental requirement.
Frequently Asked Questions
How can I test if my Basic Auth implementation is vulnerable to timing attacks?
requests in Python, can help identify these patterns.