Open Redirect Chain in Adonisjs
How Open Redirect Chain Manifests in Adonisjs
Open redirect chains in Adonisjs typically arise when redirect logic uses unvalidated user input to construct redirect URLs, allowing attackers to chain multiple redirects to bypass security controls. A common pattern occurs in authentication flows where a redirect_uri parameter is accepted without proper validation. For example, in an Adonisjs controller handling login callbacks, developers might directly use request input to build a redirect URL:
// controllers/AuthController.js
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class AuthController {
async callback({ request, response }: HttpContextContract) {
const redirectTo = request.input('redirect_uri', '/')
// Vulnerable: direct use of user input without validation
return response.redirect(redirectTo)
}
}
An attacker can exploit this by providing a redirect_uri like https://evil.com, but more dangerously, they can create a chain: https://trusted-site.com/redirect?url=https://evil.com. If the application has an open redirect at /redirect that forwards to the url parameter, and the login callback then uses that result as the final redirect, it forms a chain: login -> trusted redirector -> evil.com. This bypasses domain-based allowlists that only check the first hop. Adonisjs's routing system can exacerbate this when using named routes with dynamic parameters; for instance, a route like Route.get('/go/:destination', 'RedirectController.go') where destination is user-controlled and used in response.redirect() without validation creates a vector for chaining if the destination parameter itself contains a redirect URL.
This chaining technique is particularly effective against security mechanisms that perform single-point validation, as seen in real-world bypasses like CVE-2021-42847 (GitHub OAuth) where redirect chains were used to steal tokens. In Adonisjs, the risk is heightened when combining user-controlled redirect parameters with the framework's route() helper for generating URLs, if the helper's output is later used as input to another redirect without re-validation.
Adonisjs-Specific Detection
Detecting open redirect chains in Adonisjs requires analyzing both route definitions and controller logic for patterns where user input flows into redirect functions without adequate validation. middleBrick identifies these issues through black-box scanning of the unauthenticated attack surface, actively probing for redirect parameters and analyzing responses for chaining behavior. For example, it might send a request like GET /login?redirect_uri=https://evil.com%2F%40github.com and check if the response redirects to https://evil.com, then further test if intermediate endpoints (like /redirect) exist that could form a chain.
In the Adonisjs context, middleBrick pays special attention to routes that use dynamic parameters in redirect logic. Consider a route defined as:
// start/routes.js
Route.get('/redirect/:path', 'RedirectController.index')
With a controller:
// controllers/RedirectController.js
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class RedirectController {
async index({ params, response }: HttpContextContract) {
// Vulnerable: params.path is user-controlled and used directly
return response.redirect(params.path)
}
}
middleBrick would detect this by submitting payloads like /redirect/https%3A%2F%2Fevil.com and observing external redirects. To detect chains, it tests combinations: first probing /redirect?url=https://trusted.com/evil (if a query-param based redirect exists), then checking if the trusted site redirects to evil.com. The scanner also examines OpenAPI/Swagger specs (if present) to identify endpoints accepting redirect_uri, return_url, or similar parameters, then validates whether those inputs are properly constrained by Adonisjs's validation framework.
Crucially, middleBrick does not require access to source code or configuration; it infers vulnerability through behavioral analysis of responses to crafted inputs, making it effective for detecting chain-like behavior even when the intermediate redirect point is on a different domain or subdomain.
Adonisjs-Specific Remediation
Fixing open redirect chains in Adonisjs involves validating and sanitizing redirect URLs using the framework's built-in features. The primary defense is to avoid using untrusted input in redirect decisions. When redirects are necessary, use a strict allowlist of trusted URLs or paths. Adonisjs's validator can be employed to enforce this.
For the vulnerable login callback example, remediation involves validating the redirect_uri input against a list of allowed domains or using Adonisjs's URL helper to ensure the URL is relative or points to a trusted domain:
// controllers/AuthController.js
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema, rules } from '@ioc:Adonis/Core/Validator'
export default class AuthController {
async callback({ request, response }: HttpContextContract) {
const redirectSchema = schema.create({
redirect_uri: schema.string.optional([
rules.regex(/^\/.*/), // Only allow relative paths
// OR, for external domains, use an allowlist:
// rules.in(['https://trusted.com', 'https://app.trusted.com'])
])
})
const payload = await request.validate({ schema: redirectSchema })
const redirectTo = payload.redirect_uri || '/'
// Safe: redirect_to is either a relative path or from allowlist
return response.redirect(redirectTo)
}
}
For dynamic route parameters like :path in a redirect controller, the same validation principle applies. Instead of directly using params.path, validate it:
// controllers/RedirectController.js
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema, rules } from '@ioc:Adonis/Core/Validator'
export default class RedirectController {
async index({ params, response }: HttpContextContract) {
const redirectSchema = schema.create({
path: schema.string([
rules.regex(/^\/[^\\]*$/) // Ensures path starts with / and contains no scheme
])
})
const { path } = await request.validate({ schema: redirectSchema })
return response.redirect(path) // Now safe: only relative paths allowed
}
}
Additionally, leverage Adonisjs's named routes to generate trusted redirect URLs rather than constructing them from user input. For example, after validation, use Route.signature('dashboard') to generate a trusted URL for redirection, eliminating reliance on untrusted input for URL construction. This approach ensures that even if validation logic has flaws, the redirect destination is always a route defined within the application, preventing chaining to external malicious sites.