/jun 25, 2015

AngularJS Expression Security Internals

By Isaac Dawson

Introduction:

As part of my research duties I tasked myself with becoming more familiar with the newer MVC frameworks, the most interesting one was AngularJS. I wanted to share with everyone my process for analyzing the expression functionality built in to AngularJS as I feel it's a pretty interesting and unique code base. AngularJS exposes an expression language that exposes a limited set of JavaScript to an HTML template. These expressions are evaluated within an ng-app directive of an AngularJS application. 

Expressions:

What are AngularJS Expressions?

AngularJS Expressions are a limited sub-set of JavaScript. They are included in html elements that have a defined ng-app or ng-controller directive. Their website documents the differences between Angular and JavaScript quite nicely, but the key points are as follows:

  • No access to global (window) context
  • No control flow; no loops or exceptions. However, ternary JavaScript statements are possible, such as: {{something ? something_is_true : something_is_false }}
  • Cannot declare functions
  • Regular expression literal notation is not supported

The Scope Object

Before we begin explaining how expressions are evaluated we must cover the Scope object. The scope object is what is passed into controllers. A typical controller looks like this:

angular.module('scopeExample', [])
  .controller('MyController', ['$scope', function($scope) {
    $scope.username = 'World';
    $scope.sayHello = function() {
      $scope.greeting = 'Hello ' + $scope.username + '!';
    };
  }]);

The controller, 'MyController' has the username string and sayHello function bound to its scope. After running sayHello, greeting will also be bound to the MyController scope. What this means is that expressions in the HTML can now access these values:

<body ng-app="scopeExample">
  <div ng-controller="MyController">
  {{username}}
  {{sayHello(); greeting}} 
</div>
</body>

Scopes can also be nested, so you can have inner and outer scopes. However, there are no restrictions on what can be accessed. Take the following example:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Scope Example</title>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
  <script>
    (function(angular) {
    'use strict';
    angular.module('scopeExample', [])
    .controller('OuterController', ['$scope', function($scope) {
     $scope.outerData = "i'm outside!";
  }])
  .controller("InnerController", ["$scope", function($scope){
    $scope.innerData = "i'm inside!";
  }]);
})(window.angular);
  </script>
</head>
<body ng-app="scopeExample">
  <div ng-controller="OuterController">
  {{outerData}}<br>
  access child: {{$$childHead.innerData}}
    <div ng-controller="InnerController">
      {{innerData}}
      <br>
      access parent: {{$parent.outerData}}
    </div>
  </div>
</body>
</html>

In the above example, inner scopes may access outer scopes by getting a reference to the $$childHead property. Outer scopes may access inner scopes by accessing the $parent property. The Scope object has the following properties and methods as of 1.4.1

function Scope() {
      this.$id = nextUid();
      this.$$phase = this.$parent = this.$$watchers =
                     this.$$nextSibling = this.$$prevSibling =
                     this.$$childHead = this.$$childTail = null; // a series of references to various scope objects. These are set when a scope is created or $new is called.
      this.$root = this; // reference to self
      this.$$destroyed = false; // is this scope destroyed or not
      this.$$listeners = {}; // listener object set from $on and accessed from $emit and $broadcast
      this.$$listenerCount = {}; // number of listeners
      this.$$isolateBindings = null; // whether the Scope is isolated for directives.
}
Scope.prototype = {
    constructor: Scope,
    $new: function(isolate, parent) { ... }, // creates a new scope, setting children/parent/siblings in the process.
    $watch: function(watchExp, listener, objectEquality) { ... }, // Registers a `listener` callback to be executed whenever the `watchExpression` changes.
    $watchGroup: function(watchExpressions, listener) { ... }, // variant of above
    $watchCollection: function(obj, listener) { ... }, // Shallow watches the properties of an object and fires whenever any of the properties change
    $digest: function() { ... }, // Processes all of the Scope#$watch watchers of the current scope and its children.
    $destroy: function() { ... }, // Removes the current scope (and all of its children) from the parent scope.
    $eval: function(expr, locals) { ... }, // Executes the `expression` on the current scope and returns the result.
    $evalAsync: function(expr, locals) { ... }, // Executes the expression on the current scope at a later point in time.
    $$postDigest: function(fn) { ... }, // adds fn to an array and calls the function at the end of $digest()
    $apply: function(expr) { ... }, // this method is used to execute an expression in angular from outside of the angular framework. Do not call this from an expression, it will get stuck in an infinite loop of $digest/$watch
    $applyAsync: function(expr) { ... }, // Schedule the invocation of $apply to occur at a later time. Same warning as above.
    $on: function(name, listener) { ... }, // Listens on events of a given type.
    $emit: function(name, args) { ... }, // Dispatches an event `name` upwards through the scope hierarchy notifying the registered $on listeners.
    $broadcast: function(name, args) { ... }, // Dispatches an event `name` downwards to all child scopes (and their children) notifying the registered $on listeners.
}
 

