HIGH bola idorsails

Bola Idor in Sails

How BOLA/IdOR Manifests in Sails

Model-based authorization (BOLA) and Insecure Direct Object Reference (IdOR) vulnerabilities are particularly prevalent in Sails applications due to the framework's opinionated approach to data access and its common patterns for handling user authentication.

In Sails, the most common BOLA/IdOR pattern emerges through the req.user object and model queries. Consider a typical Sails controller action:

async findUserProfile(req, res) {
const user = await User.findOne({ id: req.params.id });
return res.json(user);
}

The vulnerability here is subtle but critical: the code fetches a user by ID without verifying that the requesting user has permission to access that specific user's data. An attacker can simply increment the ID parameter to access other users' profiles.

Sails's blueprint API exacerbates this issue. When you enable blueprints without proper policies, Sails automatically generates RESTful endpoints that expose all model operations. A User model with blueprints enabled creates endpoints like:

GET /user/:id
GET /user/:id/profile
PUT /user/:id
PATCH /user/:id

Without explicit authorization checks, these endpoints become direct object reference vulnerabilities. An authenticated user can access any user record by knowing or guessing the ID.

Another Sails-specific manifestation occurs in association handling. Sails provides convenient methods for populating associations, but these can introduce BOLA vulnerabilities:

async getUserProjects(req, res) {
const projects = await Project.find({
where: { owner: req.params.id },
populate: ['tasks', 'team']
});
return res.json(projects);
}

Here, the code fetches projects for any user ID provided in the URL without verifying the requester's relationship to those projects. An attacker can enumerate project IDs to access data they shouldn't see.

Sails Waterline ORM's query flexibility can also introduce BOLA vulnerabilities through improper filtering. Consider this common pattern:

async getUserMessages(req, res) {
const messages = await Message.find({
where: { recipient: req.params.id }
}).sort('createdAt DESC');
return res.json(messages);
}

The vulnerability is that the code trusts the URL parameter for the recipient ID without validating that the authenticated user is actually the intended recipient.

Even more subtle BOLA issues arise in Sails policies. A policy might check if a user is authenticated but fail to verify resource ownership:

module.exports = async function(req, res, next) {
if (req.session.authenticated) {
return next();
}
return res.forbidden();
}

This policy only checks authentication status, not whether the user should access the specific resource they're requesting.

Sails-Specific Detection

Detecting BOLA/IdOR vulnerabilities in Sails applications requires both static analysis of your codebase and dynamic runtime testing. Here's how to identify these issues specifically in Sails applications.

Static Code Analysis

Search your Sails controllers and services for patterns that indicate potential BOLA vulnerabilities. Look for:

grep -r "findOne(" controllers/ | grep -v "req.user.id"
grep -r "findById(" controllers/ | grep -v "req.user.id"
grep -r "find({" controllers/ | grep -v "where.*req.user"

These patterns help identify queries that use URL parameters or other untrusted input for object identification without proper authorization checks.

Blueprint API Exposure

Check your config/blueprints.js for overly permissive settings:

module.exports.blueprints = {
actions: true,
rest: true,
shortcuts: true,
populate: true
};

Blueprints with these settings expose CRUD operations without any authorization. Review each model to determine if blueprint access is appropriate.

Policy Implementation Audit

Examine your policies in api/policies/ to ensure they perform proper authorization, not just authentication:

module.exports = async function(req, res, next) {
const record = await Model.findOne({
id: req.params.id,
owner: req.user.id
});
if (!record) {
return res.forbidden();
}
return next();
}

Policies should verify resource ownership, not just user authentication status.

Dynamic Testing with middleBrick

middleBrick's BOLA/IdOR scanner specifically tests Sails applications by:

  • Analyzing your OpenAPI/Swagger spec (if available) to understand API structure
  • Testing authenticated endpoints with parameter manipulation to detect unauthorized access
  • Checking for common Sails patterns like req.params.id usage without ownership verification
  • Testing blueprint endpoints for unauthorized data exposure
  • Scanning for association-based vulnerabilities in populate operations

To scan your Sails API:

middlebrick scan https://yourapp.com/api
middlebrick scan --spec openapi.json

The scanner tests for BOLA vulnerabilities by attempting authenticated requests with manipulated parameters and checking if unauthorized data is returned.

Waterline Query Analysis

Review your Waterline queries for direct parameter usage:

const vulnerable = await Model.findOne({
id: req.params.id // vulnerable - no ownership check
});

const secure = await Model.findOne({
id: req.params.id,
owner: req.user.id // secure - verifies ownership
});

The key indicator of BOLA vulnerability is using req.params.id, req.body.id, or similar untrusted input directly in queries without ownership validation.

Sails-Specific Remediation

Remediating BOLA/IdOR vulnerabilities in Sails requires a multi-layered approach using Sails's native features and security best practices. Here are specific remediation strategies for Sails applications.

Policy-Based Authorization

Create reusable policies for common authorization patterns:

// api/policies/ownerPolicy.js
module.exports = async function(req, res, next) {
const model = req.options.model;
const id = req.params.id;
const userId = req.user.id;

if (!model || !id || !userId) {
return res.forbidden();
}

try {
const record = await sails.models[model].findOne({
id: id,
owner: userId
});

if (!record) {
return res.forbidden();
}

req.record = record;
return next();
} catch (err) {
return res.serverError(err);
}
};

Apply this policy to routes that require ownership verification:

// config/policies.js
module.exports.policies = {
UserController: {
findProfile: ['ownerPolicy', 'isAuthenticated'],
updateProfile: ['ownerPolicy', 'isAuthenticated']
},
ProjectController: {
find: ['ownerPolicy', 'isAuthenticated'],
update: ['ownerPolicy', 'isAuthenticated']
}
};

Secure Controller Patterns

Refactor vulnerable controller actions to use secure patterns:

// Vulnerable pattern
async findUserProfile(req, res) {
const user = await User.findOne({ id: req.params.id });
return res.json(user);
}

// Secure pattern
async findUserProfile(req, res) {
const user = await User.findOne({
id: req.params.id,
owner: req.user.id
});

if (!user) {
return res.notFound();
}

return res.json(user);
}

Blueprint Security Configuration

Configure blueprints with security in mind:

// config/blueprints.js
module.exports.blueprints = {
actions: true,
rest: true,
shortcuts: true,
populate: true,

// Add security middleware
beforeCreate: async (values, proceed) => {
if (values.owner === undefined) {
values.owner = req.user.id;
}
return proceed();
},

beforeUpdate: async (values, proceed) => {
if (values.owner !== undefined && values.owner !== req.user.id) {
return proceed(new Error('Cannot change record ownership'));
}
return proceed();
}
};

Waterline Query Security

Use Waterline's query capabilities to enforce authorization at the database level:

// Secure find with multiple conditions
async getUserData(req, res) {
const userId = req.user.id;
const targetId = req.params.id;

// Only allow access to own data or admin access
const isAdmin = await User.isAdmin(userId);

const query = { id: targetId };
if (!isAdmin) {
query.owner = userId;
}

const user = await User.findOne(query);
if (!user) {
return res.notFound();
}

return res.json(user);
}

Association Security

Secure association queries in Sails:

// Vulnerable
async getUserProjects(req, res) {
const projects = await Project.find({
where: { owner: req.params.id },
populate: ['tasks', 'team']
});
return res.json(projects);
}

// Secure
async getUserProjects(req, res) {
const userId = req.user.id;
const targetId = req.params.id;

// Verify requester has access to target user
const canAccess = await canUserAccessTarget(userId, targetId);
if (!canAccess) {
return res.forbidden();
}

const projects = await Project.find({
where: { owner: targetId },
populate: ['tasks', 'team']
});

return res.json(projects);
}

Input Validation and Sanitization

Always validate and sanitize ID parameters:

async secureFind(req, res) {
const id = req.params.id;
if (!isValidObjectId(id)) {
return res.badRequest('Invalid ID format');
}

const record = await Model.findOne({
id: id,
owner: req.user.id
});

if (!record) {
return res.notFound();
}

return res.json(record);
}

Testing with middleBrick

After implementing these remediations, verify your fixes using middleBrick's continuous scanning:

middlebrick scan --continuous --fail-threshold B

This ensures your BOLA vulnerabilities remain fixed as your codebase evolves.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How do I know if my Sails API has BOLA vulnerabilities?
Look for controller actions that use req.params.id or similar untrusted input in database queries without ownership verification. Use middleBrick to scan your API - it specifically tests for BOLA by attempting authenticated requests with manipulated parameters to detect unauthorized data access.
Does disabling blueprints in Sails eliminate BOLA risks?
Not entirely. While disabling blueprints reduces the attack surface by removing automatic CRUD endpoints, BOLA vulnerabilities can still exist in custom controller actions. You need to implement proper authorization checks in all data access code, regardless of whether blueprints are enabled.