Injection Flaws in Sails
How Injection Flaws Manifests in Sails
Injection flaws in Sails applications typically occur when untrusted data is sent to an interpreter as part of a command or query. Sails, being built on Node.js and Express, inherits many of the same injection vulnerabilities as other JavaScript frameworks, but with some Sails-specific patterns that developers should be aware of.
The most common injection flaw in Sails occurs through Waterline ORM queries. Consider this vulnerable pattern:
const { username, password } = req.params.all();
User.find({
where: {
username: username,
password: password
}
}).exec((err, users) => {
// vulnerable to injection via password field
});While Waterline does provide some protection against SQL injection, NoSQL injection is still possible when using MongoDB or other NoSQL databases. An attacker could craft a password value like { "$ne": null } to bypass authentication.
Another Sails-specific injection vector appears in policy chains. Policies are middleware functions that run before controllers, and if they construct dynamic queries based on request parameters without proper sanitization, injection becomes possible:
// Vulnerable policy
module.exports = async (req, res, next) => {
const sortBy = req.query.sortBy || 'createdAt';
const sortOrder = req.query.sortOrder || 'DESC';
// Attacker can inject: sortBy=createdAt&sortOrder=DESC};DROP TABLE users;{
req.sortOptions = `${sortBy} ${sortOrder}`;
return next();
};Sails' dynamic routing can also introduce injection risks. The framework allows for wildcard routes and dynamic parameter handling that, if not properly validated, can lead to path traversal or command injection:
// Vulnerable route
module.exports.routes = {
'GET /download/*': 'FileController.download',
'GET /api/:model/:id': 'DynamicController.show'
};In the above example, an attacker could request /api/User/1%20%7B%22$ne%22:%20null%7D to manipulate the query structure.
Sails-Specific Detection
Detecting injection flaws in Sails applications requires both static analysis and dynamic testing. For static analysis, look for these patterns in your codebase:
Waterline Query Analysis - Examine all find(), create(), update(), and destroy() calls. Check if any query parameters come directly from req.params.all(), req.params(), or req.query without validation.
// Search for these patterns
grep -r "find(" . | grep -E "(params|query)"
grep -r "create(" . | grep -E "(params|query)"
grep -r "update(" . | grep -E "(params|query)"Policy Injection Points - Policies are a unique Sails feature where middleware can construct database queries. Audit all policies for direct parameter usage:
// Check policies for unsafe query construction
find . -name "*.js" -path "*/policies/*" | xargs grep -n "req\.params\|req\.query"Dynamic Route Analysis - Sails' flexible routing can mask injection vulnerabilities. Review all routes in config/routes.js for wildcard patterns and dynamic segments that could be exploited.
For dynamic testing, middleBrick provides specialized scanning for Sails applications. The scanner identifies injection vulnerabilities by:
- Testing Waterline ORM endpoints with NoSQL injection payloads
- Analyzing policy chains for unsafe parameter handling
- Checking dynamic routes for path traversal and command injection
- Scanning for exposed administrative endpoints that could be manipulated
The middleBrick CLI makes this process straightforward:
npm install -g middlebrick
middlebrick scan https://yourapp.com/api/user
# Or integrate into your Sails app
middlebrick scan http://localhost:1337 --recursivemiddleBrick's injection detection includes 12 specific security checks that test for various injection patterns, providing severity levels and remediation guidance specific to Sails' architecture.
Sails-Specific Remediation
Remediating injection flaws in Sails requires a combination of input validation, parameterized queries, and secure coding practices specific to the framework's features.
Waterline Parameter Sanitization - Always validate and sanitize parameters before using them in Waterline queries. Use the built-in sails-hook-validation or explicit validation:
// In config/policies.js
module.exports.policies = {
'*': ['sanitizedParams']
};
// In api/policies/sanitizedParams.js
module.exports = async (req, res, next) => {
const whitelist = ['username', 'email', 'age'];
const sanitized = {};
whitelist.forEach(param => {
if (req.params[param] !== undefined) {
sanitized[param] = String(req.params[param]).trim();
}
});
req.sanitizedParams = sanitized;
return next();
};Safe Query Construction - Never construct Waterline queries from raw request parameters. Use explicit field mapping:
// Vulnerable
const { username, password } = req.params.all();
User.find({ where: { username, password } });
// Secure
const { username } = req.sanitizedParams;
User.find({ where: { username: username } })
.select(['id', 'username', 'email']) // explicit fields
.limit(10)
.exec((err, users) => {
// handle response
});Policy Security - Implement a base policy that validates all incoming parameters:
// api/policies/secureParams.js
const _ = require('lodash');
module.exports = async (req, res, next) => {
const allowed = {
username: v => _.isString(v) && v.length > 0 && v.length < 50,
age: v => _.isInteger(Number(v)) && Number(v) >= 0 && Number(v) <= 120,
email: v => /.+@.+\..+/.test(v)
};
const validated = {};
for (const [key, validator] of Object.entries(allowed)) {
if (req.params[key] !== undefined) {
if (!validator(req.params[key])) {
return res.badRequest(`Invalid parameter: ${key}`);
}
validated[key] = req.params[key];
}
}
req.validatedParams = validated;
return next();
};Route Protection - Use explicit route definitions instead of dynamic patterns when possible:
// config/routes.js
module.exports.routes = {
'GET /api/user/:id': {
controller: 'UserController',
action: 'show',
skipAssets: true
},
'POST /api/user': {
controller: 'UserController',
action: 'create'
}
};Security Middleware - Add security headers and input validation middleware:
// config/http.js
module.exports.http = {
middleware: {
securityHeaders: require('helmet'),
rateLimit: require('express-rate-limit'),
secureParams: require('../api/policies/secureParams')
},
order: [
'startTimer',
'cookieParser',
'session',
'securityHeaders',
'rateLimit',
'bodyParser',
'secureParams',
'router',
'www',
'favicon',
'404',
'500'
]
};