MEDIUM unicode normalizationadonisjs

Unicode Normalization in Adonisjs

How Unicode Normalization Manifests in Adonisjs

Unicode normalization attacks in Adonisjs often exploit the framework's handling of user input through its Lucid ORM and validation middleware. When Adonisjs processes HTTP requests, it passes raw query parameters and body data through its validation pipeline before reaching controllers or database operations.

The vulnerability typically appears in Adonisjs route handlers that accept string parameters for database queries or authentication. Consider this common Adonisjs pattern:

Route.get('/users/:id', async ({ params }) => {
  const user = await User.find(params.id)
  return user
})

An attacker can exploit Unicode normalization by sending visually identical but canonically different characters. For example, the username "admin" might have multiple Unicode representations:

// Normal ASCII
admin

// With combining characters
admin

// With compatibility characters
admin

In Adonisjs applications, this becomes dangerous when combined with case-insensitive comparisons or when user input is used in SQL queries without proper normalization. The framework's default behavior doesn't automatically normalize Unicode strings, allowing attackers to bypass authentication checks or access unauthorized resources.

A specific Adonisjs attack pattern involves using Unicode characters that look identical to database entries but have different byte representations. An attacker might register an account with a username containing Unicode combining marks, then use that account to bypass admin-only routes that perform simple string comparisons.

Another manifestation occurs in Adonisjs route parameter binding. When a route expects an integer ID but receives a Unicode string, Adonisjs's type coercion might silently convert visually similar characters, leading to unexpected database queries or even SQL injection in edge cases.

Adonisjs-Specific Detection

Detecting Unicode normalization issues in Adonisjs applications requires examining both the codebase and runtime behavior. Start by auditing your Adonisjs routes and controllers for patterns that accept string input without validation.

middleBrick's API security scanner specifically detects Unicode normalization vulnerabilities in Adonisjs applications by testing endpoints with Unicode variants of common attack strings. The scanner sends requests containing characters from different Unicode normalization forms (NFC, NFD, NFKC, NFKD) and analyzes the responses for inconsistencies.

Here's how middleBrick identifies the issue:

// middleBrick tests with Unicode variants
const testCases = [
  'admin', // ASCII
  'admin', // NFD form
  'admin', // NFKD form
  'admin' // Full-width variants
]

For Adonisjs applications, middleBrick examines:

  • Route handlers that use string parameters without explicit validation
  • Database queries that compare user input directly
  • Authentication middleware that doesn't normalize credentials
  • API responses that reveal whether different Unicode forms are treated identically

You can also manually test your Adonisjs application by creating a simple middleware that logs the byte length and Unicode normalization form of incoming parameters:

export class UnicodeLoggerMiddleware {
  async handle({ request }, next) {
    const params = request.all()
    Object.keys(params).forEach(key => {
      const value = params[key]
      console.log(`${key}:`, {
        value,
        length: value.length,
        bytes: Buffer.from(value).length,
        normalization: require('unorm').nfc(value) === value ? 'NFC' : 'Other'
      })
    })
    await next()
  }
}

This middleware helps identify parameters that accept Unicode input without proper handling, which is the first step in detecting normalization vulnerabilities.

Adonisjs-Specific Remediation

Remediating Unicode normalization vulnerabilities in Adonisjs requires a multi-layered approach. The most effective solution is to implement consistent Unicode normalization at the application boundary using Adonisjs middleware.

Create a normalization middleware in Adonisjs:

import { normalize } from 'unorm'

export class UnicodeNormalizerMiddleware {
  async handle({ request }, next) {
    // Normalize all incoming string parameters
    const normalizedParams = this.normalizeObject(request.all())
    request.body = normalizedParams
    
    // Also normalize query parameters
    const normalizedQuery = this.normalizeObject(request.qs())
    request.qs = () => normalizedQuery
    
    await next()
  }

  normalizeObject(obj) {
    return Object.keys(obj).reduce((acc, key) => {
      const value = obj[key]
      if (typeof value === 'string') {
        // Use NFC (Canonical Decomposition, followed by Canonical Composition)
        acc[key] = normalize(value, 'NFC')
      } else if (typeof value === 'object' && value !== null) {
        acc[key] = this.normalizeObject(value)
      } else {
        acc[key] = value
      }
      return acc
    }, {})
  }
}

Register this middleware globally in your Adonisjs application to ensure all incoming requests are normalized before reaching your business logic.

For database operations, Adonisjs's Lucid ORM provides hooks where you can implement additional validation. Create a base model that enforces strict string validation:

import { DateTime } from 'luxon'
import { BaseModel, column, beforeSave } from '@adonisjs/lucid/src/Orm'

export class SecureModel extends BaseModel {
  @beforeSave()
  static normalizeStrings(model) {
    const columns = model.$columns
    Object.keys(columns).forEach(columnName => {
      const column = columns[columnName]
      if (column.type === 'string' && model[columnName]) {
        // Ensure stored strings are in NFC form
        model[columnName] = normalize(model[columnName], 'NFC')
      }
    })
  }

  @beforeSave()
  static validateUnicode(model) {
    const columns = model.$columns
    Object.keys(columns).forEach(columnName => {
      const value = model[columnName]
      if (typeof value === 'string') {
        // Reject strings with dangerous Unicode patterns
        if (/[^-]/.test(value)) {
          throw new Error(`Unicode validation failed for ${columnName}: ${value}`)
        }
      }
    })
  }
}

Then extend this base model for all your Adonisjs models:

import { column } from '@adonisjs/lucid/src/Orm'
import SecureModel from './SecureModel'

export default class User extends SecureModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public username: string

  @column()
  public email: string
}

For authentication in Adonisjs, implement strict comparison with normalization:

import { compareSync } from 'bcryptjs'
import { normalize } from 'unorm'

export class SecureAuthService {
  async authenticate(username, password) {
    const normalizedUsername = normalize(username, 'NFC')
    const user = await User
      .query()
      .where('username', normalizedUsername)
      .first()

    if (!user) {
      return null
    }

    const passwordsMatch = await compareSync(password, user.password)
    if (!passwordsMatch) {
      return null
    }

    return user
  }
}

This approach ensures that authentication in your Adonisjs application is resistant to Unicode-based bypass attempts.

Frequently Asked Questions

How does middleBrick detect Unicode normalization vulnerabilities in Adonisjs applications?
middleBrick tests Adonisjs API endpoints by sending requests containing Unicode characters in different normalization forms (NFC, NFD, NFKC, NFKD). It analyzes whether the application treats these visually identical but byte-different strings consistently. The scanner specifically looks for authentication bypasses, authorization issues, and data exposure that occur when Unicode normalization isn't properly handled in Adonisjs route handlers and database queries.
Can Unicode normalization issues in Adonisjs lead to SQL injection?
While Unicode normalization itself doesn't directly cause SQL injection, it can create conditions where injection becomes possible. In Adonisjs applications, if user input containing Unicode characters bypasses validation and reaches raw SQL queries, the different byte representations might break parameterized query boundaries or confuse the database's string comparison logic. This is particularly dangerous when combined with case-insensitive collations or when Adonisjs's type coercion silently converts Unicode strings to unexpected types.