Timing Vulnerability Patterns
This document describes common timing vulnerability patterns and how to detect them.
Pattern 1: Missing Expiration Checks
Description
Code sets an expiration time but never validates it before use.
Example (Python)
# VULNERABLE: Token has expiration but it's never checked
def validate_token(token):
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
user_id = decoded['user_id']
# Missing: Check decoded['exp'] against current time
return user_id
# SECURE: Properly check expiration
def validate_token(token):
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
# jwt.decode automatically checks 'exp' claim
return decoded['user_id']
except jwt.ExpiredSignatureError:
raise AuthenticationError("Token expired")
Detection
- Search for token/session creation with expiration field
- Verify corresponding validation code checks expiration
- Look for:
exp,expires_at,valid_until,expiry
Example (JavaScript)
// VULNERABLE: Session has timeout but never enforced
class Session {
constructor(userId) {
this.userId = userId;
this.createdAt = Date.now();
this.expiresAt = Date.now() + (30 * 60 * 1000); // 30 min
}
isValid() {
// Missing: Check if Date.now() > this.expiresAt
return this.userId != null;
}
}
// SECURE: Check expiration
class Session {
constructor(userId) {
this.userId = userId;
this.createdAt = Date.now();
this.expiresAt = Date.now() + (30 * 60 * 1000);
}
isValid() {
return this.userId != null && Date.now() < this.expiresAt;
}
}
Pattern 2: Excessive Expiration Times
Description
Security-critical tokens/sessions with unreasonably long expiration times.
Example (Python)
# VULNERABLE: Password reset token valid for 7 days
def create_reset_token(user):
token = secrets.token_urlsafe(32)
expiry = datetime.now() + timedelta(days=7) # Too long!
save_reset_token(user, token, expiry)
return token
# SECURE: Reasonable 1-hour expiration
def create_reset_token(user):
token = secrets.token_urlsafe(32)
expiry = datetime.now() + timedelta(hours=1) # Appropriate
save_reset_token(user, token, expiry)
return token
Detection
- Look for
timedelta,setTimeout,Durationwith large values - Check: days > 1 for reset tokens, hours > 24 for sessions
- Keywords:
days=,hours=,minutes=,expires_in
Example (Java)
// VULNERABLE: JWT access token valid for 30 days
public String generateAccessToken(User user) {
return Jwts.builder()
.setSubject(user.getId())
.setExpiration(new Date(System.currentTimeMillis() +
30L * 24 * 60 * 60 * 1000)) // 30 days - too long!
.signWith(key)
.compact();
}
// SECURE: 15-minute expiration
public String generateAccessToken(User user) {
return Jwts.builder()
.setSubject(user.getId())
.setExpiration(new Date(System.currentTimeMillis() +
15L * 60 * 1000)) // 15 minutes
.signWith(key)
.compact();
}
Pattern 3: No Rate Limiting
Description
Security-sensitive operations lack rate limiting or throttling.
Example (Python/Flask)
# VULNERABLE: No rate limiting on login
@app.route('/login', methods=['POST'])
def login():
username = request.json['username']
password = request.json['password']
user = authenticate(username, password)
if user:
return {'token': generate_token(user)}
return {'error': 'Invalid credentials'}, 401
# SECURE: Rate limiting with time window
from flask_limiter import Limiter
limiter = Limiter(app, key_func=lambda: request.remote_addr)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per 15 minutes") # 5 attempts per 15-min window
def login():
username = request.json['username']
password = request.json['password']
user = authenticate(username, password)
if user:
return {'token': generate_token(user)}
return {'error': 'Invalid credentials'}, 401
Detection
- Find authentication endpoints without rate limiting decorators
- Look for:
@limiter.limit,@RateLimit,RateLimiter - Check password reset, OTP generation, API endpoints
Example (Express.js)
// VULNERABLE: No rate limiting
app.post('/api/reset-password', async (req, res) => {
const { email } = req.body;
await sendPasswordResetEmail(email);
res.json({ message: 'Reset email sent' });
});
// SECURE: Rate limiting
const rateLimit = require('express-rate-limit');
const resetLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 3, // 3 requests per window
message: 'Too many reset requests'
});
app.post('/api/reset-password', resetLimiter, async (req, res) => {
const { email } = req.body;
await sendPasswordResetEmail(email);
res.json({ message: 'Reset email sent' });
});
Pattern 4: Client-Side Expiration Only
Description
Expiration checked only on client side, not enforced server-side.
Example (JavaScript + Python)
// VULNERABLE: Client-side expiration check
function isTokenValid(token) {
const decoded = JSON.parse(atob(token.split('.')[1]));
return Date.now() < decoded.exp * 1000; // Client-side only!
}
// Server doesn't check expiration
@app.route('/api/data')
def get_data():
token = request.headers.get('Authorization')
decoded = jwt.decode(token, options={"verify_signature": False})
# Missing: Expiration verification
return get_user_data(decoded['user_id'])
Detection
- Find JWT decode with
verify=Falseorverify_signature=False - Look for expiration checks in frontend code without backend validation
- Check if server validates
expclaim
Pattern 5: Race Conditions in Expiration
Description
Time-of-check to time-of-use (TOCTOU) vulnerabilities in expiration checks.
Example (Python)
# VULNERABLE: Race condition
def use_token(token):
if is_token_expired(token): # Check time
raise TokenExpiredError()
# Time passes here - token might expire
time.sleep(0.1) # Simulating processing
# Use token - might be expired now
return perform_action(token) # Use time
# SECURE: Single atomic check
def use_token(token):
# Check and use in single operation
decoded = jwt.decode(token, SECRET_KEY) # Checks exp atomically
return perform_action(decoded)
Detection
- Look for separate expiration check and token use
- Check for time gaps between validation and use
- Look for: check → delay → use patterns
Pattern 6: Hardcoded Timeouts
Description
Security timeouts hardcoded without configuration or documentation.
Example (Python)
# VULNERABLE: Magic number timeout
def create_session(user):
session_id = generate_session_id()
expiry = datetime.now() + timedelta(seconds=86400) # What is 86400?
save_session(session_id, user, expiry)
return session_id
# SECURE: Named constant with documentation
# Session timeout: 24 hours for general users
SESSION_TIMEOUT_SECONDS = 24 * 60 * 60
def create_session(user):
session_id = generate_session_id()
expiry = datetime.now() + timedelta(seconds=SESSION_TIMEOUT_SECONDS)
save_session(session_id, user, expiry)
return session_id
Detection
- Look for magic numbers in time calculations
- Check for:
3600,86400,604800(common second values) - Verify timeouts are configurable and documented
Pattern 7: Inconsistent Timeout Enforcement
Description
Different parts of the system enforce different timeouts for the same resource.
Example (Microservices)
# Service A: 30-minute session timeout
SESSION_TIMEOUT = timedelta(minutes=30)
# Service B: 1-hour session timeout (inconsistent!)
SESSION_TIMEOUT = timedelta(hours=1)
# Service C: No timeout check at all
def validate_session(session_id):
session = get_session(session_id)
return session is not None # Missing expiration check
Detection
- Search for timeout constants across codebase
- Compare values for same resource type
- Check if all services validate expiration
Pattern 8: No Cleanup of Expired Data
Description
Expired tokens/sessions remain in database without cleanup.
Example (Python)
# VULNERABLE: Expired tokens accumulate
def validate_reset_token(token):
record = db.query(ResetToken).filter_by(token=token).first()
if not record:
return None
if datetime.now() > record.expires_at:
return None # Expired but not deleted
return record.user_id
# SECURE: Clean up expired tokens
def validate_reset_token(token):
# Delete expired tokens first
db.query(ResetToken).filter(
ResetToken.expires_at < datetime.now()
).delete()
db.commit()
record = db.query(ResetToken).filter_by(token=token).first()
if not record:
return None
return record.user_id
# Better: Scheduled cleanup job
def cleanup_expired_tokens():
"""Run periodically (e.g., hourly cron job)"""
db.query(ResetToken).filter(
ResetToken.expires_at < datetime.now()
).delete()
db.commit()
Detection
- Check if expired records are deleted
- Look for cleanup jobs or TTL indexes
- Verify database doesn't accumulate expired data
Pattern 9: Timezone Issues
Description
Mixing timezone-aware and timezone-naive datetimes causes incorrect expiration.
Example (Python)
# VULNERABLE: Timezone confusion
def create_token(user):
# Server in UTC, but using local time
expiry = datetime.now() + timedelta(hours=1) # Local time!
token = jwt.encode({
'user_id': user.id,
'exp': expiry.timestamp() # Converted to UTC
}, SECRET_KEY)
return token
# SECURE: Always use UTC
def create_token(user):
expiry = datetime.utcnow() + timedelta(hours=1) # UTC
token = jwt.encode({
'user_id': user.id,
'exp': expiry.timestamp()
}, SECRET_KEY)
return token
# Better: Use timezone-aware datetimes
from datetime import timezone
def create_token(user):
expiry = datetime.now(timezone.utc) + timedelta(hours=1)
token = jwt.encode({
'user_id': user.id,
'exp': expiry.timestamp()
}, SECRET_KEY)
return token
Detection
- Look for
datetime.now()vsdatetime.utcnow() - Check for timezone-naive datetime usage
- Verify consistent timezone handling
Pattern 10: Timing Attack Vulnerabilities
Description
Non-constant-time comparisons leak timing information.
Example (Python)
# VULNERABLE: Timing attack on token comparison
def validate_token(provided_token, stored_token):
if provided_token == stored_token: # Early exit leaks info
return True
return False
# SECURE: Constant-time comparison
import hmac
def validate_token(provided_token, stored_token):
return hmac.compare_digest(provided_token, stored_token)
Detection
- Look for direct string comparison of secrets
- Check for:
==,!=on tokens, passwords, signatures - Verify use of constant-time comparison functions
Detection Checklist
For each security-critical operation:
- Is there an expiration time set?
- Is the expiration time checked before use?
- Is the expiration time reasonable for the use case?
- Is expiration enforced server-side?
- Are there race conditions between check and use?
- Is the timeout configurable and documented?
- Is timeout enforcement consistent across services?
- Are expired records cleaned up?
- Are timezones handled correctly?
- Are comparisons constant-time where needed?