Server Side Template Injection in Sails with Mongodb
Server Side Template Injection in Sails with Mongodb — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template code that is subsequently evaluated by a server-side templating engine. In Sails.js, which encourages rapid development with minimal configuration, SSTI can manifest when user-controlled data is passed into template rendering functions or into view helpers that evaluate expressions. When the application also uses MongoDB as the primary data store, the risk profile changes because attacker-supplied input can flow directly into MongoDB queries constructed inside compromised templates.
Sails does not enforce a strict separation between data and code in all view paths. If a controller action passes request parameters into res.view() context without strict validation or escaping, and the view uses dynamic template logic (e.g., Lodash templates or custom helpers that evaluate JavaScript), an attacker may inject template directives that execute code. Because Sails often builds MongoDB queries from the same context passed to views, injected template code can manipulate query construction, leading to unintended query execution or data leakage.
For example, consider a Sails controller that renders a user profile page and passes the user-supplied username into the view context. If the view uses Lodash templating (the default in many Sails installations) and embeds the username inside a MongoDB query built via User.find(), an attacker could provide input like '}, { $where: "return false && exfiltrateData()"} to alter the query structure. While Sails/Waterline typically sanitizes inputs at the ORM layer, direct usage of eval-like helpers or unsafe interpolation in custom services can bypass these protections. The MongoDB driver then executes the attacker-influenced query, potentially returning sensitive documents or enabling further injection via MongoDB operators.
The combination of Sails’ flexible view system and MongoDB’s expressive query language amplifies the impact. Attackers can leverage SSTI to probe internal services, attempt Server Side Request Forgery (SSRF) against the MongoDB instance, or chain template execution with MongoDB aggregation pipelines if the application dynamically builds pipelines using injected template fragments. Because the attack surface includes both the template engine and the database layer, detection requires correlating runtime behavior across both components, which middleBrick’s 12 security checks perform in parallel, including Input Validation and SSRF testing.
Without runtime analysis, it is difficult to know whether a Sails application safely handles user input in templates and database interactions. middleBrick scans the unauthenticated attack surface of your endpoint in 5–15 seconds, testing for SSTI vectors and MongoDB-specific anomalies such as unexpected query patterns. Its findings map to OWASP API Top 10 and provide prioritized remediation guidance, helping you understand how template logic and database queries interact in your specific implementation.
Mongodb-Specific Remediation in Sails — concrete code fixes
Remediation focuses on strict input validation, avoiding dynamic evaluation in templates, and ensuring MongoDB queries are constructed with trusted data only. Do not rely on implicit escaping provided by Waterline alone when templates are involved.
1. Avoid dynamic template evaluation
Do not use Lodash templates (or any eval-like mechanism) to construct MongoDB queries or to render user-controlled content that influences queries. Replace dynamic templates with static views or use a dedicated serialization layer.
// ❌ Unsafe: dynamic Lodash template that builds a query string
const queryTemplate = _.template(req.query.filter); // user-controlled
const query = queryTemplate({ collection: 'users' });
const results = await User.native((err, collection) => {
collection.eval(query, (err, docs) => { /* ... */ });
});
// ✅ Safe: parameterized query with strict schema validation
const safeQuery = {
email: req.param('email'),
status: 'active'
};
const results = await User.find(safeQuery).limit(10);
2. Validate and sanitize all inputs before using them in MongoDB operations
Use explicit validation libraries (e.g., Ajv) and enforce type checks. Never directly interpolate request parameters into aggregation pipelines or query objects that are passed to User.aggregate() or User.native().
// ❌ Unsafe: directly embedding request params into aggregation
const pipeline = req.body.pipeline; // attacker-controlled
const results = await User.aggregate(pipeline);
// ✅ Safe: whitelisted pipeline stages with strict schema validation
const allowedStages = ['$match', '$sort', '$limit'];
const safePipeline = req.body.pipeline
.filter(stage => allowedStages.includes(Object.keys(stage)[0]))
.map(stage => {
const key = Object.keys(stage)[0];
return { [key]: stage[key] };
});
const results = await User.aggregate(safePipeline).limit(100);
3. Use Waterline’s built-in protections and avoid raw evaluation
Prefer Waterline query methods over raw collection.eval or JavaScript execution. If you must use native MongoDB driver methods, isolate them behind services that do not accept untrusted template fragments.
// ❌ Unsafe: using native eval with user input
const userCode = `return db.collection('users').find({$where: '${req.query.where}'}).toArray();`;
const results = await User.native((err, collection) => {
collection.eval(userCode, (err, docs) => { /* ... */ });
});
// ✅ Safe: using Waterline ORM with parameterized conditions
const users = await User.find()
.where({ status: 'verified' })
.limit(20);
// If native driver is required, wrap it safely
const docs = await User.getDatastore().manager
.db.collection('users')
.find({ status: 'verified' })
.limit(50)
.toArray();
4. Enforce principle of least privilege for MongoDB connections
Configure the MongoDB connection in config/connections.js with a user that has minimal required permissions. Avoid using a superuser account for the application, especially when templates and queries are involved.
// config/connections.js
module.exports.connections = {
mongoDb: {
adapter: 'sails-mongo',
url: 'mongodb://appuser:[email protected]:27017/appdb?authSource=admin',
// Use a role that only allows read/write on necessary collections
roles: [{ role: 'readWrite', db: 'appdb' }]
}
};