Session Fixation with Api Keys
How Session Fixation Manifests in API Keys
Session fixation in API keys is a subtle but dangerous vulnerability where an attacker can force a victim to use a predetermined API key, giving the attacker persistent access to the victim's account or data. Unlike traditional web session fixation that relies on HTTP cookies, API key fixation exploits the stateless nature of API authentication while introducing stateful persistence through key management systems.
In API contexts, session fixation typically occurs when API keys are generated or regenerated without proper invalidation of previous keys. A common scenario involves an attacker creating an account, generating an API key, then manipulating the victim into using that same key. Since many systems allow multiple active API keys per user, the original key remains valid even after the victim generates their own keys.
The attack often exploits predictable key generation patterns. If an API key follows a predictable format (such as sequential numbering, timestamp-based generation, or weak randomness), an attacker can generate keys in advance and wait for them to become active. This is particularly problematic in systems where keys are generated client-side before being registered with the server.
Another manifestation occurs in API key rotation systems. When users are prompted to rotate their keys for security, a poorly implemented system might allow the old key to remain active for an extended period. An attacker who has obtained the old key during this window maintains access while the user believes they've secured their account.
Multi-tenant systems face unique challenges. If an API key is tied to specific tenant permissions and those permissions change without key regeneration, an attacker who has obtained the key maintains access to resources even after the legitimate user's permissions are modified or revoked.
Supply chain attacks can also lead to API key fixation. If a third-party service generates API keys on behalf of users and those keys are predictable or reused across customers, an attacker who compromises the third-party service can fixate keys across multiple victims simultaneously.
API Keys-Specific Detection
Detecting session fixation in API keys requires both static analysis of key generation logic and dynamic testing of key lifecycle management. The most effective approach combines automated scanning with manual code review.
Static analysis should examine API key generation code for predictable patterns. Look for implementations that use weak random number generators, sequential IDs, or timestamp-based generation. In Node.js, for example:
// Vulnerable: predictable key generation
function generateApiKey() {
return 'api_' + Date.now() + '_' + Math.random(); // Predictable timestamp
}
// Better: cryptographically secure
function generateSecureApiKey() {
return crypto.randomBytes(32).toString('hex');
}Dynamic testing involves attempting to fixate keys through the application's key management interfaces. This includes testing whether old keys remain active after rotation, whether keys can be reused across accounts, and whether key generation parameters can be manipulated.
Automated scanning tools like middleBrick can detect API key fixation vulnerabilities by testing key lifecycle management. The scanner attempts to create multiple keys, observe their validity periods, and test whether keys from one account can be used to access another account's resources. middleBrick's black-box scanning approach tests the unauthenticated attack surface, identifying fixation points without requiring access credentials.
Key lifecycle analysis is crucial. A secure system should implement immediate key invalidation upon regeneration, enforce minimum rotation intervals, and maintain audit logs of key creation and usage. The scanner checks for these patterns by attempting to use keys across different sessions and observing the system's response.
Supply chain analysis examines whether third-party services involved in key generation follow secure practices. This includes checking whether keys are generated server-side versus client-side, whether keys are properly scoped to specific resources, and whether key generation algorithms are publicly documented (which can aid attackers).
Monitoring for anomalous key usage patterns can also indicate fixation attempts. Look for keys being used from unexpected geographic locations, at unusual times, or accessing resources outside the user's normal pattern. While this is more of a detection-after-compromise strategy, it's valuable for identifying ongoing fixation attacks.
API Keys-Specific Remediation
Remediating API key fixation requires a multi-layered approach that addresses both key generation and lifecycle management. The foundation is implementing cryptographically secure key generation that cannot be predicted or reproduced by attackers.
For secure key generation, use platform-specific secure random number generators:
# Python: cryptographically secure key generation
import secrets
import string
def generate_secure_api_key(length=32):
alphabet = string.ascii_letters + string.digits + '-_'
return ''.join(secrets.choice(alphabet) for _ in range(length))
# Alternative: URL-safe base64 encoding
import base64
import os
def generate_base64_api_key():
return base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8')In Node.js, use the built-in crypto module:
const crypto = require('crypto');
function generateSecureApiKey() {
return crypto.randomBytes(32).toString('hex');
}
// Or for URL-safe keys
function generateUrlSafeApiKey() {
return crypto.randomBytes(24).toString('base64url');
}Key lifecycle management is equally important. Implement immediate key invalidation upon regeneration:
# Django/Python example: secure key rotation
from django.db import models
import secrets
import string
def rotate_api_key(user):
# Invalidate all existing keys
user.apikey_set.update(active=False)
# Generate new key
new_key = generate_secure_api_key()
# Create new active key
ApiKey.objects.create(
user=user,
key_hash=hash_key(new_key), # Never store plaintext keys
active=True
)
return new_key
def hash_key(key):
import hashlib
return hashlib.sha256(key.encode()).hexdigest()Implement key scoping to limit the blast radius of compromised keys:
class ApiKey(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
key_hash = models.CharField(max_length=64)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
permissions = models.JSONField(default=dict) # Specific resource permissions
def can_access_resource(self, resource_path):
# Check if key has permission for this resource
return self.permissions.get(resource_path, False)
Implement rate limiting and anomaly detection at the API gateway level:
# FastAPI middleware for key anomaly detection
from fastapi import Request, HTTPException
from datetime import datetime, timedelta
async def api_key_middleware(request: Request):
api_key = request.headers.get('X-API-Key')
if not api_key:
raise HTTPException(status_code=401, detail='API key required')
# Check key validity
key_record = await get_key_record(api_key)
if not key_record or not key_record.active:
raise HTTPException(status_code=401, detail='Invalid API key')
# Check for fixation patterns
now = datetime.utcnow()
recent_usage = await get_recent_usage(api_key, since=now - timedelta(hours=1))
# Flag if key used from multiple locations rapidly
if len(set(r.ip_address for r in recent_usage)) > 1:
raise HTTPException(
status_code=403,
detail='Suspicious key usage detected'
)
Implement comprehensive logging and monitoring:
# Log key usage patterns for anomaly detection
async def log_api_key_usage(api_key, user_id, endpoint, ip_address):
usage_record = {
'api_key': api_key,
'user_id': user_id,
'endpoint': endpoint,
'ip_address': ip_address,
'timestamp': datetime.utcnow(),
'user_agent': request.headers.get('User-Agent', '')
}
await save_usage_record(usage_record)
# Check for fixation indicators
if await is_suspicious_usage_pattern(usage_record):
await alert_security_team(usage_record)Finally, implement key rotation policies that force periodic regeneration while maintaining backward compatibility during transition periods:
# Graceful key rotation with overlap period
class KeyRotationPolicy:
ROTATION_PERIOD_DAYS = 90
GRACE_PERIOD_DAYS = 7
def should_rotate(self, key):
age = datetime.utcnow() - key.created_at
return age > timedelta(days=self.ROTATION_PERIOD_DAYS - self.GRACE_PERIOD_DAYS)
def rotate_if_needed(self, user):
keys = user.apikey_set.filter(active=True)
for key in keys:
if self.should_rotate(key):
new_key = rotate_api_key(user)
return new_key
return None