When you evaluate expressions from HTML you are limited to the Scope object. At no point should you have access to the global window object. This reduces our attack surface from a research perspective to these methods and any built in javascript methods we can call from objects we create. Objects created in expressions are implicitly bound to the current Scope object which is determined by where the expression exists in an ng-app or controller.

<body ng-app="expressionExample">
  <div ng-controller="SomeController">
  {{ x = 1234; }}
  <!-- this.x = 1234; where this = SomeController.Scope. -->
  </div>
</body>

In the above example, defining x as a seemingly global variable is actually internally being set to a property on the current Scope object. If you try to call 'global' functions like eval or parseInt, they will fail because any function you call must be bound to the Scope object. Given that, we can call $eval, $apply, $on, $emit or other Scope methods from an injected expression because they exist in our current Scope object. Additionally, we can call toString or valueOf because they are bound to all objects (including our Scope object), note it's the same as calling Scope.toString(), Scope.valueOf().

Before I begin getting into how I attempted, and failed, to break out of the Scope sandbox, we need to cover how expressions are parsed and compiled.

How Expressions are Compiled and Evaluated

It all begins in Scope.$eval(), the expression from an HTML page is passed into eval. $eval() calls an internal parser with the string representation of the expression. The majority of security checks are contained within the calls of $ParseProvider.$parse.

$ParseProvider.$parse:

First, the expression is parsed out using a custom Lexer. If Content Security Policy is enabled, these options are also passed into the lexer and custom parser.

// ParseProvider.$parse line 13957:
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
var lexer = new Lexer(parseOptions);
var parser = new Parser(lexer, $filter, parseOptions);
parsedExpression = parser.parse(exp); // magic happens here

parser.parse calls on the AST (Abstract Syntax Tree) object to compile the expression. The AST compiler internally calls the lexer to break the expression into various tokens/statements. Below is an image of the tokens that the lexer created from the input "y = toString". Note the various properties ( y is an identifier, = is an operator etc).

This lexer data is then fed into the program function of AST (seen above) which turns the tokens into a proper AST model. Here's the AST result of parsing y = toString:

Once this is done, it takes the AST model and compiles it into a string of JavaScript. A recursive loop over the AST takes identifiers and renames them to generic variables such as v1, v2, v3 etc. During this recursion, each identifier is checked in an ensureSafeMemberName function which is defined as:

function ensureSafeMemberName(name, fullExpression) {
  if (name === "__defineGetter__" || name === "__defineSetter__"
      || name === "__lookupGetter__" || name === "__lookupSetter__"
      || name === "__proto__") {
    throw $parseMinErr('isecfld',
        'Attempting to access a disallowed field in Angular expressions! '
        + 'Expression: {0}', fullExpression);
  }
  return name;
}

This function protects against someone attempting to define setter/getters or function prototypes on an object, which were exploited in sandbox bypasses in the past. 

"y" is safe here, so it just returns without throwing an error.

Another security function is called a bit later for the same identifier called, isPossiblyDangerousMemberName:

In our case, y != 'constructor' so we are safe here as well. If it was not, the compiled code will wrap the access attempt in a call to ensureSafeObject. Now that the compiled JavaScript for 'y' has been completed, it moves on to the next identifier. Lets take a look at the code it has generated so far:

Below is the generated 'body':

