AngularJS Services for Working with Dangerous Data

A common attack on web applications is to try to get them to display data crafted to fool either the browser or another user. This usually involves getting the browser to execute JavaScript code that the attacker has provided, but attacks can also involve trying to alter the application layout with some carefully crafted CSS styles. The types of attack are endless, but one common thread is injecting malicious content into the application through forms, either so it will be displayed back to the attacker or so it will be presented to other users. AngularJS has some nice built-in support for mitigating the risk of this kind of attack, and in this section I describe how it works and explain the built-in facilities that allow you to take control of the mitigation process. Table 19-7 shows the services that AngularJS provides for working with dangerous data.

1. Why and When to Use the Dangerous Data Services

AngularJS has a good default policy for dealing with potentially dangerous content, but you will need to work directly with the services I describe in this section when you need a little more flexibility. This can be needed when you are writing an application that allows users to generate HTML content (such as an online HTML editor, for example) or where you are dealing with content that is generated from a legacy system that mixes data and presentation in HTML fragments (old content management systems and portals are terrible for this).

2. Displaying Dangerous Data

AngularJS uses a feature called strict contextual escaping (SCE) that prevents unsafe values from being expressed through data bindings. This feature is enabled by default, and to demonstrate how it works, I have added a new HTML file called htmlData.html to the angularjs folder, the contents of which are shown in Listing 19-14.

Listing 19-14. The Contents of the htmlData.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>SCE</title>

<script src=”angular.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope) {

$scope.htmlData

= “<p>This is <b onmouseover=alert(‘Attack!’)>dangerous</b> data</p>”;

});

</script>

</head>

<body ng-controller=”defaultCtrl”>

<div class=”well”>

<p><input class=”form-control” ng-model=”htmlData” /></p>

<p>{{htmlData}}</p>

</div>

</body>

</html>

The controller scope in this example contains an input element bound to a property called htmlData, which is then displayed using an inline binding expression. I have set the property to a dangerous HTML string so that you don’t have to enter the text manually into the input element, but the idea is that an attacker will try to get the browser to execute some JavaScript code from the input element that isn’t part of the application—in this case, to display the alert dialog box, but in most of the attacks that I have seen in the wild, attackers try to get the application to display the data they enter as HTML to other users, most often to prompt them for their credentials or simply as a destructive act.

To help mitigate the risk, AngularJS automatically replaces dangerous characters (like < and > in HTML content) with their display-safe escaped counterparts, as shown in Figure 19-3.

AngularJS has transformed this HTML string from the input element:

<p>This is <b onmouseover=alert(‘Attack!’)>dangerous</b> data</p>

into this string, which is safe to display:

&lt;p&gt;This is &lt;b onmouseover=alert(‘Attack!’)&gt;dangerous&lt;/b&gt; data&lt;/p&gt;

Each of the characters that would lead the browser to treat the string as HTML has been replaced with a safe alternative.

Tip The process of escaping content doesn’t affect the original values in the scope—just the way that the data is displayed by the binding. This means you can continue to safely work with HTML data behind the scenes and allow AngularJS to render it safely in the browser.

For most applications, the default AngularJS behavior is exactly what is required to prevent dangerous data from being displayed. If, however, you find yourself in one of rare situations where you need to display HTML content without it being escaped, then there are a range of techniques available.

3. Using an Unsafe Binding

The first technique is to use the ng-bind-html directive, which allows you to specify that a data value is trusted and should be displayed without being escaped. The ng-bind-html directive depends on the ngSanitize module, which isn’t included in the main AngularJS library. Go to http://angularjs.org, click Download, select the version you require (version 1.2.5 is the latest version as I write this), and click the Extras link in the bottom-left corner of the window, as shown in Figure 19-4.

Download the angular-sanitize.js file into the angularjs folder. In Listing 19-15, you can see how I have added a dependency on the ngSanitize module and applied the ng-bind-html directive to display a dangerous data value.

Listing 19-15. Displaying Trusted Data in the htmlData.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>SCE</title>

<script src=”angular.js”></script>

<script src=”angular-sanitize.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [“ngSanitize”])

.controller(“defaultCtrl”, function ($scope) {

$scope.htmlData

= “<p>This is <b onmouseover=alert(‘Attack!’)>dangerous</b> data</p>”;

});

</script>

</head>

<body ng-controller=”defaultCtrl”>

<div class=”well”>

<p><input class=”form-control” ng-model=”htmlData” /></p>

<p ng-bind-html=”htmlData”></p>

</div>

</body>

</html>

There is no inline binding expression for the ng-bind-html directive, so I have added a span element so that I can apply it to the content. You can see the effect in Figure 19-5.

