Timing Attack in Express
How Timing Attack Manifests in Express
Timing attacks in Express applications exploit the fact that different code paths execute in different amounts of time, allowing attackers to infer sensitive information based on response latency. These attacks are particularly dangerous in authentication systems where attackers can determine if a username exists by measuring response times.
In Express, timing attacks commonly manifest in authentication middleware. Consider a login route that compares passwords:
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user) {
// Deliberate delay to hide timing information
setTimeout(() => res.status(401).send('Invalid credentials'), 200);
return;
}
if (user.password !== password) {
setTimeout(() => res.status(401).send('Invalid credentials'), 200);
return;
}
res.send('Login successful');
});This naive approach actually worsens the problem. The setTimeout calls create consistent delays, but the initial user lookup still takes different amounts of time depending on whether the username exists. An attacker can measure these variations to enumerate valid usernames.
Another Express-specific timing vulnerability occurs in route parameter handling. Express processes routes in the order they're defined, and parameter extraction can leak information:
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
const user = db.getUserById(userId); // Database query time varies
if (!user) {
res.status(404).send('User not found');
return;
}
res.json(user);
});The database query time itself leaks information. A valid user ID might hit an indexed column and return quickly, while an invalid ID could trigger a full table scan or different query execution plan, creating measurable timing differences.
Rate limiting middleware in Express can also introduce timing side channels. Consider this implementation:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
keyGenerator: (req) => req.ip,
skip: (req) => req.path.startsWith('/public/')
});
app.use(limiter);
app.post('/api/secret', (req, res) => {
// Processing logic
});The skip function creates a timing difference between public and private endpoints. Attackers can probe endpoints to determine which ones are rate-limited versus which ones bypass the check, potentially mapping the application's structure.
Express-Specific Detection
Detecting timing attacks in Express requires both manual code review and automated scanning. middleBrick's black-box scanning approach is particularly effective for identifying timing vulnerabilities without requiring access to source code.
middleBrick scans Express APIs by sending carefully crafted requests and measuring response characteristics. For timing attack detection, it performs:
- Repeated requests with slight variations to measure response time consistency
- Authentication attempts with valid and invalid credentials to detect timing differences
- Parameter manipulation to observe database query timing variations
- Rate limit bypass detection by comparing public vs private endpoint behavior
The scanner analyzes response patterns across multiple requests, looking for statistically significant timing differences that could indicate information leakage. For Express applications specifically, middleBrick examines:
Authentication Timing Analysis:
- Username enumeration via timing differences
- Password comparison timing leaks
- Session validation timing variations
Route Handling Timing:
- Parameter parsing time differences
- Middleware execution order timing
- Database query timing consistency
Middleware Timing:
- Rate limiting bypass detection
- CORS preflight timing analysis
- Static file serving timing comparisonmiddleBrick's CLI provides detailed timing analysis reports:
$ middlebrick scan https://api.example.com --timing-analysis
Timing Attack Analysis Results:
• Login endpoint: High risk (timing variance: 18.3ms)
• User retrieval: Medium risk (timing variance: 7.2ms)
• Rate limiting bypass: Detected on /public/* endpoints
Recommendations:
- Implement constant-time comparison for passwords
- Add uniform delays to authentication failures
- Review database query optimizationFor Express developers, the GitHub Action integration allows continuous timing attack monitoring:
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npx middlebrick scan http://localhost:3000 --ci --fail-below B
env:
MIDDLEBRICK_API_KEY: ${{ secrets.MIDDLEBRICK_API_KEY }}This integration fails builds if timing attack vulnerabilities are detected, preventing insecure code from reaching production.
Express-Specific Remediation
Remediating timing attacks in Express requires implementing constant-time operations and eliminating timing side channels. The most critical area is authentication.
For password comparison, use crypto.timingSafeEqual instead of ===:
const crypto = require('crypto');
function constantTimeCompare(val1, val2) {
const buf1 = Buffer.from(val1);
const buf2 = Buffer.from(val2);
return crypto.timingSafeEqual(buf1, buf2);
}
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user) {
// Always perform a dummy comparison
constantTimeCompare('', 'dummy');
res.status(401).send('Invalid credentials');
return;
}
const passwordsMatch = constantTimeCompare(user.password, password);
if (!passwordsMatch) {
res.status(401).send('Invalid credentials');
return;
}
res.send('Login successful');
});This ensures the same amount of work is performed regardless of whether the username exists, preventing username enumeration.
For database queries, use uniform query patterns and add constant delays:
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
let user;
let queryTime = 0;
try {
const startTime = Date.now();
user = await db.getUserById(userId);
queryTime = Date.now() - startTime;
} catch (error) {
user = null;
}
// Apply uniform minimum response time
const minResponseTime = 100; // milliseconds
const delay = Math.max(0, minResponseTime - queryTime);
setTimeout(() => {
if (!user) {
res.status(404).send('User not found');
} else {
res.json(user);
}
}, delay);
});This approach ensures all requests take approximately the same time, regardless of database query results.
Rate limiting in Express can be made timing-safe by using consistent middleware execution:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
keyGenerator: (req) => req.ip,
// Always execute the check, even for skipped paths
handler: (req, res) => {
res.status(429).json({
success: false,
message: 'Too many requests, please try again later.'
});
}
});
// Apply rate limiting to all routes, then bypass logic inside
app.use(limiter);
app.post('/api/secret', limiter, (req, res) => {
// Processing logic
});Using the handler option ensures consistent response behavior even for skipped paths, eliminating timing differences between rate-limited and non-rate-limited endpoints.
For comprehensive protection, integrate middleBrick's continuous monitoring to verify that timing attack mitigations remain effective as the codebase evolves:
// Package.json script for timing attack testing
{
"scripts": {
"test:timing": "middlebrick scan http://localhost:3000 --timing-analysis"
}
}This ensures timing vulnerabilities are caught early in the development process rather than discovered in production.
Frequently Asked Questions
How can I test my Express API for timing attacks without access to the source code?
middlebrick scan https://yourapi.com and the tool will automatically identify timing attack vulnerabilities without requiring any credentials or access to your codebase.Does adding artificial delays to hide timing information actually work?
crypto.timingSafeEqual for comparisons, ensuring database queries take consistent time, and applying uniform minimum response times. middleBrick's scanning can verify whether your timing mitigations are actually effective by measuring response time consistency across many requests.