v2 = v3 ? l : s;
if(!(v3)) {
   if (s) {
     v1=s.y;
   }
} else {
  v1 = l.y;
}
if (v2 != null) {  

Note that how our identifier 'y' has been attached to s and l (which is short for Scope, the scope object, l is locals, any local variables). This has some interesting implications as to what can be used for values and properties as I eluded to above in the Scope object description.

It then proceeds to do the same calls with our toString identifier. When it's compiling/building this javascript code it also wraps the identifiers in function calls that will do security checks on the result of any object reference.

It should be noted any filters are then processed after our expression code. After prepending a function call and appending return text to the compiled JavaScript code, it creates the function and calls it with some additional references to internal functions:

Which results in the compiled code being turned into a function of:

The parameters to the function (s, l, a, i) are (Scope, locals, assign, input). 

Some interesting properties of this code: identifiers are always attached to a scope object, never on their own. This is why trying to call global functions like parseInt or eval do not work, they are not bound to the Scope object so they would be undefined. 

At this point our function has been compiled and created. It will have different properties depending on the type of expression. This is best left to their documentation:

 * The returned function also has the following properties:
 *      * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
 *        literal.
 *      * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
 *        constant literals.
 *      * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
 *        set to a function to change its value on the given context.
 

The $parse method then calls return addInterceptor(parsedExpression, interceptorFn); Where parsedExpression is our compiled function and interceptorFn is what was passed to parse (usually parseStringifyInterceptor which is set by the $interpolate service). A long complicated process by the interpolate service is kicked off with it finally resulting in Scope.$digest calling our expression with scope set:

The result of the function being called:

The ensureSafeObject is called on object references which does the following:

Each object reference is checked that it's not a constructor (or Function), not the window object, not a DOM (document included) element, angular element, and finally not an Object. These sets of checks turn out to be pretty good at stopping any attempt at insecure object access. There is one final security check that is performed but wasn't included due to the simplicity of the input expression. It also verifies the expression does not use a Function.prototype's call, apply or bind methods. Again this was exploited in the past by various sandbox breakouts.

The expressionInputWatch function re-runs the compiled javascript again with the result of the first run. Re-running with the variables assigned goes through the same process of ensuring each new object reference is safe (via ensureSafeFunction and ensureSafeObject). This is done recursively on each object reference lookup.

if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
  lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
  oldInputValueOf = newInputValue && getValueOf(newInputValue);
}

As you can see AngularJS has clearly taken significant steps to reducing the exposure of insecure JavaScript object access via AngularJS expressions. This leaves us with very little wiggle room when trying to escape the Scope sandbox and execute arbitrary JavaScript.

Attacks Performed and Failed

Why AngularJS Expression Injection is a Problem

Before I begin explaining my attempts of breaking out of the Scope sandbox, I want to cover why we should even bother looking at expression injection. JavaScript MVC frameworks that have built in templating take a rather different approach than AngularJS. With other frameworks, templates are usually precompiled into javascript and called from various Views. While it is possible to have inline templates such as this KendoUI example, it's usually called by passing in javascript objects or properties to the template to be rendered.

<script id="product-template" type="text/x-kendo-template">
    <div>not encoded #= SomeUserName #</div>
    <div>encoded #: SomeUserName #</div>
</script>

If on some unrelated page I set my username to #: Something # for some KendoUI application, it would not be interpreted by the templating system as template syntax, but rather as data. It is rare for other MVC frameworks to have server side templating data inside of a client side template. The real danger lies in mixing server side templating with Angular expressions. AngularJS has no idea that your JSP page read in SomeUserName and inserted it into the output of the HTML page. All it sees, if I set my username to {{ someUserInsertedExpression }}, is:

<body ng-app="someApp">
<div ng-controller="MyController">
   {{someValidExpression}}
</div>
<div>
   User Name: {{someUserInsertedExpression}} <!-- included via server side templating (such as JSP) -->
</div>
</body>

When AngularJS goes to compile the above HTML page into an ngApp it can not tell that someUserInsertedExpression shouldn't actually be evaluated. In AngularJS's defense they do clearly state on their security page that you should never mix server side templating and AngularJS as it can lead to expression injection. However, one of AngularJS's selling points is that you can only add AngularJS to segments of an HTML page which makes it easier to migrate from server side templating to using AngularJS in full.

This is why a site moving to AngularJS can suddenly become vulnerable. Sites moving piecemeal to AngularJS may have to leave in code that was previously safe (by html entity encoding or some other security measures), but would become unsafe by unintentionally allowing expressions to be injected. I tested five major sites using AngluarJS, three of which were vulnerable, and two of those were exploitable using old well known bugs from the Mustache Security wiki page.

In the above expression injection example, the inserted expression was outside of the ng-controller definition but still inside the ng-app directive which means it will evaulate the injected expression. The below example would be one way of correcting a site from expression injection, by moving server side templating data outside of any ng-controller or ng-app definition. It is not recommended to use any server side templating however, due to the risk of unintentionally having server side templated data rendered inside of AngularJS. The best way to mitigate this type of attack is to only pull in data from the $http service.

<body>
<!-- START ngApp -->
<div ng-app="someApp">
  <!-- Start MyController -->
  <div ng-controller="MyController">
    {{someValidExpression}}
  </div>
  <!-- END MyController --> 
