What is Cross-Site Scripting (XSS)

Cross-site scripting (XSS) is a web vulnerability that lets hackers inject unwanted commands into legitimate client-side code (usually JavaScript) executed by a browser on behalf of the web application. It's estimated that around 40% of cyber attacks are XSS-based and that over 60% of websites are vulnerable. XSS can be "reflected" or "stored". The first case occurs when an application receives unsanitized data from an HTTP request (e.g. search terms) and includes it unsafely in the immediate response; in the second case, the data is instead saved to a database and included in future responses.

Reflected Cross-Site Scripting

Let's say, for example, we have a search form whose parameters are saved in the URL:

https://unsafesite.com/search?term=searchedKeyword

The site might display the value in the page flow in a more or less simple way, for example:

You searched for: searchedKeyword

Without data sanitization, an attacker could include a payload in the parameter and have it run on the page:

https://unsafesite.com/search?term=/* Malicious code */

This would result in the script being unintentionally loaded into the user interface:

You searched for: /* Malicious code */

Every time a user visits the URL modified by the attacker, perhaps by clicking a malicious link, the script runs in the user's browser. It could, for example, steal cookies.

Stored Cross-Site Scripting

This is called stored (or "persistent") because the data is saved in a database, such as blog comments. Let's say a site lets users leave comments on a blog post. The comment can be submitted with a POST request:

POST /post/comment HTTP/1.1
Host: unsafesite.com
Content-Length: 100

postId=3&comment=This+post+is+really+good.&name=John+Doe&email=john%40doe.com

Submitting the POST request will display the comment in the user interface:

This post is really good.

An attacker could submit a comment encoded with a malicious script:

comment=%3Cscript%3E%2F*%2BMalicious%2Bcode%2B*%2F%3C%2Fscript%3E

The site saves the comment and displays it every time a user visits the page:

/* Malicious code */

This can allow the attacker to take over the user's session.

How to protect against it

The single most important measure against XSS attacks is sanitization. Sanitizing data means removing or neutralizing input, meaning data supplied by users through forms, URLs, APIs, etc. The goal is to remove or neutralize any data that could be interpreted as executable code by the browser. For example, HTML tags or event attributes like onclick can be removed or converted to safe entities.

An example of sanitization in Vue.js using DOMPurify. Credits Giovanni Manetti

Data sanitization libraries

DOMPurify: a very popular library for sanitizing HTML, SVG and MathML. Written in JavaScript, installed via Node's package manager (NPM), and usable in Vue, React and Angular to strip unwanted parts of user-submitted code before it can cause harm. It offers extensive customization.

Sanitize-HTML: another popular JavaScript library that works in a very similar way to DOMPurify and is installed via NPM.

OWASP Java HTML Sanitizer: specific to Java applications, this also lets you configure sanitization rules that remove or transform HTML elements and attributes as needed.

Bleach: an HTML sanitization library for Python, widely used in Django-based web applications. It's built on html5lib.

PHP, while it has various libraries, also has native filtering functions like "filter_var" with various sanitization filters:

// Using filter_var with FILTER_SANITIZE_FULL_SPECIAL_CHARS
$sanitizedText = filter_var($userInput, FILTER_SANITIZE_FULL_SPECIAL_CHARS);

echo $sanitizedText;

// Output: escaped HTML-safe text

In the example above, filter_var with the FILTER_SANITIZE_FULL_SPECIAL_CHARS filter is used to escape special characters and HTML tags. Beyond data sanitization, CSP (Content Security Policy) headers are also used to prevent XSS attacks. It's a response header widely supported by major browsers. It works by specifying "policies" that tell the browser which resources are allowed to load on the web page. On an Apache web server, for example, you can add a rule in the .htaccess file or in the virtual host config files:

Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://apis.google.com"

In this example:

  • default-src 'self': allows loading all resources (such as images, CSS, fonts, etc.) only from the same domain as the document.

  • script-src 'self' https://apis.google.com: allows script execution only from the same domain as the document and from https://apis.google.com.

In PHP, you can set the CSP header directly in your script as well.