HIGH side channel attackadonisjs

Side Channel Attack in Adonisjs

How Side Channel Attack Manifests in Adonisjs

Side channel attacks in Adonisjs applications exploit timing differences and resource consumption patterns to infer sensitive information. These attacks are particularly dangerous because they bypass traditional authentication mechanisms by observing indirect signals rather than attacking the system directly.

In Adonisjs, timing attacks commonly occur during authentication flows. Consider a login function where the response time varies based on whether the username exists:

class AuthController {
  async login({ request, response, auth }) {
    const { email, password } = request.all();
    
    // Vulnerable: timing differs based on user existence
    const user = await User.findBy('email', email);
    if (user) {
      try {
        await auth.attempt(email, password);
        return response.json({ message: 'Login successful' });
      } catch (error) {
        return response.json({ message: 'Invalid credentials' });
      }
    } else {
      return response.json({ message: 'Invalid credentials' });
    }
  }
}

The attacker can measure response times to determine if an email exists in the system. A valid email might take 50-100ms longer due to database lookups and password hashing, while invalid emails return immediately.

Another common pattern in Adonisjs is resource-based side channels through database queries. When filtering large datasets, the time to return "no results" versus "partial results" can leak information:

class UserController {
  async search({ request, response }) {
    const { query } = request.qs;
    
    // Vulnerable: timing reveals if any matches exist
    const users = await User.query()
      .where('name', 'LIKE', `%${query}%`)
      .limit(10)
      .fetch();
    
    return response.json(users);
  }
}

If the query matches many records, the database performs more work. An attacker can send progressively longer queries and measure response times to determine if partial matches exist, eventually reconstructing valid values character by character.

Resource exhaustion attacks also manifest in Adonisjs through improper pagination and filtering. Consider an API endpoint that doesn't limit query complexity:

class ReportController {
  async generateReport({ request, response }) {
    const { filters } = request.all();
    
    // Vulnerable: complex filters consume varying resources
    const report = await Report.query()
      .applyFilters(filters) // User-controlled filter complexity
      .fetch();
    
    return response.json(report);
  }
}

An attacker can craft increasingly complex filter combinations to cause the server to consume more CPU, memory, or database connections. By observing when the server becomes unresponsive or returns errors, they can infer information about the underlying data structure and available resources.

Adonisjs-Specific Detection

Detecting side channel vulnerabilities in Adonisjs requires both manual code review and automated scanning. middleBrick's black-box scanning approach is particularly effective because it can measure timing variations and resource consumption patterns without requiring access to source code.

When scanning an Adonisjs API with middleBrick, the tool performs several specific tests:

Timing Analysis: middleBrick sends identical requests with slight variations and measures response time distributions. For authentication endpoints, it tests with valid and invalid credentials across multiple attempts to identify statistically significant timing differences.

Resource Consumption Profiling: The scanner varies request complexity and monitors server responses. For Adonisjs applications, this includes testing different query parameter combinations, payload sizes, and nested filtering operations to identify resource-dependent response patterns.

LLM Security Testing: For Adonisjs applications using AI features, middleBrick's unique LLM security checks test for system prompt leakage and prompt injection vulnerabilities that could expose sensitive data through indirect channels.

# Using middleBrick CLI to scan an Adonisjs API
middlebrick scan https://api.example.com/auth/login \
  --profile api \
  --max-retries 3 \
  --output json > scan-results.json

The scan analyzes 12 security categories including authentication bypass, input validation, and data exposure. For side channel detection specifically, middleBrick examines:

  • Response time consistency across similar requests
  • Resource utilization patterns under varying loads
  • Error message differentiation that could leak information
  • Authentication flow timing analysis

middleBrick's OpenAPI analysis also validates that your Adonisjs API specification doesn't expose internal implementation details through overly descriptive schemas or error responses that could aid timing attacks.

Adonisjs-Specific Remediation

Remediating side channel vulnerabilities in Adonisjs requires implementing constant-time operations and uniform resource consumption patterns. The framework's middleware system and validation features provide excellent tools for this.

Constant-Time Authentication: Implement uniform response times for authentication attempts:

import { Helpers } from '@adonisjs/core'

class AuthController {
  async login({ request, response, auth }) {
    const { email, password } = request.all();
    const startTime = Date.now();
    
    // Always perform user lookup and hashing
    const user = await User.findBy('email', email) || { id: null };
    let success = false;
    let message = 'Invalid credentials';
    
    if (user.id) {
      try {
        await auth.attempt(email, password);
        success = true;
        message = 'Login successful';
      } catch (error) {
        // Intentionally same timing as success case
        await this.simulateHashTiming();
      }
    }
    
    // Constant delay to mask timing variations
    const elapsedTime = Date.now() - startTime;
    const targetTime = 200; // ms
    if (elapsedTime < targetTime) {
      await new Promise(resolve => setTimeout(resolve, targetTime - elapsedTime));
    }
    
    return response.json({ message });
  }
  
  async simulateHashTiming() {
    // Simulate password hashing time
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}

Uniform Database Queries: Use consistent query patterns regardless of input:

class UserController {
  async search({ request, response }) {
    const { query } = request.qs;
    
    // Always perform same query structure
    const users = await User.query()
      .where('name', 'LIKE', `%${query}%`)
      .limit(10)
      .preload('profile') // Always preload same relations
      .fetch();
    
    return response.json(users);
  }
}

// Add middleware for query timing standardization
class QueryTimingMiddleware {
  async handle(ctx, next) {
    const startTime = process.hrtime.bigint();
    await next();
    const elapsed = Number(process.hrtime.bigint() - startTime) / 1e6;
    
    // Enforce minimum response time
    const minTime = 100;
    if (elapsed < minTime) {
      await new Promise(resolve => setTimeout(resolve, minTime - elapsed));
    }
  }
}

// Register in start/kernel.js
const globalMiddleware = [
  () => import('@adonisjs/core/src/Middleware/AllowMethod'),
  () => import('@adonisjs/core/src/Middleware/AllowRoute'),
  () => import('./app/Middleware/QueryTimingMiddleware'),
];

Resource Rate Limiting: Implement consistent resource limits using Adonisjs middleware:

import Limiter from '@adonisjs/limiter'

class ResourceLimiterMiddleware {
  async handle(ctx, next) {
    const { request } = ctx;
    
    // Limit query complexity regardless of input
    const complexity = this.calculateQueryComplexity(request.qs);
    const maxComplexity = 100;
    
    if (complexity > maxComplexity) {
      return ctx.response.status(429).json({
        error: 'Request too complex',
        allowed_complexity: maxComplexity
      });
    }
    
    await next();
  }
  
  calculateQueryComplexity(params) {
    let complexity = 0;
    for (const [key, value] of Object.entries(params)) {
      complexity += key.length + (value?.length || 0);
      // Add complexity for nested objects
      if (typeof value === 'object') {
        complexity += Object.keys(value).length * 5;
      }
    }
    return complexity;
  }
}

// Apply globally for API routes
Route.group(() => {
  Route.get('/search', 'UserController.search');
  Route.get('/report', 'ReportController.generate');
}).middleware(['resource-limiter']);

Frequently Asked Questions

How can I test if my Adonisjs API is vulnerable to timing attacks?
Use middleBrick's automated scanning to measure response time variations across similar requests. The tool performs statistical analysis to identify timing differences that could leak information. You can also manually test by sending identical requests with slight variations and measuring response times using tools like curl with --write-out '%{time_total}' or HTTP client libraries with timing capabilities.
Does implementing constant-time responses affect legitimate user experience?