Privilege Escalation in Feathersjs with Basic Auth
Privilege Escalation in Feathersjs with Basic Auth
FeathersJS is a framework for creating JavaScript APIs with services and hooks. When Basic Auth is used for authentication, privilege escalation can occur if authorization is not enforced consistently across services or if hooks are misconfigured. A BOLA/IDOR or BFLA (Broken Function Level Authorization) issue in this context means a lower-privilege account can act as a higher-privilege account by manipulating identifiers or bypassing role checks.
Consider a FeathersJS app with a users service that exposes /users and an authenticated endpoint /account. If the service does not validate that the requesting user can only access their own record, an attacker authenticated with Basic Auth can change the user id in the request (e.g., from /users/me to /users/123) and read or modify another user’s data. This becomes privilege escalation when roles are stored on the user object (e.g., role: "user" vs role: "admin") but not verified on each operation.
Example vulnerable FeathersJS service with Basic Auth (authentication via an auth hook, but missing proper authorization):
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const auth = require('@feathersjs/authentication');
const jwt = require('@feathersjs/authentication-jwt');
const local = require('@feathersjs/authentication-local');
const app = express(feathers());
app.configure(auth({
secret: 'your-secret',
authentication: ['local', 'jwt']
}));
app.use('/authentication', local.authentication());
// Example users service (in-memory for illustration)
app.use('/users', {
async find(params) {
// WARNING: No authorization check ensuring params.user.id matches requested user id
return [
{ id: 1, username: 'alice', role: 'admin' },
{ id: 2, username: 'bob', role: 'user' }
];
},
async get(id, params) {
// WARNING: Returns user record without verifying that id belongs to the authenticated subject
return { id, username: 'bob', role: 'user' };
}
});
// A misconfigured hook that skips role validation
app.service('users').hooks({
before: {
all: [],
find: [],
get: [context => {
// Missing: ensure context.params.user.id === id
// Missing: verify context.params.user.role when performing admin-sensitive actions
return context;
}],
create: [],
update: [],
patch: [],
remove: []
}
});
module.exports = app;
In this setup, a user authenticating with Basic Auth receives a JWT or session after login. If the client can tamper with the resource identifier (id), they can enumerate or modify records they should not access, effectively escalating from a standard user to another user’s permissions. The vulnerability is not in Basic Auth itself, but in the lack of binding the authenticated subject to the requested resource and missing role-based checks at the function level.
Another scenario involves an admin-only endpoint exposed without role verification. If an endpoint like PATCH /users/:id/role is available and the hook does not assert that context.params.user.role === 'admin', a regular user can escalate privileges by calling this endpoint with another user’s id.
Real-world parallels: This class of issue maps to OWASP API Top 10 2023 — BOLA (broken object level authorization) and BFLA (broken function level authorization). It also relates to improper authorization checks noted in frameworks like FeathersJS when developers rely solely on authentication and skip per-request authorization. There are no known public CVEs specific to FeathersJS Basic Auth privilege escalation, but the pattern is common across Node.js services that omit context-aware authorization.
Basic Auth-Specific Remediation in Feathersjs
Remediation centers on enforcing authorization on every service method and ensuring the authenticated subject can only operate on their own data unless explicitly elevated by verified role checks. Use hook context to access the authenticated subject and compare it with the resource identifier. Implement role checks before sensitive operations.
Secure FeathersJS service with proper authorization (using the authenticated user from context):
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const auth = require('@feathersjs/authentication');
const app = express(feathers());
app.configure(auth({
secret: 'your-secret',
authentication: ['local']
}));
app.use('/users', {
async find(params) {
const { user } = params;
// Ensure users can only see their own record unless they are admin
if (user && user.role === 'admin') {
return /* list of users or filtered list per policy */;
}
// Non-admin users only get themselves
return [{ id: user.id, username: user.username, role: user.role }];
},
async get(id, params) {
const { user } = params;
// Authorization: user can only get their own record
if (String(user.id) !== String(id)) {
throw new Error('Unauthorized: you can only access your own record');
}
return { id, username: user.username, role: user.role };
}
});
// Secure hook that validates ownership or admin role before sensitive actions
app.service('users').hooks({
before: {
update: [context => {
const { user } = context.params;
const { id } = context.id;
if (String(user.id) !== String(id) && user.role !== 'admin') {
throw new Error('Unauthorized: cannot update other users');
}
return context;
}],
patch: [context => {
const { user } = context.params;
const { id } = context.id;
if (String(user.id) !== String(id) && user.role !== 'admin') {
throw new Error('Unauthorized: cannot patch other users');
}
// Example: prevent non-admins from promoting roles
if (context.data && context.data.role && user.role !== 'admin') {
throw new Error('Unauthorized: role modification requires admin privileges');
}
return context;
}],
remove: [context => {
const { user } = context.params;
const { id } = context.id;
if (String(user.id) !== String(id) && user.role !== 'admin') {
throw new Error('Unauthorized: cannot remove other users');
}
return context;
}]
}
});
// Example with Basic Auth credentials available via params.auth if using feathers-authentication with express
// Ensure your authentication hook populates params.auth and params.user consistently.
module.exports = app;
Key practices:
- Always compare the authenticated user’s id with the resource id on get, update, patch, and remove.
- Check roles at the function level for admin-only endpoints (e.g., changing roles, viewing all users).
- Do not rely on client-supplied role values; derive role from the authenticated subject stored server-side (e.g., JWT payload or session).
- Use middleware/hooks to centralize authorization logic rather than scattering checks across route handlers.
These steps reduce BOLA and BFLA risks by tightly coupling authentication context to authorization decisions. The approach aligns with OWASP API Security Top 10 controls for broken access control and helps maintain least-privilege access.