Skip to content

Angularjs Invalid Left-Hand Side In Assignment Processing

Below is a graph over the amount of searches for AngularJS versus a bunch of other Single Page Application frameworks. Despite the flawed methodology, the story seems to be pretty clear: Popularity wise, Angular is beating the shit out of the other frameworks. I spent most of last year working on a large project built on AngularJS, and I’ve gotten to know the framework in some depth. Through this work I have learned that Angular is built around some really bad ideas that make it a pain to work with, and that we need to come up with something better. Whatever the reason is for Angulars popularity, it isn’t that it’s a great framework.

The amount of searches for various SPA frameworks.  (A less charitable interpretation of this data would be that Angular users have to search for answers more often than the others do.)

Bad Idea #1: Dynamic scoping

The scope of a variable is the part of the program where the variable can be legally referenced. If your system has variables, it has some concept of scoping.

Angular has a DSL that is entangled with the HTML and used primarily to express the data-bindings between the UI and application code. This has variables, and thus a concept of scopes. Let’s take a look at it. Consider for example :

<input type="text" ng-model="obj.prop" />

This creates a two way binding on the property of object . If you type into the input field, the property updates. If you assign to the property , the input field updates. Neat.

Now let’s add some simple parts:

<input type="text" ng-model="obj.prop" /> <div ng-if="true"> <input type="text" ng-model="obj.prop" /> </div>

Question: What does refer to in the second input tag? The answer is that it is literally impossible to tell what meaning of ng-model=”obj.prop” is by reading the code. Whether or not the two “” names refer to the same thing depends on the runtime state of the program. Try it out here: http://jsfiddle.net/1op3L9yo/ If you type into the first input field first, the two inputs will share the same model. If you type into the second one first, they will have distinct models.

WTF?

What’s going on here? Understanding that requires some knowledge of AngularJS terminology – skip this paragraph if you don’t care. The part that says is what’s called a directive. It introduces a new scope that is accessible as an object within the program. Let’s call it . Let’s call the scope of the first input . Typing “t” into the first input will automatically assign an object to , and assign the string you typed to the property like so: .  Typing into the second input will do the same to the . The complication is that prototypically inherits from , so whether or not inherits the property depends on whether or not it is initialized in , and thus ultimately depends on the order in which the user interacts with the page.

This is insane. It should be an uncontroversial statement that one should be able to understand what a program does by reading its source code. This is not possible with the Angular DSL, because as shown above a variable binding may depend on the order in which a user interacts with a web page. What’s even more insane is that it is not even consistent: Whether or not a new scope is introduced by a directive is up to its implementer. And if a new scope is introduced, it is up to its implementer to decide if it inherits from its parent scope or not. In total there are three ways a directive may change the meaning of the code and markup that uses it, and there’s no way to tell which is in play without reading the directive’s source code. This makes the code-markup mix so spectacularly unreadable that one would think it is deliberately designed for obfuscation.

When variable scope is determined by the program text it is called lexical scoping. When scope is dependent on program state it is called dynamic scoping. Programming language researchers seem to have figured out pretty early that dynamic scoping was a bad idea, as hardly any language uses it by default. Emacs Lisp does, but that is only because Richard Stallman saw it as a necessary evil to get his Lisp interpreter fast enough back in the early 80s.

JavaScript allows for optional dynamic scoping with the with statement. This is dangerous enough to make Douglas Crockford write books telling you not to use it, and it is very rarely seen in practice.  Nevertheless, with-statements are similar to how scoping works in Angular.

Pit of Despair

At this point, I imagine some readers are eager to tell me how to avoid the above problem. Indeed, when you know about the problem, you can avoid it. The problem is that a new Angular user likely does not know about the problem, and the default, easiest thing to do leads to problems.

The idea of the Pit of Success is said to have been a guiding principle in designing platforms at Microsoft.

The Pit of Success: in stark contrast to a summit, a peak, or a journey across a desert to find victory through many trials and surprises, we want our customers to simply fall into winning practices by using our platform and frameworks.  To the extent that we make it easy to get into trouble we fail.

–Rico Mariani, MS Research MindSwap Oct 2003. 

Angular tends to not make you fall into the Pit of Success, but rather into the Pit of Despair – the obvious thing to do leads to trouble.

