Privilege Escalation in Feathersjs
How Privilege Escalation Manifests in Feathersjs
Privilege escalation in Feathersjs applications often occurs through improper service access controls and inadequate authentication checks. The framework's flexible service architecture can create subtle security gaps when developers assume certain operations are protected by default.
A common pattern involves services that expose sensitive data through find() or get() operations without proper authorization checks. For example:
class UsersService {
async find(params) {
// No authorization check - any authenticated user can see all users
return await this.Model.findAll();
}
}This allows any authenticated user to retrieve the complete user database, including sensitive fields like email addresses, roles, and hashed passwords. The issue compounds when services use dynamic ID parameters without validating whether the requesting user has permission to access that specific resource.
Another Feathersjs-specific vulnerability appears in nested service calls where parent services don't validate child service permissions. Consider a messaging system:
class MessagesService {
async get(id, params) {
const message = await this.Model.findByPk(id);
}
}Users can access any message by simply knowing its ID, bypassing intended access controls. This becomes particularly dangerous when combined with Feathersjs's powerful query syntax that allows filtering and projection operations.
Hook-based privilege escalation often occurs when developers forget to apply authentication hooks to all service methods. A service might have:
class OrdersService {
async find(params) {
// Missing authentication hook allows unauthenticated access
}
}Without the authenticate hook, anyone can access order data. Similarly, developers sometimes apply overly permissive hooks that only check authentication but not authorization, allowing any authenticated user to perform admin-level operations.
Feathersjs's real-time features through Socket.io can also introduce privilege escalation if channel permissions aren't properly configured. A channel that broadcasts all user data to connected clients without filtering can leak sensitive information across user boundaries.
Feathersjs-Specific Detection
Detecting privilege escalation in Feathersjs requires examining both the service definitions and the hook chains that control access. middleBrick's black-box scanning approach is particularly effective for Feathersjs applications since it tests the actual runtime behavior without requiring source code access.
middleBrick scans for several Feathersjs-specific privilege escalation patterns:
- Unauthenticated service access - Testing if services respond to requests without valid authentication tokens
- Permission bypass through ID manipulation - Attempting to access resources by modifying ID parameters to see if authorization checks are properly enforced
- Excessive data exposure - Checking if services return more data than intended, including sensitive fields
- Hook chain analysis - Verifying that authentication and authorization hooks are properly chained and applied to all service methods
For local analysis, you can audit your Feathersjs services by examining the hook chains. Each service should have authentication hooks applied to all methods:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { authorize } = require('@feathersjs/authorization').hooks;
class SecureUsersService {
async find(params) {
return await this.Model.findAll();
}
}
module.exports = function() {
const app = this;
const users = app.use('/users', new UsersService(app.get('sequelize')));
// Apply hooks to all service methods
users.hooks({
before: {
all: [authenticate('jwt'), authorize()],
find: [checkUserPermissions],
get: [checkResourceOwnership]
}
});
};middleBrick's scanning also identifies issues with Feathersjs's dynamic service registration where services might be created without proper security configurations. The scanner tests various HTTP methods and payload combinations to uncover authentication bypass opportunities.
For OpenAPI/Swagger analysis, middleBrick cross-references your API specification with actual runtime behavior, identifying discrepancies between documented security requirements and implemented protections.
Feathersjs-Specific Remediation
Remediating privilege escalation in Feathersjs requires a defense-in-depth approach using the framework's built-in security features. The most effective strategy combines proper hook chaining, role-based access control, and careful service design.
Start by implementing comprehensive authentication and authorization hooks. Feathersjs provides these out of the box:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { authorize } = require('@feathersjs/authorization').hooks;
const { iff, isProvider } = require('feathers-hooks-common');
module.exports = function() {
const app = this;
const users = app.use('/users', new UsersService(app.get('sequelize')));
users.hooks({
before: {
all: [authenticate('jwt')],
find: [authorize(), limitUserData],
get: [authorize(), checkResourceOwnership],
create: [authorize('admin')],
update: [authorize('admin')],
patch: [authorize(), checkResourceOwnership],
remove: [authorize('admin')]
},
all: [sanitizeUserData]
}
});
};The key is applying authorize() hooks to all methods and using conditional logic for different permission levels. For resource-specific access, implement ownership checks:
async function checkResourceOwnership(context) {
const { params, id } = context;
const userId = params.user?.id;
const resource = await context.service.get(id);
if (resource.userId !== userId && !params.user.isAdmin) {
throw new Error('Unauthorized access');
}
return context;
}For data exposure prevention, sanitize responses to remove sensitive fields:
async function sanitizeUserData(context) {
const result = context.result;
if (Array.isArray(result)) {
return result.map(item => {
delete item.password;
delete item.sensitiveData;
return item;
});
} else if (result) {
delete result.password;
delete result.sensitiveData;
}
return context;
}Implement role-based access control using custom authorization logic:
async function authorizeRole(roles = []) {
return async context => {
const { user } = context.params;
if (!user) throw new Error('Not authenticated');
if (!roles.length || roles.includes(user.role)) return context;
throw new Error('Insufficient permissions');
};
}
// Usage in hooks
create: [authorizeRole(['admin', 'manager'])],
remove: [authorizeRole(['admin'])]For real-time features, configure channels with proper filtering:
app.on('connection', (connection) => {
app.channel('authenticated').join(connection);
});
app.publish((data, hook) => {
if (data.service === 'users') {
return app.channel(`user-${data.user.id}`);
}
return app.channel('authenticated');
});Finally, use middleBrick's CLI tool to continuously scan your APIs during development:
npm install -g middlebrick
middlebrick scan https://api.your-app.com --frequency daily --threshold BThis catches privilege escalation issues before they reach production, ensuring your Feathersjs application maintains proper access controls throughout the development lifecycle.
Frequently Asked Questions
How does middleBrick detect privilege escalation in Feathersjs applications?
middleBrick uses black-box scanning to test your Feathersjs API endpoints without requiring source code access. It attempts authenticated and unauthenticated requests to each service method, manipulates ID parameters to test authorization checks, and analyzes responses for data exposure. The scanner also examines your OpenAPI/Swagger specification to identify discrepancies between documented security requirements and actual runtime behavior. middleBrick's 12 parallel security checks specifically target authentication bypass, permission escalation, and data exposure vulnerabilities common in Feathersjs applications.
What's the difference between authentication and authorization in Feathersjs security?
Authentication verifies who a user is (typically through JWT tokens or session cookies), while authorization determines what an authenticated user is allowed to do. In Feathersjs, authentication hooks like authenticate('jwt') validate tokens and populate context.params.user, but this alone isn't sufficient. You need authorization hooks like authorize() to enforce role-based permissions and resource ownership checks. A common mistake is applying authentication without authorization, which allows any authenticated user to access any resource. Proper security requires both layers: first verify identity, then verify permissions for the requested operation.