Although the content is displayed at HTML, the onmouseover event handler that I applied to the b element doesn’t work, and that’s because there is a second security measure in place that strips out dangerous elements and attributes from HTML strings. Here is what the htmlData value has been transformed into:

<p>This is <b>dangerous</b> data</p>

This process removes script and css elements, inline JavaScript event handlers and style attributes, and anything else that might cause problems. The process is known as sanitization and is provided by the $sanitize service in the ngSanitize module. The $sanitize service is used automatically by the ng-bind-html directive and is the reason that I had to add the module to the example.

3.1. Performing the Sanitization Directly

You can rely on AngularJS to use the $sanitize service on the values it displays unless you specifically disable the safety measures (which I describe later in this chapter). However, you may want to go further and sanitize values that you store in your application. Making display values safe is good practice, but if you store unsafe HTML in a database, for example, you can easily make your application the attack vector for any other application that reads that data and doesn’t benefit from the AngularJS protections. In Listing 19-16, you can see how I sanitize my HTML content before I added to the scope by using the $sanitize service directly.

Listing 19-16. Explicitly Sanitizing Content in the htmlData.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>SCE</title>

<script src=”angular.js”></script>

<script src=”angular-sanitize.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [“ngSanitize”])

.controller(“defaultCtrl”, function ($scope, $sanitize) {

$scope.dangerousData

= “<p>This is <b onmouseover=alert(‘Attack!’)>dangerous</b> data</p>”;

$scope.$watch(“dangerousData”, function (newValue) {

$scope.htmlData = $sanitize(newValue);

});

});

</script>

</head>

<body ng-controller=”defaultCtrl”>

<div class=”well”>

<p><input class=”form-control” ng-model=”dangerousData” /></p>

<p ng-bind=”htmlData”></p>

</div>

</body>

</html>

I have changed the ng-model directive on the input element to set an implicitly defined variable called dangerousData. In the controller, I use a scope watcher function to monitor the defaultData property for changes and, when there is a new value, use the $sanitize service object to process the value. The $sanitize object is a function that takes the potentially dangerous value and returns the sanitized result. To demonstrate the effect, I have reverted to a standard ng-bind directive to display the sanitized htmlData value, as shown in Figure 19-6.

You can see that the sanitization process has removed the JavaScript event handler from the string I entered into the input element. The value isn’t displayed as HTML because the ng-bind directive is still escaping the dangerous characters.

4. Explicitly Trusting Data

There are some—incredibly rare—circumstances under which you may need to display potentially dangerous content without escaping or sanitizing it. You can declare content to be trustworthy by using the $sce service.

Caution I have worked on countless web application projects over the years, and the number of times that I have needed to display raw untrusted data values is still in single digits. There was a trend in the mid-2000s for delivering applications as portals, and each piece of content tended to come with its own JavaScript and CSS. When the portal movement died out, the applications that replaced them inherited a database of content fragments that had to be rendered without interference, which meant that the features loosely equivalent to AngularJS SCE had to be disabled. In every other project that I have worked on, I have been at pains to achieve the opposite effect, which is to safely escape every piece of data that the application displays—and that’s especially true for data provided by users. The bottom line is: Don’t mess around with this stuff unless you have a truly compelling need.

The $sce service object defines the trustAsHtml method, which returns a value that will be displayed with the SCE process being applied, as demonstrated in Listing 19-17.

Listing 19-17. Displaying Dangerous Content in the htmlData.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>SCE</title>

<script src=”angular.js”></script>

<script src=”angular-sanitize.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [“ngSanitize”])

.controller(“defaultCtrl”, function ($scope, $sce) {

$scope.htmlData

= “<p>This is <b onmouseover=alert(‘Attack!’)>dangerous</b> data</p>”;

$scope.$watch(“htmlData”, function (newValue) {

$scope.trustedData = $sce.trustAsHtml(newValue);

});

});

</script>

</head>

<body ng-controller=”defaultCtrl”>

<div class=”well”>

<p><input class=”form-control” ng-model=”htmlData” /></p>

<p ng-bind-html=”trustedData”></p>

</div>

</body>

</html>

I use a watcher function to set a trustedData property with the result from the $sce.trustAsHtml method. I still have to use the ng-bind-html directive to display the value as HTML rather than escaped text. Trusting the data value prevents the JavaScript event handler from being removed, and using the ng-bind-html directive prevents character escaping. The result is that the browser displays the content from the input element and processes the JavaScript.

If you move the mouse over the bold text, you will see the alert window displayed, as illustrated by Figure 19-7.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *