AngularJS Security Fundamentals

March 17, 2020 | in Application Security
| By Liran Tal

In this security best practices cheatsheet, we focus on AngularJS and discuss tips and guidelines that ensure secure coding practices. In essence, this cheatsheet is a collection of AngularJS security fundamentals for web developers.

Below are the 10 AngularJS security fundamentals best practices that we cover in this blog post:

  1. The “Angular way” safeguards you from XSS
  2. Avoid using Angular DOM related input injection
  3. Avoid dynamically loading Angular templates from untrusted sources
  4. AngularJS open redirect vulnerabilities
  5. Server-side Angular code injection
  6. Avoid using the Angular .element jQuery-compatible API to manipulate the DOM
  7. Use Angular security linters
  8. Scan and fix vulnerabilities in Angular third-party components
  9. Built-in CSRF support for Angular applications
  10. Angular’s built-in CSP compatibility

Why is it so important to talk about AngularJS security best practices?

According to our previous study of JavaScript Frameworks Security, Angular v1.x makes up for, approximately, 30% of all Angular version downloads and just over 2 million downloads in July 2019.

source: https://snyk.io/blog/javascript-frameworks-security-report-2019/

Is Angular better than AngularJS?

Angular is the successor version of AngularJS. It is worth noting that we weren’t able to find any security vulnerabilities reported to the core set of Angular components. However, AngularJS has 23 vulnerabilities tracked across all release branches of version 1.x.

Which version of AngularJS should I use?

You should always opt for the latest version of AngularJS — AngularJS 1.7, at the time of writing —  which is always known to be up to date with security fixes and will be supported until June 30, 2021.

Many thanks to Lewis Arden who had previously shared extensive research and helpful information on AngularJS security. Here is his talk about securing AngularJS in OWASP London.

1. The “Angular way” safeguards you from XSS

AngularJS by default applies automatic output encoding and input sanitization that is context-aware for any data values that are pushed on to the DOM. As long as you are doing things the “Angular” way you benefit from this safeguard.

Let’s take a look at a few examples.

Using Angular’s ng-bind

Angular’s ng-bind directive allows you to bind value to an HTML’s element value from the one that was set in Angular’s code, such as the controller.

The following view.html provides the Angular template which uses ng-bind to interpolate an expression in Angular code and render its value for the div HTML element:

<div class="jumbotron" ng-controller="AboutController as about">
 <h1>About</h1>
 <p>
   Signed by:
 </p>
 <div ng-bind="about.signature"></div>
</div>

The controller that provides value for this view is AboutController:

(function() {
 "use strict";
 angular.module("angularjs-starter").controller("AboutController", function() {
   this.signature =
     'Yours truly, Angle <img src="x" onError="alert('its me!')"/>> Bob';
 });
})();

As you can see, there’s a simulated attempt in the signature value to inject HTML elements that trigger a browser’s window alert message. However, when this is rendered to the screen, Angular encodes characters such as < to their HTML entities so this prints out as text, instead of being inserted into the DOM as an actual HTML element.

Using Angular curly braces

Another way to bind data value into their HTML elements values is by using Angular’s convention of double curly braces. Both ngBind‘s directive and curly braces’ notation is the same, with some subtle differences regarding the UX.

Here’s an example of how Angular encodes malicious data when using curly braces, starting off with adding a description value to the view:

<div class="jumbotron" ng-controller="AboutController as about">
 <h1>About</h1>
 <p>
   {{ about.description }}
 </p>

 <p>
   Signed by:
 </p>
 <div ng-bind="about.signature"></div>
</div>

And the corresponding controller to provide a value with malicious intent to inject Cross-site Scripting attacks into the page:

(function() {
 "use strict";
 angular.module("angularjs-starter").controller("AboutController", function() {
   this.signature =
     'Yours truly, Angle <img src="x" onError="alert('its me!')"/>> Bob';
   this.description =
     "hello there you <a href="javascript:alert('hello')">crazy</a> diamond";
 });
})();

Since this is done the Angular way, you see how Angular applies it’s output encoding counter-measure for Cross-site Scripting attacks when curly braces and ngBind directives are both used with potentially malicious user input:

Key takeaway: Leverage Angular’s default safeguards to mitigate Cross-site Script  (XSS) vulnerabilities: interpolate data the “Angular way” with ngBind directives or curly braces such as {{ data }} so that Angular applies its output encoding and sanitization by default.

Abusing the Angular unsafe HTML binding

Earlier AngularJS versions such as 1.2 and prior had an ng-bind-html-unsafe directive which allowed to insert HTML data into the DOM and create new elements. Some practices for using this was to create DOM elements straight from data input and then filter certain strings in them, like remove links and others.

