Handlebars 4.1.2: Command Execution
After reading about NPM Advisory 755 in Mahmoud Gamal's blog post, I decided to poke around a bit and see if there are other ways to trick handlebars into letting us escape the sandbox.
Exploit
Background
Handlebars is a logicfull templating engine that attempts to restrict what you can do by providing a limited sandbox for your templates to run it. In the end, your templates are compiled to javascript and so it's a pretty tricky feat to pull off.
The patch to the prototype pollution vulnerability in NPM Advisory 755 is essentially to deny reading/writing properties if the property is not enumerable. We need access to Function.prototype.constructor to run our payload so we need to bypass this check.
Our goals
In order to get this exploit cooking we need to accomplish the following:
- Override
propertyIsEnumerablewith a function that always returns true in order to bypass the mitigation to NPM 755 - Get a reference to the
Functionconstructor - Call the function constructor with an attacker controlled thing
- Call the constructed function to execute our payload
Setting up the payload
Our first step is to create an array containing a single string element which defines which javascript we're going to execute. This is accomplished by calling split after setting the this to the string containing our payload:
Abusing the with helper
In handlebars, if you can call functions with this bound to the current context. As you can see above, we used the with helper to set the context to a string and call split.
The with helper is a lot like the with operator in Javascript. Effectively, it changes what this is. We want to set the context to a Function but handlebars throws us a curveball! If you pass a function to the with helper, it will call the function with no arguments and use the return value as the context, not the function itself.
You can think of:
As:
But we need:
something
So how do we get around this? We need a function that will return a function.
Leveraging __defineGetter__ and __setGetter__
These two little functions are easy to forget but are very useful. Essentially, __defineGetter__ let's define a function that is called every time you access a property and returns a value for it. It's partner __lookupGetter__ simply returns the function that is used to generate the value.
If we look at the first step in the exploit:
This effectively compiles down to something like:
"undefined", this.valueOf
with
We can't control the arguments passed to __lookupGetter__ but luckily in javascript if you don't pass an argument the variable will be set to undefined. This will get cast to a string and end up being the same as calling __lookupGetter__("undefined"). So, all we have to do to set the context to a function is define a getter with the property name "undefined" and use {#with __lookupGetter__}.
Bypassing the patch
In order to bypass the patch, we need to make propertyIsEnumerable always return 1.
With our shiny new primitive, we can do this via:
What we've done here is set a getter for propertyIsEnumerable to valueOf bound with a context of the number 1.
You can think of this block as:
"propertyIsEnumerable", 1
// valueOf.propertyIsEnumerable = function() {
// return (1).valueOf()
//}
Now context.propertyIsEnumerable will always return 1!
With the bypass out of the way, we simply get a reference to this.constructor which will be Function and call it with our payload.
Timeline
I reported this to NPM Security about 5 months back and haven't received a response. Since this vulnerability requires a template injection already I've decided to disclose it.