</div>
<!-- END ngApp -->
<div>
   User Name: {{someUserInsertedExpression}} <!-- not evaluated because not inside ng-controller or ng-app. -->
</div>
</body>

Testing for AngularJS Expression Injection

Testing for AngularJS expression injection is quite easy. While doing research I noticed if you insert {{this}} into input form fields it will be stringified to "$SCOPE" upon evaluation. When testing an AngularJS application for expression injection, insert {{this}} for parameter and form field values and use Chrome or Firefox's element inspector to search for $SCOPE in the serialized DOM result. A lot of times it will be immediately visible in the rendered page. It is also recommended to use the browser debugger to look at angular.version to check if they are running an old version which is vulnerable to attacks listed on the Mustache Security page.  

Looking for Sandbox Escapes

I now want to cover the tests I did to attempt to break out of the Scope sandbox. As a security researcher, I can not recommend enough looking at security relevant test cases prior to investigating any software for bugs. In AngularJS's case, they have a number of tests targeting potential sandbox leaks and their tests are view-able on github. You'll notice a lot of the known attacks being tested to ensure they don't regress in future versions. Some of the more interesting tests are:

expect(function() {
  scope.$eval('{}.toString.constructor("alert(1)")');
}).toThrowMinErr('$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString.constructor("alert(1)")');
 
expect(function() {
  scope.$eval('{}.toString["constructor"]["constructor"] = 1');
}).toThrowMinErr('$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString["constructor"]["constructor"] = 1');

