Insecure Direct Object Reference in Feathersjs
How Insecure Direct Object Reference Manifests in Feathersjs
Insecure Direct Object Reference (IDOR) in Feathersjs typically occurs when applications expose internal object identifiers and rely on client-side access controls. Feathersjs's flexible service architecture and dynamic routing can create multiple attack vectors if proper authorization isn't implemented.
The most common IDOR pattern in Feathersjs involves service methods that accept ID parameters without validating whether the requesting user owns or has permission to access the resource. Consider this vulnerable Feathersjs service:
const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
class UsersService {
async get(id, params) {
// Vulnerable: no ownership check
return this._super(id, params);
}
async update(id, data, params) {
// Vulnerable: any authenticated user can update any user
return this._super(id, data, params);
}
async remove(id, params) {
// Vulnerable: any authenticated user can delete any user
return this._super(id, params);
}
}
The core issue is that Feathersjs services automatically handle CRUD operations without built-in authorization checks. A malicious user can simply modify the ID parameter in API requests to access other users' data.
Another Feathersjs-specific IDOR pattern occurs with params.query objects. Attackers can manipulate query parameters to bypass filters:
// Vulnerable: query parameter manipulation
app.service('messages').find({
query: { userId: req.user.id }
});
// Attacker can modify request to:
app.service('messages').find({
query: { userId: 'malicious-user-id' }
});
Feathersjs's hooks system, while powerful, can also introduce IDOR vulnerabilities if hooks aren't properly chained. A common mistake is applying authentication hooks but forgetting authorization hooks:
// Vulnerable hook chain
{
before: {
all: [ authenticate('jwt') ],
get: [],
update: []
}
}
// Missing: authorization hooks to verify resource ownership
Feathersjs's population feature can also create IDOR risks. When services automatically populate related data, attackers might access relationships they shouldn't see:
// Vulnerable: automatic population without access control
app.service('posts').get(id, {
populate: 'comments'
});
// Attacker could access comments on posts they don't own
Feathersjs-Specific Detection
Detecting IDOR in Feathersjs applications requires both static code analysis and dynamic runtime testing. The dynamic nature of Feathersjs services means vulnerabilities can hide in unexpected places.
Static analysis should focus on service method implementations. Look for these red flags in your Feathersjs codebase:
// Search for patterns like this:
async get(id, params) {
return this._super(id, params); // No authorization check
}
async update(id, data, params) {
return this._super(id, data, params); // No ownership verification
}
async remove(id, params) {
return this._super(id, params); // No permission validation
}
Pay special attention to services that handle user data, financial information, or sensitive content. Feathersjs applications often have services like 'users', 'accounts', 'messages', 'files', or 'documents' that are prime targets for IDOR attacks.
Dynamic testing with middleBrick specifically targets Feathersjs IDOR vulnerabilities through black-box scanning. The scanner tests unauthenticated and authenticated endpoints for parameter manipulation:
npm install -g middlebrick
middlebrick scan https://your-feathersjs-app.com/api/messages
middleBrick's IDOR detection includes testing for:
- Sequential ID enumeration (user/1, user/2, user/3...)
- GUID/UUID substitution attacks
- Negative ID testing (-1, -999)
- SQL injection in ID parameters
- Path traversal in file-based services
For Feathersjs applications using MongoDB, middleBrick tests ObjectId manipulation:
// Test if changing ObjectId grants access to other documents
const validId = new ObjectId('507f1f77bcf86cd799439011');
const maliciousId = new ObjectId('507f1f77bcf86cd799439012');
middleBrick also detects IDOR in Feathersjs's real-time features. Socket.io and Primus-based real-time services can have IDOR vulnerabilities where subscription filters aren't properly enforced:
// Vulnerable real-time subscription
app.service('messages').publish((data, context) => {
return app.channel(`user-${data.userId}`);
});
// Attacker could subscribe to other users' channels
Feathersjs-Specific Remediation
Feathersjs provides several native mechanisms to prevent IDOR vulnerabilities. The most effective approach combines service-level authorization with proper hook implementation.
The recommended pattern uses Feathersjs's verifyPermissions hook from @feathersjs/authentication-hooks:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider, verifyPermissions } = require('feathers-hooks-common');
const usersService = {
before: {
all: [
iff(
isProvider('external'),
authenticate('jwt')
)
],
get: [
verifyPermissions({
roles: ['admin', 'user'],
field: 'id',
owner: async (id, context) => {
const user = await context.app.service('users').get(id);
return user.id === context.params.user.id;
}
})
],
update: [
verifyPermissions({
roles: ['admin'],
field: 'id',
owner: async (id, context) => {
return id === context.params.user.id.toString();
}
})
]
}
};
For more granular control, implement custom authorization hooks specific to your business logic:
const checkOwnership = async (context) => {
const { id, params, method } = context;
const userId = params.user.id;
// Special handling for different resource types
if (context.service.name === 'users') {
if (method === 'remove' && !params.user.isAdmin) {
throw new Error('Only admins can delete users');
}
if (id !== userId && !params.user.isAdmin) {
throw new Error('You can only access your own user record');
}
}
if (context.service.name === 'messages') {
const message = await context.app.service('messages').get(id);
if (message.userId !== userId && !params.user.isAdmin) {
throw new Error('Unauthorized access to message');
}
}
};
Feathersjs's fastifyAdapter or expressAdapter integration allows middleware-based authorization for REST endpoints:
const isAdmin = (req, res, next) => {
if (req.user && req.user.isAdmin) {
return next();
}
res.status(403).json({ error: 'Admin privileges required' });
};
app.get('/admin/users/:id', isAdmin, async (req, res) => {
try {
const user = await app.service('users').get(req.params.id);
res.json(user);
} catch (error) {
res.status(404).json({ error: 'User not found' });
}
});
For Feathersjs applications using TypeScript, leverage type safety to prevent IDOR:
interface User {
id: string;
email: string;
isAdmin: boolean;
}
interface Message {
id: string;
userId: string;
content: string;
}
const authorizeMessageAccess = async (id: string, userId: string): Promise => {
const message = await messagesService.get(id);
if (message.userId !== userId && !isAdmin(userId)) {
throw new Error('Unauthorized');
}
return message;
};
Finally, implement comprehensive logging and monitoring to detect potential IDOR attacks:
const auditLog = async (context) => {
const { method, params, id } = context;
const userId = params.user?.id;
if (userId && method !== 'find') {
await app.service('audit-logs').create({
action: `${method} ${context.service.name}`,
resourceId: id,
userId: userId,
timestamp: new Date(),
ip: params.provider === 'rest' ? params.headers['x-forwarded-for'] : 'socket'
});
}
};
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |