Bola Idor in Restify
How Bola Idor Manifests in Restify
BOLA/IdOR (Broken Object Level Authorization/Insecure Direct Object References) occurs when a Restify application fails to properly verify whether a user is authorized to access a specific object. In Restify, this vulnerability often manifests through route handlers that assume the requesting user has rights to the object identified in the URL parameters.
A common pattern in Restify applications involves fetching objects directly from databases or services using IDs from the request without validating ownership. For example:
const restify = require('restify');
const server = restify.createServer();
// Vulnerable: No ownership verification
server.get('/users/:userId/orders/:orderId', (req, res) => {
const { userId, orderId } = req.params;
// Directly fetches order without checking if user owns it
Order.findById(orderId, (err, order) => {
if (err) return res.send(500, err);
res.send(200, order);
});
});
In this Restify endpoint, any authenticated user can access any order by simply changing the orderId parameter. The server trusts the URL parameter without verifying that the authenticated user (req.user) actually owns the requested order.
Restify's parameter parsing and routing make this particularly dangerous because URL parameters are automatically parsed and made available in req.params without any built-in authorization layer. The framework's simplicity and flexibility mean developers must explicitly implement authorization checks.
Another Restify-specific manifestation occurs with nested resource routes:
server.get('/companies/:companyId/projects/:projectId/tasks/:taskId', (req, res) => {
const { companyId, projectId, taskId } = req.params;
// Vulnerability: No verification that user belongs to company
// or has access to project or task
Task.findById(taskId, (err, task) => {
if (err) return res.send(500, err);
res.send(200, task);
});
});
Here, a user could access tasks from any project by guessing task IDs, even if they don't belong to the associated company or project. Restify's hierarchical routing makes it easy to create these nested endpoints without considering the authorization implications.
Restify applications often compound this issue by using middleware that populates req.user from authentication tokens, but then forgetting to use that information for authorization:
const authMiddleware = require('./auth-middleware');
server.use(authMiddleware); // Populates req.user
server.get('/documents/:docId', (req, res) => {
const docId = req.params.docId;
// Vulnerability: req.user exists but isn't checked
Document.findById(docId, (err, doc) => {
if (err) return res.send(500, err);
res.send(200, doc);
});
});
The presence of authentication middleware creates a false sense of security—the application appears protected, but authorization is still missing.
Restify-Specific Detection
Detecting BOLA/IdOR vulnerabilities in Restify applications requires both manual code review and automated scanning. middleBrick's black-box scanning approach is particularly effective for Restify APIs because it tests the actual runtime behavior without needing source code access.
middleBrick scans Restify endpoints by systematically testing parameter manipulation patterns. For a Restify endpoint like:
server.get('/users/:userId/files/:fileId', (req, res) => {
const { userId, fileId } = req.params;
// Vulnerable: No ownership check
File.findById(fileId, (err, file) => {
if (err) return res.send(500, err);
res.send(200, file);
});
});
middleBrick would detect this by:
- Authenticating with one user account
- Accessing a resource the user owns
- Then attempting to access the same resource type with modified IDs
- Flagging any successful access to resources not owned by the authenticated user
The scanner's 12 parallel security checks include specific BOLA/IdOR detection that looks for these parameter tampering patterns in Restify's URL-based routing system.
For development teams, the middleBrick CLI provides a quick way to scan Restify APIs:
npm install -g middlebrick
middlebrick scan https://api.yourservice.com
This command tests all accessible endpoints, including Restify's parameter-based routes, and returns a security score with BOLA/IdOR findings highlighted.
GitHub Action integration allows teams to catch these vulnerabilities before deployment:
- name: Scan API Security
uses: middlebrick/middlebrick-action@v1
with:
api_url: http://localhost:8080
fail_below_score: 80
token: ${{ secrets.MIDDLEBRICK_TOKEN }}
This setup ensures Restify endpoints are automatically scanned in CI/CD pipelines, preventing BOLA/IdOR vulnerabilities from reaching production.
Restify-Specific Remediation
Remediating BOLA/IdOR in Restify requires implementing proper authorization checks at the route handler level. The most effective approach is to verify resource ownership before returning any data.
Here's a secure Restify pattern for user-owned resources:
const restify = require('restify');
const server = restify.createServer();
// Middleware to populate req.user from JWT or session
server.use(authMiddleware);
// Secure pattern: Verify ownership before access
server.get('/users/:userId/orders/:orderId', (req, res, next) => {
const { userId, orderId } = req.params;
const authenticatedUserId = req.user.id;
// Check if authenticated user matches requested user
if (authenticatedUserId !== parseInt(userId)) {
return res.send(403, { error: 'Access denied' });
}
// Verify order belongs to user
Order.findOne({
_id: orderId,
userId: authenticatedUserId
}, (err, order) => {
if (err) return next(err);
if (!order) return res.send(404, { error: 'Order not found' });
res.send(200, order);
});
});
This Restify-specific pattern ensures both that the user is accessing their own data and that the resource actually belongs to them.
For nested resources, implement hierarchical authorization:
server.get('/companies/:companyId/projects/:projectId/tasks/:taskId', (req, res, next) => {
const { companyId, projectId, taskId } = req.params;
const userId = req.user.id;
// Verify user belongs to company
Company.findById(companyId, (err, company) => {
if (err) return next(err);
if (!company || !company.users.includes(userId)) {
return res.send(403, { error: 'Not authorized for this company' });
}
// Verify project belongs to company
Project.findById(projectId, (err, project) => {
if (err) return next(err);
if (!project || project.companyId !== companyId) {
return res.send(403, { error: 'Project not found in company' });
}
// Verify task belongs to project
Task.findById(taskId, (err, task) => {
if (err) return next(err);
if (!task || task.projectId !== projectId) {
return res.send(404, { error: 'Task not found' });
}
res.send(200, task);
});
});
});
});
Restify's middleware system enables reusable authorization logic:
function authorizeResource(resourceType, resourceIdParam, ownerIdField) {
return (req, res, next) => {
const resourceId = req.params[resourceIdParam];
const userId = req.user.id;
const Model = getModelForResourceType(resourceType);
Model.findOne({
_id: resourceId,
[ownerIdField]: userId
}, (err, resource) => {
if (err) return next(err);
if (!resource) return res.send(403, { error: 'Access denied' });
req.authorizedResource = resource;
next();
});
};
}
// Usage
server.get('/users/:userId/files/:fileId',
authorizeResource('file', 'fileId', 'userId'),
(req, res) => {
res.send(200, req.authorizedResource);
}
);
This pattern separates authorization concerns from business logic, making Restify applications more maintainable and secure.
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 |