Replay Attack with Api Keys
How Replay Attack Manifests in API Keys
Replay attacks in API keys occur when an attacker intercepts a valid API key and its associated request, then resubmits that same request to the server at a later time. Unlike cryptographic replay attacks that target encrypted sessions, API key replay attacks exploit the stateless nature of REST APIs where the same key and payload can be reused repeatedly.
The most common API key replay scenario involves intercepting an API key from network traffic, logs, or client-side code. Since API keys often remain valid for extended periods (sometimes indefinitely), an attacker who captures a key can replay any request that uses it. This becomes particularly dangerous when the intercepted request includes privileged operations like account modifications, financial transactions, or data exports.
Consider this vulnerable pattern in Node.js:
// Vulnerable: API key never expires and requests are idempotent
app.post('/transfer', (req, res) => {
const apiKey = req.headers['x-api-key'];
const { from, to, amount } = req.body;
// No replay protection - same request can be resent
transferFunds(apiKey, from, to, amount);
res.json({ success: true });
});An attacker who captures this request can repeatedly submit it to drain the account. The problem compounds when APIs use predictable request structures or when API keys are embedded in client-side JavaScript, making them trivial to extract.
Mobile applications often ship with hardcoded API keys, creating massive replay attack surfaces. If a banking app contains an API key that authorizes fund transfers, decompiling the APK reveals the key, allowing attackers to craft and replay transfer requests at will.
Time-sensitive operations without proper nonce validation are especially vulnerable. A password reset request using only an API key and email address can be replayed to repeatedly trigger reset emails, enabling credential stuffing attacks or denial of service against user accounts.
API Keys-Specific Detection
Detecting replay attack vulnerabilities in API keys requires examining both the key lifecycle and request handling patterns. Start by analyzing how API keys are generated, stored, and validated.
Key characteristics that indicate replay vulnerability:
- API keys with unlimited or very long validity periods (90+ days)
- Lack of key rotation mechanisms
- No request timestamp validation
- Idempotent operations without nonce validation
- Analyzing key expiration policies in OpenAPI specifications
- Testing whether intercepted requests can be successfully replayed
- Checking for proper timestamp validation in request headers
- Verifying nonce or request ID validation mechanisms
middleBrick's API security scanner specifically tests for replay attack vulnerabilities by:
Manual detection should include:
const testReplayVulnerability = async (baseUrl, apiKey) => {
const initialResponse = await fetch(`${baseUrl}/sensitive-endpoint`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
// Wait and replay the same request
await new Promise(resolve => setTimeout(resolve, 1000));
const replayedResponse = await fetch(`${baseUrl}/sensitive-endpoint`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return {
initialStatus: initialResponse.status,
replayedStatus: replayedResponse.status,
vulnerable: initialResponse.status === 200 && replayedResponse.status === 200
};
};Network-level detection involves monitoring for repeated identical requests from the same API key within short timeframes. Tools like Wireshark or mitmproxy can capture traffic to verify whether requests contain sufficient entropy to prevent replay.
Log analysis often reveals replay attempts through patterns of repeated identical API calls. Look for bursts of identical requests with the same API key, especially those that modify state or access sensitive resources.
API Keys-Specific Remediation
Effective replay attack prevention for API keys requires a multi-layered approach combining key lifecycle management, request validation, and operational controls.
Key Expiration and Rotation
Implement short-lived API keys with automatic rotation:
// JWT-based API key with 15-minute expiration
const generateSecureApiKey = (userId) => {
return jwt.sign(
{ userId, type: 'api_key', issuedAt: new Date().toISOString() },
process.env.API_SECRET,
{ expiresIn: '15m' }
);
};Store keys in a database with metadata tracking issuance and rotation schedules:
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
key_hash TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP,
rotation_policy TEXT NOT NULL
);Request Timestamp Validation
Reject requests with timestamps outside acceptable windows:
app.use((req, res, next) => {
const timestamp = req.headers['x-request-timestamp'];
const requestTime = new Date(timestamp).getTime();
// Reject if request is older than 5 minutes
if (now - requestTime > 5 * 60 * 1000) {
return res.status(400).json({ error: 'Request timestamp too old' });
}
next();
});Nonce and Request ID Validation
Require unique nonces for state-changing operations:
app.post('/transfer', async (req, res) => {
const { apiKey, nonce, from, to, amount } = req.body;
// Check if nonce already used
const used = await db.collection('nonces').findOne({ nonce });
if (used) {
return res.status(400).json({ error: 'Nonce already used' });
}
// Process transfer
await processTransfer(apiKey, from, to, amount);
// Mark nonce as used
await db.collection('nonces').insertOne({ nonce, usedAt: new Date() });
res.json({ success: true });
});Rate Limiting and Behavioral Analysis
Implement intelligent rate limiting that considers API key patterns:
const rateLimiter = new RateLimiterRedis({
store: new Redis({ host: 'redis-server' }),
keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
points: 100, // 100 requests
duration: 60 // per minute
});For high-security operations, implement challenge-response mechanisms that change with each request, making replay attacks computationally expensive.