Server Side Template Injection in Sails
How Server Side Template Injection Manifests in Sails
Server Side Template Injection (SSTI) in Sails.js occurs when user-controlled data is improperly interpolated into server-side templates without proper sanitization. Sails uses the EJS templating engine by default, which evaluates JavaScript expressions within template tags, creating a dangerous attack surface when user input reaches these templates.
The most common SSTI vector in Sails applications is through view rendering where user parameters directly influence template content. Consider this vulnerable pattern:
// Vulnerable controller action
exports.showProfile = async function(req, res) {
const userId = req.params.id;
const user = await User.findOne({ id: userId });
res.view('profile', { user: user });
}The vulnerability emerges when the user object contains attacker-controlled properties that get rendered in EJS templates:
<!-- profile.ejs -->
<h1><%= user.name %></h1>
<p><%= user.bio %></p>
If an attacker can manipulate the bio field to contain template code like <%= process.mainModule.require('child_process').execSync('id').toString() %>, the server will execute arbitrary commands when rendering the profile.
Sails-specific SSTI patterns include:
- Using
res.localswith unsanitized user data that propagates to all views - Passing entire request objects or database records to views without filtering
- Dynamic template rendering where template names come from user input
- Using
sails.hooks.views.render()with user-controlled template content
Another critical Sails-specific vector is through the res.json() method when combined with template rendering. If an API endpoint returns JSON that includes template code as strings, and another endpoint renders that JSON without proper escaping, SSTI can occur.
Attackers often exploit SSTI in Sails through crafted payloads that leverage Node.js's require() function to access the filesystem, execute commands, or read sensitive files like config/local.js or .env files. The process object provides particularly powerful primitives for exploitation.
Sails-Specific Detection
Detecting SSTI in Sails applications requires examining both code patterns and runtime behavior. Start with static code analysis to identify dangerous template interpolation patterns:
// Search for these patterns in your Sails codebase
grep -r 'res.view(' --include='*.js'
grep -r 'res.locals' --include='*.js'
grep -r 'sails.hooks.views.render' --include='*.js'
Look specifically for cases where user input reaches these functions without sanitization. The req.params, req.query, and req.body objects are common sources of untrusted data.
Dynamic detection with middleBrick reveals SSTI vulnerabilities by actively testing template injection points. The scanner attempts to inject template syntax into parameters and observes whether the server executes the injected code. For Sails applications, middleBrick specifically tests:
- EJS template injection patterns like
<%= global.process %> - JavaScript code execution within template tags
- Require() function access through template injection
- Property access chains that traverse Node.js objects
middleBrick's API security scanning identifies SSTI by sending payloads that, if executed, would cause observable differences in response timing or content. The scanner looks for indicators like:
- Process information disclosure in responses
- Changes in response size or structure
- Execution of time-delaying operations
- Access to system files
For comprehensive coverage, configure middleBrick to scan all your Sails API endpoints, including those that render views and those that return JSON data that might be consumed by other templated endpoints.
Runtime monitoring can also detect SSTI attempts by logging template evaluation errors and unusual template rendering patterns. Enable Sails' view engine logging to capture template compilation and execution details.
Sails-Specific Remediation
Remediating SSTI in Sails requires a defense-in-depth approach. The primary strategy is strict input validation and output encoding, but Sails provides specific features to help prevent template injection.
First, implement strict data whitelisting before passing data to views:
// Safe controller pattern
exports.showProfile = async function(req, res) {
const userId = req.params.id;
// Whitelist only safe properties
const safeUser = {
name: user.name,
email: user.email
};
res.view('profile', { user: safeUser });
}Never pass entire database objects or request objects to templates. Always explicitly select the properties you need.
For dynamic content that might contain user input, use Sails' built-in escaping mechanisms:
<!-- profile.ejs -->
<h1><%= user.name %></h1> <!-- Auto-escaped by EJS -->
<p><%- user.bio %></p> <!-- Unescaped - avoid this for user input -->
The <%= %> syntax automatically HTML-escapes content, while <%- %> does not. Always use the escaped version for user-supplied data.
Implement Content Security Policy headers to reduce the impact of successful SSTI attacks:
// In config/policies.js or a global policy
module.exports.policies = {
'*': ['securityPolicy']
};
// In api/policies/securityPolicy.js
module.exports = function(req, res, next) {
res.set('Content-Security-Policy',