Using this approach to black list data is error-prone and not always safe due to new attack vectors and methodologies that are discovered over time and require maintaining such black list.

Let’s have a look at an example of how ng-bind-html-unsafe is injecting HTML elements into the DOM.

An Angular Template:

<div ng-controller="AboutController" ng-app>
  <span>{{email}}</span>
  <div ng-bind-html-unsafe="link">
  </div>
</div>

And the associated Angular controller for the view:

function AboutController($scope) {
  $scope.email = 'Email me:'
  $scope.link = "<a href='javascript:alert(1)'>Contact me</a>"
}

The rendered HTML view will include the “Contact me” as an actual link which triggers an alert prompt, when clicked: 

Abusing Angular Strict Contextual Escaping

The AngularJS 1.2 release has deprecated the ng-bind-html-unsafe directive and introduced Strict Contextual Encoding (SCE) instead. SCE enables escaping and sanitization data based on a context but not on a specific HTML element’s data.

That said, escape hatches exist that allow you forgo the encoding and assume that the data provided is safe to use. Such actions are made possible with Angular’s methods $sce.trustAsHtml() and  $sce.trustAs(type, value).

Furthermore, it is possible to use $sceProvider.enabled(false) to turn off strict contextual escaping globally and ignore all sanitization to trust the input. This is yet another bad practice to follow.  We advise you to use this only if you have really good reasons to do so and make sure to include other countermeasures and defenses. 

Let’s consider the following example that intends to allow users to create links but at the same time it introduces Cross-site Scripting vulnerabilities. The Angular template for it:

<html>
  <body ng-app='myApp'>
    <div ng-controller="AboutController">
      <span>{{email}}</span>
      <div ng-bind-html="link | stripDangerousHTML">
      </div>
    </div>
  </body>
</html>

The Angular controller is:

var myApp = angular.module('myApp', []);

myApp.filter('stripDangerousHTML', function($sce) {
  return function(value) {
    let dataInput = value
    dataInput = dataInput
      .replace(/javascript/g, '')
      .replace(/alert/g, '');
    return $sce.trustAsHtml(dataInput);
  }
})

myApp.controller('AboutController', function($scope) {
  // this is filtered ok:
  // const emailAddress = 'javascript:alert(1)'
  // but is this??
  const emailAddress = `jav
ascript:al
ert(1);`
  $scope.email = 'Email me:'
  $scope.link = `Hi there, <a href="${emailAddress}">Contact me</a>`
});

By creating  a stripDangerousHTML Angular filter, a developer ensures that that no evil JavaScript code makes it into the href attribute and it should be sanitized only to use emails.

If the user-provided input for emailAddress is 'javascript:alert(1)' then my Regular Expression-based pattern matching works great. However, if a user-provided input value is jav ascript:al ert(1); it doesn’t work that well. This specific input bypasses the pattern matching and yields a valid JavaScript statement that results in an XSS.

Key takeaway: Avoid using Angular DOM-related input injection which may introduce vulnerabilities.

  • In Angular 1.2 and prior avoid using the ng-bind-html-unsafe directive
  • In Angular 1.2 and later avoid blindly trusting user input with Strict Contextual Escaping collection of methods such as $sce.trustAsHtml(value)

3. Avoid dynamically loading Angular templates from untrusted sources

Angular code makes use of Angular templates all the time. These HTML templates look like this:

<div>
  Hello, {{ name }}
</div>

Most of the time you are composing Angular templates with files that are bound to an Angular application during build time. However, it is also possible to lazy-load these HTML templates during run-time from the client-side by sending a request to download the template and dynamically evaluate it.

Nonetheless, this introduces the risk of insecurely loading templates over untrusted domains that are beyond the control and trust of the web application. Moreover, developers should take further note of accessing the templates over secure protocols such as up-to-date versions of TLS to mitigate Man-In-The-Middle attacks.

An example of how an Angular template is loaded dynamically over the network:

angular.module(‘myModule’)
  .component(‘myComponent’, {
     templateUrl: ‘/path/to/template.html’,
     controller: function() {}
   });

Angular provides a way to set controls over template resources, and referring to the official documentation that describes it well:

The $sceDelegateProvider allows one to get/set the whitelists and blacklists used to ensure that the URLs used for sourcing AngularJS templates and other script-running URLs are safe (all places that use the $sce.RESOURCE_URL context)

If your Angular application code makes use of the SCE delegation provider, you want to make sure the configuration of the resources whitelist is properly considered:

  • use HTTPS as a secure medium to fetch the remote templates and ensure up-to-date TLS configuration exists on the remote endpoint.
  • avoid using the double asterisk wildcard ** for domains or protocols.
  • when necessary, create a blacklist for defense-in-depth.