scope.key1 = "const";
scope.key2 = "ructor";
expect(function() {
   scope.$eval('{}.toString[key1 + key2].foo = 1');
}).toThrowMinErr('$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + 'Expression: {}.toString[key1 + key2].foo = 1');

expect(function() {
  scope.$eval('$eval.call()');
}).toThrowMinErr('$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' + 'Expression: $eval.call()');

These tests are attempting various ways of accessing a Function object which would allow arbitrary JavaScript to execute. While I did attempt a number of these and their variants, the recursive object property checks to ensure they are safe stopped me every time. Since I wasn't making any headway attempting to defeat their Object or Function checks, I decided to change focus.

Looking for 'this' leakage, or "learn from my mistakes"

I wanted to see if there was a way I could get the global 'this' object to leak by calling a series of expressions. One attempt that seemed promising at first was using Scope.$eval. I noticed when calling this.$eval(this.$new) I was getting some interesting looking errors:

At first I thought this could be abused (I did not properly read the error message) I incorrectly thought it meant "Cannot read the undefined property '$root'." Chrome's debugger did not help correct my misconceptions, when looking at what 'this' was in the debugger, chrome shows the following:

However, after the parent = parent || this; assignment, parent is actually 'undefined'. Meaning 'this' wasn't actually the window object. This is due to AngularJS using 'use strict' in their functions. It helps protect against these types of scope leakages. From MDN,

Thus for a strict mode function, the specified this is not boxed into an object, and if unspecified, this will be undefined

Leaking 'this' does not seem like a valid route to investigate.

Abusing Scope Object Methods

While expressions are checked against insecure object access, internal Scope methods are not. It is worth investigating if it is possible to call Scope methods with data that can be later evaluated outside of the compiled code security checks. The most interesting methods are the $on and $emit methods. Let's take a closer look at the $on method:

$on: function(name, listener) {
    var namedListeners = this.$$listeners[name];
    if (!namedListeners) {
      this.$$listeners[name] = namedListeners = [];
    }
    namedListeners.push(listener);
    var current = this;
    do {
      if (!current.$$listenerCount[name]) {
        current.$$listenerCount[name] = 0;
      }
      current.$$listenerCount[name]++;
    } while ((current = current.$parent));
    var self = this;
    return function() {
      var indexOfListener = namedListeners.indexOf(listener);
      if (indexOfListener !== -1) {
        namedListeners[indexOfListener] = null;
        decrementListenerCount(self, 1, name);
      }
    };
  },

The $on function looks interesting at first because you can see it accessing the internal $$listeners object using bracket notation using the name variable (which we control) as the key. A common pattern of executing javascript code is using the constructor to get access to a Function object which can take arguments for it's source. As an example, open your browser's console and type in: Object["constructor"]("alert(1)")(). 

In the above example the x variable is equal to an anonymous function with the body being alert(1). So let's pass "constructor" as a parameter to $on and see what happens.

Input: {{ $on("constructor", "constructor") }} 

So we can get access to the Object() but an exception will be thrown because namedListeners won't have a push method. 

Note if you tried to access $$listeners['constructor'] from an expression directly you'd get a security error:

"Error: [$parse:isecobj] Referencing Object in Angular expressions is disallowed! Expression: this.$$listeners['constructor']"

But this doesn't help us because we can't get access to the result of $$listeners['constructor']. $emit has the same problem, we can get it to reference the constructor by passing "constructor" but prior to calling our function, it treats the assignment of scope.$$listeners['constructor'] as an array and attempts to index/call splice on the constructor which is invalid. $broadcast also has the same issue.

$new, while doesn't take any interesting arguments, does reference Scope properties that we can modify prior to the method being called. This is another potential avenue where we change the internal definitions of properties and call internal methods with the hope we can some how influence them. In the case of $new, it attempts to call new this.$$ChildScope(). We have control over ChildScope but unfortunately we can not attempt to set $$ChildScope like the following

<body ng-app="">
  <br><br><br>
  <span>
    {{ $$ChildScope = toString.constructor; $new(false, "constructor") }} // throws Error: [$parse:isecfn] Referencing Function in Angular expressions is disallowed! Expression: $$ChildScope = toString.constructor; $new(false, "constructor")
</span>
</body>

We are limited in what we can pass to the $watch method, Most parameters take functions which we can not really define, we can only pass built in functions we have access to, or those already defined on the Scope object. It does however, introduce us to the $$watchers object. $$watchers are created for every expression after they are compiled. The Scope.$$watchers object has the following objects dynamically bound: exp, fn and get. The exp and get objects are the most interesting because their function's end up calling our compiled script code with parameters we can influence. Here is the source of exp and get:

The compiled expression is the function called parsedExpression, we can call this.$$watchers[0].exp and change scope, locals, assign and input parameter values. To call $$watchers[0].exp we need to have an expression prior to our second expression calling it. So we set the first expression to {{ y = constructor }} this will set a compiled variable to Scope(), when we add another expression of $$watchers[0].exp(constructor, constructor, constructor, constructor) we are attempting to get a reference to constructor.constructor. While the second call works, it is picked up by the ensureSafeObject on line 22 and we error out with a security exception:

$$watchers[0].get() has the same problems, even if we can get access to a Function(), it's caught by the security checks. 

$watchGroup and $watchCollections do not appear to be interesting. $digest can't be called because it's called automatically when any data changes and it checks what state it is in before running. $destroy also doesn't appear interesting. $eval appears interesting at first, but every time you write an expression eval is implicitly called. $evalAsync also does not appear interesting. 

$$postDigest is unique in that there is no documentation for it. It takes a single argument fn, or function. The functions passed to this method are added to an internal postDigestQueue array which is executed by $digest near the end of the method. However, nothing is returned when it is executed so it doesn't seem of much value for breaking out of the sandbox. Calling $apply will send the client into an infinite loop of $apply -> $digest -> $apply. That pretty much sums up the Scope methods. When doing code reviews of other objects and functions being bound to $scope in controller code, pay close attention to their properties as it may be possible to influence them. Application defined scope objects have the potential for allowing an attacker who can do expression injection to execute your $scope bound functions or get access to underlying JavaScript objects. As mentioned before, child controller scopes can access parents and vice versa. So while there may not be any interesting scope objects defined in your current scope, verify child and parent controller scopes are safe as well.

Conclusion

Overall, I'm extremely impressed with the expression sandbox. It appears well thought out and the recursive checks on object references for potential insecure object access is a surprisingly effective method for locking down expressions. However, people can be quite creative and it is definitely possible I missed or overlooked some area of the code. It may also be worth investigating calling the compiled functions with different types of values to see if there is a potential bypass I missed. Another area to look at is possibly fuzzing various expression syntax to see if it's possible to confuse the AST parser into generating invalid/insecure JavaScript.

A takeaway for users of AngularJS is to be very careful with mixing server side templating and AngularJS, you're only one step away from having a potential XSS vulnerability crop up if a new sandbox bypass is found in the future. Also, be very careful with what objects and functions are exposed to the $scope object in your controllers. I hope this post has highlighted what a difficult balance of exposing functionality is with the potential for it being abused.

I still haven't looked into directives and filters. If there is enough interesting information from a security perspective on these I'll try to write another blog post. 

 

Related Posts

By Isaac Dawson

Isaac Dawson is a Senior Security Research at Veracode, where he researches attacks and methods for creating a next generation application security scanner. This cutting edge research has led to advancements in Veracode’s automated web scanner’s ability to identify and exploit security issues. He has developed numerous internal systems containing thousands of variations of web vulnerabilities for use in ensuring the greatest amount of scanner coverage.