Prototype Pollution in Adonisjs with Basic Auth
Prototype Pollution in Adonisjs with Basic Auth
Prototype pollution in Adonisjs with Basic Auth occurs when user-controlled input in request headers, query parameters, or body properties is merged into objects used across the application without validation. In Adonisjs, controllers often parse Basic Auth credentials from the Authorization header and then merge parsed values into configuration or request context objects. If an attacker sends a crafted header such as Authorization: Basic base64("polluter:pass") and the application merges parsed credentials into a shared object using a utility like Object.assign or the spread operator, special keys such as __proto__, constructor.prototype, or constructor can modify the prototype chain. This can lead to insecure property overrides that affect all instances of the object, potentially bypassing authorization checks or changing runtime behavior.
During a black-box scan, middleBrick runs 12 security checks in parallel, including Authentication, Input Validation, and Property Authorization. For an endpoint protected by Basic Auth, middleBrick tests whether injected prototype properties affect authentication outcomes or property-based access controls. For example, a payload like { "__proto__": { "isAdmin": true } } merged into a user or settings object could cause an authenticated context to incorrectly treat a low-privilege account as an admin. The scanner cross-references these runtime findings with OpenAPI/Swagger definitions (2.0, 3.0, 3.1) and resolves $ref pointers to ensure the pollutable paths are explicitly defined or intentionally restricted.
An illustrative Adonisjs route and controller snippet is shown below. This code extracts Basic Auth credentials and merges them into a user object. Without strict validation, an attacker can supply keys intended to pollute prototypes, and the merged user object may adopt altered behavior in downstream checks.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
export default class AuthController {
public async login({ request, auth }: HttpContextContract) {
const basicAuthHeader = request.headers().authorization
// naive parsing for example purposes
const decoded = Buffer.from(basicAuthHeader.split(' ')[1], 'base64').toString('utf-8')
const [username, password] = decoded.split(':')
const user = await User.findBy('username', username)
if (!user || !(await verifyPassword(password, user.password))) {
return { error: 'Unauthorized' }
}
// potentially unsafe merge: user-supplied fields overwrite defaults
const merged = { isAdmin: false, permissions: [], ...user.serialize() }
return { user: merged }
}
}
In this example, if user.serialize() reflects properties from the database record and an attacker can influence object construction or the merge target, prototype pollution may bypass isAdmin checks. middleBrick’s Property Authorization and Input Validation checks are designed to surface such risky merge patterns in unauthenticated scans, even when Basic Auth is used, by analyzing how runtime values relate to spec-defined schemas and security boundaries.
Basic Auth-Specific Remediation in Adonisjs
Remediation focuses on preventing untrusted data from reaching prototype-sensitive operations and enforcing strict schema validation. Avoid merging raw user input or serialized model fields directly into shared objects. Instead, explicitly whitelist allowed fields and use libraries that do not expose dangerous merges. For Basic Auth, parse credentials defensively and treat the decoded payload as untrusted input.
Below is a hardened Adonisjs example that validates the Basic Auth credentials against a schema, retrieves the user, and constructs a safe response without merging attacker-influenced keys into prototypes or shared objects. It uses Joi-like validation via Adonisjs rules and explicit field selection.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
import { schema, rules } from '@ioc:Adonis/Core/Validator'
const authSchema = schema.create({
username: schema.string.optional(),
password: schema.string.optional(),
})
export default class AuthController {
public async login({ request, response }: HttpContextContract) {
const basicAuthHeader = request.headers().authorization
if (!basicAuthHeader || !basicAuthHeader.startsWith('Basic ')) {
return response.unauthorized()
}
const base64 = basicAuthHeader.split(' ')[1]
const decoded = Buffer.from(base64, 'base64').toString('utf-8')
const [username, rawPassword] = decoded.split(':')
// Validate structure and types before use
const payload = { username, password: rawPassword }
const validated = authSchema.validate(payload, { stripUnknown: true })
const user = await User.findBy('username', validated.username)
if (!user) {
return response.unauthorized()
}
const passwordValid = await verifyPassword(validated.password, user.password)
if (!passwordValid) {
return response.unauthorized()
}
// Explicit, safe construction — no merging of user-controlled keys into shared objects
const safeUser = {
id: user.id,
username: user.username,
role: user.role,
permissions: user.permissions || [],
}
return { user: safeUser }
}
}
Additional measures include avoiding Object.assign or spread merges that pull in unexpected keys, using strict schema validation for any parsed input, and ensuring that authorization checks examine roles or permissions stored in server-side session or token data rather than properties derived from merged objects. middleBrick’s Authentication and Property Authorization checks can highlight endpoints where unsafe merges or missing validation exist, even when Basic Auth is in use.