4. AngularJS open redirect vulnerabilities

The browser-native APIs provide page navigation capabilities, such as $window.location. Angular abstracts the navigation handling with the $location service provider and provides methods such as $location.url(), $location.path() and $location.hash(), as well as others, to access the current navigation URL and synchronises it, as required.

You possibly have Angular code that performs navigation based on URL query parameters or other URL related data.

Consider the following example:

myApp.controller('AboutController', function($scope, $location) {
    $scope.submit = function() {
       const referer = $location.hash()
     }

     window.location.href = referer
});

The intention of this Angular code is to parse a URL, for example, https://www.example.com/path/to/this#referer and use it as a base for navigating to another page.

However, since the URL address is in the control of the user browsing the web application, that means a user can set the hash to valid JavaScript code — for example https://www.example.com/path/to/this#javascript:alert(1) and execute that URL.

Likewise, by setting window.location.href to user input — for example, the JavaScript injection example on the hash — would result in the same thing.

AngularJS security fundamentals

Best practices to avoid open redirects and JavaScript code injection:

  1. avoid page navigation based on absolute user input.
  2. when in need to perform page navigation based on user input, make use of dictionary maps in order to associate user-provided input to a page or URL.
  3. never concatenate user input to page navigation URLs.

Key takeaway: Avoid open direct pitfalls by implementing user-provided input directly to perform page navigation.

  • Avoid patterns such as window.location.href = $location.hash which potentially lead to JavaScript Code Injection attacks.
  • Use dictionary maps to perform page navigation based on user-provided input.

5. Server-side Angular code injection

Whereas server-side code sanitizes or encodes malicious HTML entities, such as scripts, it is not aware of the client-side AngularJS context and so attackers are able to provide an Angular template expression, for example, curly braces with further JavaScript payload, that is then evaluated during run-time on the client-side and creates an injection attack.

Best practices to mitigate server-side Angular code injection:

  • Avoid mixing server-side and client-side templates and instead treat templates only within one application context: either the server-side or the client-side.
  • reduce the scope of ng-app directive from an HTML’s document body to specific DOM element context within the page itself.
  • Bind the data from the template to ng-bind or ng-bind-html to ensure user input is being properly handled with Angular’s support for output encoding and sanitization controls with these Angular directives.
  • Use ng-non-bindable to make sure the data is not being treated by Angular as an expression that needs to be evaluated and so mitigating the Angular code injection.

Key takeaway: Mitigate server-side Angular code injection.

6. Avoid using the Angular .element jQuery-compatible API to manipulate the DOM

Angular provides an angular.element() API to provide jQuery-like API compatibility (jqLite) to query and manipulate the DOM directly.

This might seem as a convenient way to access and interact with the DOM, however, it introduces potential injection vulnerabilities due to the unsafe DOM injection.

Consider the following simple Angular template:

    <div ng-controller="AboutController">
      <span id=”content”>{{content}}</span>
    </div>

The following is the associated Angular controller using angular.element() to manipulate the page:

function AboutController($scope) {
  const element = angular.element(‘#content’)
  element.after($scope.content)
}

The problem here lies with element.after() that appends the contents of $scope.content into the DOM without any encoding or sanitization, since that is compatible with jQuery’s own after API. If the value for that variable includes anything malicious then the Angular safeguards are not applied and that leaves you vulnerable to potential XSS.

More about angular.element from Angular’s official documentation on https://docs.angularjs.org/api/ng/function/angular.element.

Key takeaway: Avoid using Angular’s angular.element() jQuery-compatible API to manipulate the DOM as this leads to potential Cross-site Scripting vulnerabilities due to directly creating HTML elements on the DOM.

7. Use Angular security linters

Linters are common in the world of JavaScript development and often developers make use of a popular project like ESLint along with plugins that extend it. The eslint-plugin-angular project helps with general Angular coding conventions and guidelines. It also has some rules for security, one of which is no-jquery-angularelement that disallow wrapping of angular.element objects with jQuery or $.

An example of how ESLint plugin catches issues, according to the official documentation:

/*eslint angular/no-jquery-angularelement: 2*/

// invalid
$(angular.element("#id")) // error: angular.element returns already a jQLite element. No need to wrap with the jQuery object

// invalid
jQuery(angular.element("#id")) // error: angular.element returns already a jQLite element. No need to wrap with the jQuery object

If you’re unsure why using angular.element() is a security issue, we recommend reading the previous section: Avoid using Angular’s .element jQuery-compatible API to manipulate the DOM.

Key takeaway: Use static code analysis tools to automate finding insecure code and alerting developers when this happens, early in the process of development. Security linters that are part of AngularJS security fundamentals:

8. Scan and fix vulnerabilities in Angular third-party components

It’s highly likely that you are using open source packages on top of the Angular core and its extended components in order to build your web application. In fact, the AngularJS project itself has vulnerabilities. According to a study on JavaScript Frameworks Security by Snyk, AngularJS has over 20 security vulnerabilities across the Angular 1.x version branch. 

Moreover, there the dependencies you use to build your Angular-based web application that possibly have security vulnerabilities too. As the following table shows, some of those vulnerabilities don’t even have an available fix, to date.

Scan for vulnerabilities

Looking for an AngularJS security scanner? To stay up to date with security vulnerabilities on your frontend project, use Snyk and connect your GitHub or Bitbucket projects  — that way Snyk automatically finds and creates fix pull requests for you.

Another quick way to get started is to use the Snyk CLI:

npm install -g snyk
snyk test

When you run a Snyk test, Snyk reports the vulnerabilities found and displays the vulnerable paths so you can track the dependency tree and understand which module introduced a vulnerability. 

Unfortunately, if you use npm audit, you probably missed all the AngularJS vulnerabilities that Snyk reports on. npm audit also misses all three React vulnerabilities, giving developers a false sense of security. 

source: https://snyk.io/blog/angular-vs-react-security-bakeoff-2019/

Snyk provides you with actionable remediation advice in order to upgrade to a fixed version through an automated pull request that Snyk opens in your repository. You can also apply a patch that Snyk provides to mitigate the vulnerability if no fix is available.

If you’re looking for anything close to an AngularJS security scanner you should start with Snyk as a way to track your open source dependencies, get notified, and fix them as vulnerabilities are discovered.

Key takeaway: AngularJS has over 20 vulnerabilities to date and there are Angular components with millions of downloads that are still vulnerable.

  • Connect Snyk to GitHub or other SCMs for optimal CI/CD integration with your projects.
  • Snyk finds vulnerabilities in 3rd party components you use and opens fix pull requests so you can merge the version update and mitigate the risk.

9. Built-in CSRF support for Angular applications

A Cross-Site Request Forgery (CSRF, XSRF) is a web security attack in which users are tricked into performing actions they didn’t intend to, such as sending an API request that submits a form.

A technique to mitigate CSRF vulnerabilities is to employ randomly generated tokens or – even better – are cryptographic nonce, as a means of authenticating that a request can be trusted.

Angular has a built-in support for CSRF tokens in which it reads cookies of specific naming conventions, such as XSRF-TOKEN, that is sent from the server and appends the value to further HTTP requests being made from the Angular application.

The entire CSRF token handling is done within the $http service for AngularJS. For newer Angular versions it is also provided via the HttpClient in the officially supported @angular/common/http module. It also provides further configuration of the cookie name for the CSRF token and general behavior via the $http.xsrfCookieName API and others.

Key takeaway: Angular has built in support for CSRF token handling on the client-side via its $http service. Use this service instead of rolling your own.

10. Angular’s built-in CSP compatibility

Some Angular features conflict with common CSP restrictions such as those requiring no inline JavaScript code evaluation. However, instead of turning CSP off completely, which is not recommended, Angular has the ngCsp directive that offers compatibility with CSP configuration.

Content Security Policy (CSP) is a web standard that allows establishing trust policies. It is mostly used as a defense-in-depth layer for broadening security and helps in mitigating issues like a (XSS) injection.

AngularJS has CSP-related built-in controls but when enabled, it has to exclude some functionality — for example, not allowing inline scripts and expressions, due to eval() not being permitted. Yet, this is internally required by AngularJS for some of its features.

Recommended reading on CSP is on Mozilla’s website and Angular’s API documentation for the ngCsp directive.

Key takeaway: Implementing a Content Security Policy (CSP) is an important step in providing an additional layer of security, especially one that helps mitigate Cross-site Scripting attacks.


10 AngularJS security fundamentals best practices:

  1. The “Angular way” safeguards you from XSS
  2. Avoid using Angular DOM related input injection
  3. Avoid dynamically loading Angular templates from untrusted sources
  4. AngularJS open redirect vulnerabilities
  5. Server-side Angular code injection
  6. Avoid using the Angular .element jQuery-compatible API to manipulate the DOM
  7. Use Angular security linters
  8. Scan and fix vulnerabilities in Angular third-party components
  9. Built-in CSRF support for Angular applications
  10. Angular’s built-in CSP compatibility