Privilege Escalation in Adonisjs
How Privilege Escalation Manifests in Adonisjs
Privilege escalation in Adonisjs applications often occurs through improper authorization checks and role-based access control (RBAC) weaknesses. The framework's elegant middleware system and Lucid ORM can create subtle vulnerabilities when developers assume certain protections are in place.
One common pattern involves bypassing authorization middleware. Consider a user management route:
Route.put('/users/:id', 'UserController.update')If the update method doesn't verify that the requester has permission to modify the target user, any authenticated user could escalate privileges by updating their role or admin status:
async update({ params, request, auth }) {
const user = await User.find(params.id)
user.merge(request.post())
await user.save()
return user
}Without an authorization check like if (auth.user.id !== params.id && !auth.user.isAdmin), this becomes a privilege escalation vulnerability.
Adonisjs's Gate and Policy system, while powerful, can be misused. Developers sometimes define overly permissive policies or skip policy checks entirely:
const Post = use('App/Models/Post')
const { policy } = use('App/Providers/AuthorizationProvider')
// Weak policy definition
policy.register('Post', {
update: () => true, // Allows any authenticated user to update any post
})Another Adonisjs-specific issue involves Lucid model relationships. When fetching related data without proper authorization, users might access data they shouldn't see:
async show({ params }) {
const post = await Post.query()
.where('id', params.id)
.with('comments.author', (builder) => {
// No authorization check on comment authors
builder.select('id', 'username')
})
.firstOrFail()
return post
}Even if users can't directly modify comments, they might extract sensitive information about other users through exposed relationships.
Middleware ordering is another critical area. Adonisjs executes middleware in the order they're defined, and placing authorization middleware after business logic creates windows for privilege escalation:
Route.put('/posts/:id', async ({ params, request, auth }) => {
const post = await Post.find(params.id)
// Business logic executes first
post.merge(request.post())
await post.save()
})
.middleware('auth')
.middleware('canUpdatePost')If canUpdatePost throws an error after the save operation, the privilege escalation has already occurred.
Adonisjs-Specific Detection
Detecting privilege escalation in Adonisjs requires examining both the codebase and runtime behavior. Static analysis should focus on policy definitions, middleware usage, and authorization patterns.
Code scanning should identify these anti-patterns:
// Search for missing authorization checks
const missingAuthPatterns = [
/find\(([^)]+)\)/,
/firstOrFail\(\)/,
/update\(\)/,
/delete\(\)/
]Dynamic analysis with middleBrick's black-box scanner specifically tests for privilege escalation by attempting authenticated actions on resources belonging to other users:
// Test authenticated user attempting to modify another user's data
const testPrivilegeEscalation = async (baseUrl) => {
const authClient = await createAuthenticatedClient()
// Create test data
const victim = await User.create({ email: '[email protected]', role: 'user' })
const attacker = await User.create({ email: '[email protected]', role: 'user' })
// Attempt to modify victim's data as attacker
const response = await authClient
.put(`${baseUrl}/users/${victim.id}`)
.send({ role: 'admin' })
.expect(200)
return response.body.role === 'admin'
}middleBrick's scanner automates these tests across 12 security categories, including BFLA/Privilege Escalation checks. The scanner attempts authenticated requests with elevated permissions and verifies whether authorization controls properly restrict access.
Middleware analysis is crucial for Adonisjs applications. The scanner examines middleware chains to ensure authorization middleware executes before any data modification:
// Check middleware ordering
const checkMiddlewareOrder = (route) => {
const authIndex = route.middleware.indexOf('auth')
const canIndex = route.middleware.indexOf('canUpdatePost')
if (canIndex > -1 && authIndex > canIndex) {
return 'Authorization middleware should execute before auth'
}
}middleBrick's OpenAPI analysis also validates that API specifications don't expose privileged operations without proper authentication requirements, cross-referencing spec definitions with actual runtime behavior.
Adonisjs-Specific Remediation
Adonisjs provides several native mechanisms for preventing privilege escalation. The Gate and Policy system offers fine-grained authorization control:
// app/Policies/PostPolicy.js
class PostPolicy {
async update(user, post) {
// Only allow post owners or admins to update
return user.id === post.user_id || user.isAdmin
}
async delete(user, post) {
// More restrictive: only admins or post owners can delete
return user.id === post.user_id || user.role === 'admin'
}
}
module.exports = PostPolicyRegister the policy in the authorization provider:
// app/Providers/AuthorizationProvider.js
class AuthorizationProvider {
async boot() {
const Policy = use('App/Providers/AuthorizationProvider')
Policy.register('Post', use('App/Policies/PostPolicy'))
}
}Apply policies at the route level using middleware:
Route.put('/posts/:id', 'PostController.update')
.middleware(['auth', 'can:Post.update'])For controller methods, use the can helper:
async update({ params, request, auth }) {
const post = await Post.find(params.id)
if (!(await can(auth.user, 'update', post))) {
return response.status(403).send('Unauthorized')
}
post.merge(request.post())
await post.save()
return post
}Adonisjs's Lucid ORM provides query-level authorization to prevent unauthorized data access:
// app/Models/User.js
static boot() {
super.boot()
this.addHook('beforeFetch', (model) => {
const auth = use('Adonis/Addons/Auth')
if (auth.user && !auth.user.isAdmin) {
// Scope queries to only return user's own data
model.query().where('id', auth.user.id)
}
})
}For API endpoints, implement role-based access control using middleware:
// app/Middleware/RequireRole.js
class RequireRole {
async handle({ auth, response }, next, allowedRoles) {
const user = auth.user
if (!user || !allowedRoles.includes(user.role)) {
return response.status(403).send('Insufficient privileges')
}
await next()
}
}
module.exports = RequireRoleApply the middleware to routes:
Route.put('/admin/users/:id', 'AdminController.updateUser')
.middleware(['auth', 'requireRole:admin'])middleBrick's scanner can verify these protections are correctly implemented by testing authenticated requests against protected resources and validating that authorization controls properly restrict access based on user roles and permissions.