AngularJS expression sandbox bypass

In the past years we've seen some great bypasses for the expression sandbox used in the AngularJS 1.x framework. The issue was raised publicly, starting from Mario Heiderich's {{constructor.constructor('alert(1)')()}} (patched in 1.2.0) to Gareth Heyes {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}} (patched in 1.5.0), with a handful of different interesting vectors from other researchers. All the vectors so far have been patched by the Angular team in subsequent software releases.

Occasionally, the AngularJS sandbox bypass vectors are reported through our VRP. We forward them to the product team, and that usually helps them tighten the sandbox. However, we don't consider breaking the AngularJS sandbox a vulnerability, because as explained in the Developer Guide, it's not a security feature:

AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper separation of application responsibilities. For example, access to window is disallowed because it makes it easy to introduce brittle global state into your application. However, this sandbox is not intended to stop attackers who can edit the template before it's processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings if an attacker can modify them.

We understand that when the sandbox is bypassed, AngularJS expressions may introduce XSS vulnerabilities into the web applications, especially when mixed with server-side templating languages that prevent XSS by contextual autoescaping. That's why when building AngularJS applications, server-side templating is discouraged. This is a deliberate design choice of that framework and as a consequence the sandbox should not be relied on for security.

In fact, we think that, even if perfect, the AngularJS expression sandbox cannot be a security boundary. Whether the sandbox existed or not, the attacker able to execute Angular expressions, can access the exposed scope and alter the state of the application in an unexpected way, and that's already a vulnerability.

Therefore, an expression injection flaw in the AngularJS template is no different than a regular code injection (XSS). In short, if you're able to make AngularJS evaluate your {{31338-1}} expression to 31337, it's already a potential script execution flaw, equivalent to eval("31338-1") in non-AngularJS applications.

What does it effectively mean for our VRP? Actually, it might be good news for you. Remember that we always try to analyze and pay for the maximum possible impact of a reported flaw (see the FAQ). When discovering the template expression injection issue in one of our applications, you don't need to be able to break out of the sandbox (e.g. there might be no known bypasses for a given version). Just report an expression injection and we'll treat it like an XSS (we already did in the past).

We closely follow the Angular security research done by the community. We like the "sandbox" and the challenge of bypassing any complex technical restrictions, so breaking the AngularJS sandbox is fun for us too. For example, here is the newest vector by one of us, @sirdarckcat:

<div ng-app ng-csp>
// Works in 1.2.0-1.5.0 in CSP mode, but HTML injection is required.
<div ng-focus="x=$event;" id=f tabindex=0>foo</div>
<div ng-repeat="(key, value) in x.view">
<div ng-if="key == 'window'">{{ [1].reduce(value.alert, 1); }}</div>
</div>
</div>

If you're interested in researching security vulnerabilities in AngularJS framework, we consider its HTML sanitizer a security boundary, so please let us know should you be able to bypass it!