Timing Attack in Adonisjs
How Timing Attack Manifests in Adonisjs
Timing attacks in Adonisjs applications exploit the time differences between successful and failed authentication attempts, password comparisons, or database queries. These attacks allow malicious actors to infer sensitive information by measuring response times.
The most common manifestation occurs in authentication middleware. Adonisjs's built-in auth system uses bcrypt for password hashing, but if you implement custom authentication logic, you might inadvertently introduce timing vulnerabilities. Consider this problematic Adonisjs controller code:
class AuthController {
async login({ request, auth }) {
const { email, password } = request.all()
const user = await User.query().where('email', email).first()
if (user && await compare(password, user.password)) {
return auth.generate(user)
}
return response.status(401).send('Invalid credentials')
}
}The vulnerability here is subtle but critical. When a user exists but provides an incorrect password, the compare() function still executes, taking measurable time. When a user doesn't exist, the function returns immediately. An attacker can measure these timing differences to determine which emails are registered.
Another Adonisjs-specific timing attack vector appears in route parameter validation. Adonisjs's Lucid ORM query builder can leak timing information through different query execution paths:
async show({ params }) {
const user = await User.find(params.id)
if (!user) {
return response.status(404).send('User not found')
}
return user
}This code leaks information through database query timing. A valid ID returns faster than an invalid one because the database engine processes them differently. An attacker can exploit this to enumerate valid IDs and discover the total number of records.
Adonisjs's middleware system can also introduce timing leaks if not implemented carefully. Consider this authorization middleware:
async handle({ auth, params }, next) {
const user = await auth.getUser()
const resource = await Resource.find(params.resourceId)
if (!resource) {
return response.status(404).send('Resource not found')
}
if (resource.owner_id !== user.id) {
return response.status(403).send('Access denied')
}
await next()
}This middleware leaks whether a resource exists through timing differences, allowing attackers to map out valid resource IDs even when they don't have permission to access them.
Adonisjs-Specific Detection
Detecting timing attacks in Adonisjs applications requires both manual code review and automated scanning. middleBrick's API security scanner includes specific checks for timing vulnerabilities in Adonisjs applications.
For manual detection, use a timing analysis tool to measure response variations. Here's how to test your Adonisjs endpoints:
const axios = require('axios')
const { performance } = require('perf_hooks')
async function testTiming(url, iterations = 100) {
const timings = []
for (let i = 0; i < iterations; i++) {
const start = performance.now()
try {
await axios.post(url, {
email: i % 2 === 0 ? 'valid@example.com' : 'invalid@example.com',
password: 'wrongpassword'
})
} catch (error) {
// Ignore errors, we're measuring timing
}
const end = performance.now()
timings.push(end - start)
}
const avg = timings.reduce((a, b) => a + b) / timings.length
const variance = timings.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / timings.length
console.log(`Average: ${avg.toFixed(2)}ms, Variance: ${variance.toFixed(2)}ms`)
}
// Test your Adonisjs auth endpoint
testTiming('http://localhost:3333/api/auth/login')middleBrick scans for timing attack vulnerabilities by analyzing your Adonisjs application's response patterns. It tests authentication endpoints with valid and invalid credentials, measuring statistical timing differences. The scanner also examines your OpenAPI/Swagger spec to identify endpoints that should have constant-time responses.
middleBrick's LLM security features are particularly relevant for Adonisjs applications using AI integrations. It tests for prompt injection vulnerabilities that could leak timing information through AI model responses, and checks for excessive agency patterns that might expose timing-sensitive operations.
The scanner provides a security score with detailed findings, showing you exactly which endpoints are vulnerable and what the timing variance is. It prioritizes findings based on severity and provides specific remediation guidance for Adonisjs applications.
Adonisjs-Specific Remediation
Remediating timing attacks in Adonisjs requires implementing constant-time operations and eliminating information leakage through response timing. Here are Adonisjs-specific solutions:
For authentication, always perform the same operations regardless of whether the user exists. Use Adonisjs's built-in auth system or implement constant-time comparison:
const crypto = require('crypto')
class AuthController {
async login({ request, auth, response }) {
const { email, password } = request.all()
let user
let validPassword = false
// Always query for the user to maintain consistent timing
user = await User.query().where('email', email).first()
if (user) {
validPassword = await compare(password, user.password)
}
// Always perform a dummy comparison to equalize timing
const dummyHash = '$2b$10$abcdefghijklmnopqrstuv' // 24 chars
await compare(password, dummyHash)
if (user && validPassword) {
return auth.generate(user)
}
// Use a constant-time delay to equalize response times
const start = performance.now()
while (performance.now() - start < 100) {
// Busy wait for 100ms
}
return response.status(401).send('Invalid credentials')
}
}For resource access, implement constant-time existence checks:
async handle({ auth, params }, next) {
const user = await auth.getUser()
const resourceId = params.resourceId
// Always perform the same database operations
const [resource, dummy] = await Promise.all([
Resource.find(resourceId),
Resource.query().where('owner_id', user.id).limit(1).first()
])
// Always perform authorization checks even if resource doesn't exist
const hasAccess = resource ? resource.owner_id === user.id : false
const dummyCheck = dummy ? dummy.owner_id === user.id : false
// Use a constant-time decision
const authorized = hasAccess || dummyCheck
if (!authorized) {
return response.status(403).send('Access denied')
}
await next()
}middleBrick's CLI tool helps you verify these fixes:
npm install -g middlebrick
middlebrick scan http://localhost:3333/api/auth/login --profile adonisjs
This command runs Adonisjs-specific timing attack detection, checking your authentication and authorization endpoints for constant-time vulnerabilities. The GitHub Action integration allows you to fail builds if timing attack vulnerabilities are detected:
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npx middlebrick scan http://localhost:3333 --fail-on-severity=highFor applications using Adonisjs's AI features, middleBrick's MCP Server integration allows you to scan APIs directly from your IDE while coding, providing real-time feedback on timing attack vulnerabilities as you implement authentication and authorization logic.