Software Integrity

 

An escape room called the ‘AngularJS sandbox’

The AngularJS framework has become extremely popular in the last couple of years. There are several reasons for this. First, it provides convenient data binding on the client-side. AngularJS also allows for the decoupling of the HTML template from the page logic written in JavaScript. This makes development much easier. It also allows seamlessly enabling a content security policy into your application. Second, AngularJS makes creating single-page applications very easy. This is because every “page” is now a view that the client-side code can route to, passing the state of the application within scope variables. Third, AngularJS provides good protection from cross-site scripting (XSS) out of the box. The framework comes with automatic contextual output encoding. This applies the right encoding scheme to user input depending where the input ends up in the page (in the URL, as an HTML attribute, or as an HTML tag).

AngularJS also comes with its own expression language. This allows it to execute a boiled down set of JavaScript in the scope of the page between the curly braces. This is also where the known security issues come in for AngularJS.

If you’ve been following AngularJS security news, you’ve probably heard about “escaping the sandbox” in every other version of the framework. Recently, the same vulnerability was discovered in versions 1.5.7 and 1.5.8.

The output of AngularJS expressions is written directly to the page at the expression location. That means that if an attacker can control the expression, the attacker can execute any JavaScript code. However, this is only applicable in the Angular sandbox. The sandbox restricts the AngularJS expression from evaluating unsafe expressions. These can attempt to access the Function constructor, window object, DOM element, global variables, or the Object constructor.

With these restrictions, all an attacker can do is execute a mathematical function, call an already existing function of the application, or display and modify data from a scope variable. This limits the impact of an Angular injection.

Although, the AngularJS documentation explicitly states that the sandbox’s purpose is to maintain a separation of application responsibilities. The sandbox isn’t available for the purpose of security. As a result, the AngularJS sandbox shouldn’t be considered a security boundary.

An attacker manipulating an existing AngularJS expression or injecting a new AngularJS expression can break out of the sandbox and execute malicious JavaScript within the HTML page. This is how the Angular sandbox escape works. Basically, by manipulating the functions and objects allowed in the sandbox (i.e., toString(), charAt(), trim(), prototype, and constructor), an attacker is able to make AngularJS execute traditional JavaScript (e.g., pop up an alert() function or execute an AJAX call to a third-party domain to send data from the AngularJS application).

The latest sandbox escape is demonstrated by executing the following code:
{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(1),a')}}

To try it out for yourself, create an HTML file with the following content and open it in your browser:

angularjs security

When running this code, you will see an alert box pop up with the word “hacked” in it. You can also test it out on https://jsfiddle.net/.
angularjs security

You can view a list of sandbox escape payloads for different Angular versions at the Pastebin website. Read about the technicalities of a possible attack using these payloads at the PortSwigger blog. A question that developers usually ask is “What does this mean to my application; is my AngularJS application now insecure?” The answer is: not necessarily. An AngularJS application is vulnerable to an XSS if it meets these two conditions:

  1. The version of AngularJS that you are using is vulnerable to a sandbox escape. This condition is met will all currently known versions of AngularJS that is up to 1.5.8.
  2. Your application has an AngularJS injection. Let’s look at what that means.

Generally, data binding provided by AngularJS, by default, does not allow AngularJS code to run where data is inserted into a template. However, there are several exceptions to this case.

First, developers may use the framework and Angular templates incorrectly.

In such cases, they first insert user data into the template. The AngularJS code evaluates the template to perform the rest of the data binding. This often happens when developers use both server-side and client-side templates. For example, developers use JSP (when using Java) or EJS (when using Node.js) on the server-side. Additionally, within the JSP or EJS template files, they are adding AngularJS directives and some user input.

Server-side techniques for preventing XSS encode special characters like angle brackets, but will not encode curly braces which indicate an AngularJS expression. That means the AngularJS template is modified on the server-side. User data is inserted into it before it passes to the client-side. At this point, the rest of the data is inserted through Angular data binding. If the user data inserted on the server-side contains an AngularJS expression, it will now be evaluated as code on the client-side. This is the typical injection scenario: “data is evaluated as code.”

Second, similar to using server-side templates, AngularJS expressions can be written into the response directly by the server-side code.

For example, a PHP code inside the HTML file can inject an AngularJS expression and inject some user input between the double curly braces. In this case, again, the code between the curly braces will be evaluated on the client-side as an AngularJS expression.

Third, user input ends up as an argument to the $eval function provided by the AngularJS framework.

Note that the $eval function is scoped to only execute AngularJS expressions. However, in all of these cases if an AngularJS expression sent by a malicious user contains a sandbox escape payload, then any JavaScript can be executed, resulting in a well-known XSS attack.

What can we do as developers and security analysts to prevent XSS in our AngularJS applications?

Developers should avoid mixing server-side and client-side templates as much as possible. However, this is often not a valid solution when a team is porting an old application into AngularJS. In this case, developers can limit the scope of the AngularJS app on their website. Limit the scope by only assigning the “ng-app” directive to the HTML element that must contain AngularJS code, not the whole HTML document.

Another important way to prevent AngularJS injections is a combination of thorough code review and penetration testing that looks for these types of vulnerabilities. Unfortunately, there is no silver bullet to solve this problem, like many other problems in application security. Following security best practices and security activities at every step of the software development life cycle is the only way to create better, more secure web applications.

Upgrade the security skills of everyone involved in your software development life cycle.

Written in coordination with Lewis Ardern.