Bad Idea #2: Parameter name based dependency injection

Angular has a built in dependency injector that will pass appropriate objects to your function based on the names of its parameters:

function MyController($scope, $window) { // ... }

Here, the names of the parameters and will be matched against a list of known names, and corresponding objects get instantiated and passed to the function. Angular gets the parameter names by calling on the function, and then parsing the function definition.

The problem with this, of course, is that it stops working the moment you minify your code. Since you care about user experience you will be minifying your code, thus using this DI mechanism will break your app. In fact, a common development methodology is to use unminified code in development to ease debugging, and then to minify the code when pushing to production or staging. In that case, this problem won’t rear its ugly head until you’re at the point where it hurts the most.

Even when you’ve banned the use of this DI mechanism in your project, it can continue to screw you over, because there are third party apps that rely on it. That isn’t an imaginary risk, I’ve experienced it firsthand.

Since this dependency injection mechanism doesn’t actually work in the general case, Angular also provides a mechanism that does. To be sure, it provides two. You can either pass along an array like so:

module.controller('MyController', ['$scope', '$window', MyController]);

Or you can set the $inject property on your constructor:

MyController.$inject = ['$scope', '$window'];

It’s unclear to me why it is a good idea to have two ways of doing this, and which will win in case you do both. See the section on unnecessary complexity.

To summarize, there are three ways to specify dependencies, one of which doesn’t work in the general case. At the time of writing, the Angular guide to dependency injection starts by introducing the one alternative that doesn’t work. It is also used in the examples on the Angular front page. You will not fall into the Pit of Success when you are actively guided into the Pit of Despair.

At this point, I am obligated to mention ng-min and ng-annotate. These are source code post-processors that intend to rewrite your code so that it uses the DI mechanisms that are compatible with minification. In case you don’t think it is insane to add a framework specific post-processor to your build process, consider this: Statically determining which function definitions will be given to the dependency injector is just as hard as solving the Halting Problem. These tools don’t work in the general case, and Alan Turing proved it in 1936.

Bad Idea #3: The digest loop

Angular supports two-way databinding, and this is how it does it: It scans through everything that has such a binding, and sees if it has changed by comparing its value to a stored copy of its value. If a change is found, it triggers the code listening for such a change. It then scans through everything looking for changes again. This keeps going until no more changes are detected.

The problem with this is that it is tremendously expensive. Changing anything in the application becomes an operation that triggers hundreds or thousands of functions looking for changes. This is a fundamental part of what Angular is, and it puts a hard limit on the size of the UI you can build in Angular while remaining performant.

A rule of thumb established by the Angular community is that one should keep the number of such data bindings under 2000. The number of bindings is actually not the whole story: Since each scan through the object graph might trigger new scans, the total cost of any change actually depends on the dependency graph of the application.

It’s not hard to end up with more than 2000 bindings. We had a page listing 30 things, with a “Load More” button below. Clicking the button would load 30 more items into the list. Because the UI for each item was somewhat involved, and because there was more to this page than just this list, this page had more than 2000 bindings before the “Load More” button was even clicked. Clicking it would add about a 1000 more bindings. The page was noticeably choppy on a beefy desktop machine. On mobiles the performance was dreadful.

Keep in mind that all this work is done in order to provide two-way bindings. It comes in addition to any real work your application may be doing, and in addition to any work the browser might be doing to reflow and redraw the page.

To avoid this problem, you have to avoid this data binding. There are ways to make bindings happen only once, and with Angular version 1.3 these are included by default. It nevertheless requires ditching what is perhaps the most fundamental abstraction in Angular.

If you want to count the number of bindings in your app, you can do so by pasting the following into your console (requires underscore.js). The number may surprise you.

function getScopes(root) { var scopes = []; function traverse(scope) { scopes.push(scope); if (scope.$$nextSibling) traverse(scope.$$nextSibling); if (scope.$$childHead) traverse(scope.$$childHead); } traverse(root); return scopes; } var rootScope = angular.element(document.querySelectorAll("[ng-app]")).scope(); var scopes = getScopes(rootScope); var watcherLists = scopes.map(function(s) { return s.$$watchers; }); _.uniq(_.flatten(watcherLists)).length;

Bad Idea #4: Redefining well-established terminology

A common critique is that Angular is hard to learn. This is partly because of unnecessary complexity in the framework, and partly because it is described in a language where words do not have their usual meanings.

“Constructor functions”

In JavaScript, a constructor function is any function called with new, thus instantiating a new object. This is standard OO-terminology, and it is explicitly in the JavaScript specification. But in the Angular documentation “constructor function” means something else. This is what the page on Controllers used to say:

Angular applies (in the sense of JavaScript’s `Function#apply`) the controller constructor function to a new Angular scope object, which sets up an initial scope state. This means that Angular never creates instances of the controller type (by invoking the `new` operator on the controller constructor). Constructors are always applied to an existing scope object.

https://github.com/angular/angular.js/blob/…

That’s right, these constructors never create new instances. They are “applied” to a scope object (which is new according to the first sentence, and existing according to the last sentence).

“Execution contexts”

This is a quote from the documentation on scopes:

Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context.

https://docs.angularjs.org/guide/scope

In the JavaScript specification, and in programming languages in general, the execution context is well defined as the symbols reachable from a given point in the code. It’s what variables are in scope. Angular does not have its own execution context any more than every JavaScript function does. Also, Angular does not “modify the normal JavaScript flow”. The program flow in Angular definitely follows the same rules as any other JavaScript.

“Syntactic sugar”

This is a quote from the documentation on providers:

[…] the Provider recipe is the core recipe type and all the other recipe types are just syntactic sugar on top of it. […] The Provider recipe is syntactically defined as a custom type that implements a $get method.

https://docs.angularjs.org/guide/providers

If AngularJS was able to apply syntactic sugar, or any kind of syntax modification to JavaScript, it would imply that they had their own parser for their own programming language. They don’t*, the word they are looking for here is interface. What they’re trying to say is this: “Provide an object with a $get method.”

(*The Angular team does actually seriously intend to create their own compile-to-JS programming language to be used with Angular 2.0.)

Bad idea #5: Unnecessary complexity

Let’s look at Angular’s dependency injector again. A dependency injector allows you to ask for objects by name, and receive instances in return. It also needs some way to define new dependencies, i.e. assign objects to names. Here’s my proposal for an API that allows for this:

injector.register(name, factoryFn);

Where is a string, and is a function that returns the value to assign to the name. This allows for lazy initialization, and is fully flexible w.r.t. how the object is created.

The API above can be explained in two sentences. Angular’s equivalent API needs more than 2000 words to be explained. It introduces several new concepts, among which are: Providers, services, factories, values and constants. Each of these 5 concepts correspond to slightly different ways of assigning a name to a value. Each have their own distinct APIs. And they are all completely unnecessary, as they can be replaced with a single method as shown above.

If that’s not enough, all of these concepts are described by the umbrella term “services”. That’s right, as if “service” wasn’t meaningless enough on its own, there’s a type of service called a service. The Angular team seem to find this hilarious, rather than atrocious:

Note: Yes, we have called one of our service recipes ‘Service’. We regret this and know that we’ll be somehow punished for our misdeed. It’s like we named one of our offspring ‘Child’. Boy, that would mess with the teachers.

https://docs.angularjs.org/guide/providers

Einstein supposedly strived to make things as simple as possible, but no simpler. Angular seems to strive to make things as complicated as possible. In fact it is hard to see how to complicate the idea of assigning a name to a value any more than what Angular has done.

In conclusion

There are deep problems with Angular as it exists today. Yet it is very popular. This is an indication of a problem with how we developers choose frameworks. On the one hand, it’s really hard to evaluate such a project without spending a long time using it. On the other hand, many people like to recommend projects they haven’t used in any depth, because the idea of knowing what the next big thing is feels good. The result is that people choose frameworks largely based on advice from people who don’t know what they’re talking about.

I’m currently hoping Meteor, React or Rivets may help me solve problems. But I don’t know them in any depth, and until I do I’ll keep my mouth shut about whether or not they’re any good.

larseidnes

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:

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:

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:

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

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.

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.

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:

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':

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 $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.

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.

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:

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.

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:

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  is not boxed into an object, and if unspecified,  will be 

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:

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

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. 

 

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.