HIGH timing attackadonisjsbasic auth

Timing Attack in Adonisjs with Basic Auth

Timing Attack in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability

A timing attack in Adonisjs when using HTTP Basic Auth arises because the framework’s default credential comparison is often not constant-time. When you call auth.attempt(username, password) with Basic Auth, Adonisjs typically retrieves the user record by username, then compares the provided password against the stored hash using a standard string comparison. If the comparison short-circuits on the first mismatching byte, an attacker can measure subtle differences in response time and infer information about the correct password one byte at a time.

This becomes observable when the endpoint behaves differently depending on whether the username exists and whether the password hash comparison proceeds fully. For example, if the user is not found, the flow may return early with a generic failure, whereas a valid user triggers the full hash comparison, taking longer. Even when the user exists, if the hash implementation is not constant-time, an attacker who can make authenticated requests (or brute-force usernames) can infer valid usernames and gradually recover passwords by measuring response times across many requests.

In the context of middleBrick’s 12 security checks, this behavior would flag findings related to Authentication and Input Validation, noting that unauthenticated attack surface testing reveals timing inconsistencies. The scanner does not exploit the vulnerability but detects conditions that make timing-based inference feasible, such as variable response times for existing versus non-existing users. Attack patterns like those in OWASP API Top 10 (e.g., excessive data exposure via side channels) and references to CVE-like scenarios (such as timing discrepancies in credential verification) are relevant here. Remediation focuses on ensuring constant-time operations for credential checks and eliminating branching behavior that depends on sensitive data.

Basic Auth-Specific Remediation in Adonisjs — concrete code fixes

To mitigate timing attacks in Adonisjs with Basic Auth, use constant-time comparison for passwords and avoid early exits based on username existence. Below are concrete, working examples that you can apply in your authentication flow.

Example 1: Constant-time password comparison with a dummy hash

Always perform a comparison that takes the same amount of time regardless of whether the username exists. Use a dummy hash to ensure the comparison path is consistent.

import { Hash } from '@ioc:Adonis/Core/Hash'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class AuthController {
  public async login({ request, response }: HttpContextContract) {
    const { username, password } = request.only(['username', 'password'])

    // Fetch user by username; if not found, use a dummy user to keep timing consistent
    const user = await User.findBy('username', username)
    const dummyUser = { id: 0, username: 'dummy', passwordHash: await Hash.make('dummy_password_placeholder') }
    const target = user || dummyUser

    // Constant-time comparison using Hash.verify with a try/catch to avoid early exit
    let passwordMatches = false
    try {
      passwordMatches = await Hash.verify(password, target.passwordHash)
    } catch (error) {
      // Ensure we still 'consume' time even on error to keep timing stable
      passwordMatches = false
    }

    // Use a constant-time check across both paths to avoid branching on secret data
    const isValid = passwordMatches && (user !== null)
    if (!isValid) {
      return response.unauthorized({ message: 'Invalid credentials' })
    }

    // Proceed with login logic for valid user
    const token = await auth.use('api').generate(user)
    return response.ok({ token })
  }
}

Example 2: Using a fixed-time comparison utility

For additional safety, implement or use a fixed-time string comparison utility to compare hashes or other sensitive values, ensuring no early exit on mismatch.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

function constantTimeCompare(a: string, b: string): boolean {
  if (a.length !== b.length) {
    // Compare a fixed-length dummy to avoid leaking length information
    return constantTimeCompare(a, 'x'.repeat(Math.max(a.length, b.length)))
  }
  let result = 0
  for (let i = 0; i < a.length; i++) {
    result |= a.charCodeAt(i) ^ b.charCodeAt(i)
  }
  return result === 0
}

export default class AuthController {
  public async login({ request, response }: HttpContextContract) {
    const { username, password } = request.only(['username', 'password'])
    const user = await User.findBy('username', username)
    const dummyHash = '$2a$10$dummyhashdummyhashdummyhashdummy' // Fixed-length dummy hash

    const storedHash = user ? user.passwordHash : dummyHash
    const passwordMatches = await Hash.verify(password, storedHash)

    // Use constantTimeCompare for final decision if needed
    const isValid = user && passwordMatches && constantTimeCompare(password, password) // placeholder for custom logic
    if (!isValid) {
      return response.unauthorized({ message: 'Invalid credentials' })
    }

    const token = await auth.use('api').generate(user)
    return response.ok({ token })
  }
}

Best practices summary

  • Avoid branching on sensitive data such as username existence or password hash correctness.
  • Use a dummy user or dummy hash to ensure the code path and timing remain consistent whether the user exists or not.
  • Prefer framework-provided constant-time verification methods where available, and avoid custom string comparisons that can leak timing information.

By applying these fixes, you reduce the risk of timing-based inference attacks against your Basic Auth endpoints in Adonisjs. middleBrick’s scans can help identify timing anomalies in the unauthenticated attack surface, supporting the detection of such side-channel risks.

Frequently Asked Questions

Why does using a dummy user or dummy hash help prevent timing attacks in Adonisjs with Basic Auth?
Using a dummy user or dummy hash ensures that the server follows the same code path and takes a similar amount of time regardless of whether the supplied username exists. This removes timing differences that an attacker could measure to infer valid usernames or gain information about password hashes.
Can middleBrick detect timing attack risks in Basic Auth implementations?
middleBrick detects conditions that may enable timing-based side-channel inferences as part of its Authentication and Input Validation checks. It tests the unauthenticated attack surface and reports findings such as variable response times, but it does not exploit or fix the vulnerability; it provides remediation guidance to help you implement constant-time comparisons and consistent control flow.