Express.js Template Injection via res.render()

High Risk Template Injection
expresstemplate-injectionsstirenderjavascriptcode-execution

What it is

The Express.js application is vulnerable to template injection through the res.render() method when user-controlled input is passed directly to template rendering without proper sanitization. This can lead to server-side template injection (SSTI), allowing attackers to execute arbitrary code or access sensitive data.

// Vulnerable: User input passed directly to template
app.get('/profile', (req, res) => {
  const userInput = req.query.name;
  res.render('profile', {
    name: userInput, // Dangerous: can contain template injection
    message: req.query.message
  });
});
// Secure: Input sanitization and validation
const validator = require('validator');
const xss = require('xss');

app.get('/profile', (req, res) => {
  const userInput = req.query.name;
  const message = req.query.message;
  
  // Sanitize and validate input
  const safeName = validator.escape(userInput || '');
  const safeMessage = xss(message || '');
  
  res.render('profile', {
    name: safeName,
    message: safeMessage
  });
});

💡 Why This Fix Works

The vulnerable code was updated to address the security issue.

Why it happens

Express.js applications pass user-controlled input directly to res.render() as template variables without sanitization. Template engines (Pug, EJS, Handlebars) may interpret special syntax in user data, enabling server-side template injection (SSTI) attacks that can execute arbitrary code.

Root causes

User Input in Template Data

Express.js applications pass user-controlled input directly to res.render() as template variables without sanitization. Template engines (Pug, EJS, Handlebars) may interpret special syntax in user data, enabling server-side template injection (SSTI) attacks that can execute arbitrary code.

User-Controlled Template Names

Applications use user input to determine which template to render (e.g., res.render(req.query.template, data)). Attackers can specify arbitrary template paths, potentially accessing unintended templates, reading sensitive files through path traversal, or exploiting template-specific vulnerabilities.

Inadequate Template Variable Sanitization

Template variables containing user input lack proper sanitization or escaping before rendering. While template engines provide auto-escaping for HTML, they may not escape template engine syntax itself, allowing injection of template directives.

Dynamic Template Compilation with User Content

Applications dynamically compile templates using user-provided content through template engine APIs. This allows attackers to inject arbitrary template code that gets compiled and executed on the server, leading to remote code execution.

Missing Pre-Render Input Validation

No input validation or filtering applied before passing data to template rendering. Applications trust all user input as safe template data without checking for malicious template syntax, expressions, or directives.

Fixes

1

Sanitize User Input Before Template Rendering

Apply strict sanitization and validation to all user input before passing data to res.render(). Use libraries like validator.js for input validation and DOMPurify or xss for sanitizing HTML content. Escape special characters that could be interpreted as template syntax. This prevents malicious template directives from being injected through user-controlled data.

2

Use Predefined Template Names

Never use user input to determine which template to render. Maintain a whitelist of allowed template names and map user selections to predefined values. For example, use a lookup object like { 'profile': 'user-profile.pug', 'settings': 'user-settings.pug' } rather than passing req.query.template directly to res.render(). This prevents path traversal and unauthorized template access.

3

Implement Input Validation and Encoding

Enforce strict input validation rules for all data passed to templates. Validate data types, length limits, character whitelists, and format patterns. Use schema validation libraries like Joi or express-validator to define expected input structure. Apply context-appropriate encoding (HTML, JavaScript, URL) based on where data will be rendered in the template.

4

Enable Template Engine Auto-Escaping

Configure your template engine (Pug, EJS, Handlebars) with automatic escaping enabled by default. For Pug, use #{variable} instead of !{variable} for escaped output. For EJS, use <%= variable %> instead of <%- variable %>. For Handlebars, use {{variable}} instead of {{{variable}}}. Review template engine documentation for security configurations and ensure escaping is not disabled globally.

5

Avoid Dynamic Template Compilation

Never dynamically compile templates using user-provided content through APIs like pug.compile(), ejs.compile(), or Handlebars.compile(). Pre-compile all templates at build time or application startup. If dynamic content is required, use template partials or data variables instead of compiling new template code. Dynamic compilation with user input enables direct remote code execution.

6

Implement Content Security Policy Headers

Add Content Security Policy (CSP) headers using helmet.js or custom middleware to restrict script sources and prevent inline script execution. Configure CSP directives like script-src 'self' to only allow scripts from your domain, and disable 'unsafe-inline' and 'unsafe-eval'. This provides defense-in-depth protection that limits the impact of successful template injection attacks.

Detect This Vulnerability in Your Code

Sourcery automatically identifies express.js template injection via res.render() and many other security issues in your codebase.