6.6. Cross Site Scripting (XSS) - theory

 

Reflected XSS

Reflected XSS or Cross-Site Scripting (XSS) occurs when a server takes values obtained from GET/POST parameters and inserts them into a page without proper filtering, which is then sent back to the user. The user's browser, upon receiving the requested page, displays it to the user. If malicious JavaScript code is inserted as values for GET/POST parameters, it can be executed in the user's browser, leading to various consequences.

Let's explain this with a simple and common example. Imagine a website with a search function. This function typically uses GET parameters to send the search query to the server. Sometimes POST requests are also used. After performing the search, the server may insert the received search query into the page and send it back as the search result. This is a common practice for many websites and is convenient for users.

However, what if instead of a regular search query or word, malicious JavaScript code is inserted? The server will perform its standard operation as programmed, extracting the received code and inserting it into the page, which is then delivered to the user. The user's browser will execute the injected malicious code.

Why is this possible? Can't the server insert the provided parameter values on generated pages?

Of course, it can, but the problem is that the server does not filter the input data and does not perform checks for prohibited or allowed characters.

But how can an attacker leverage this vulnerability for their purposes? Users wouldn't willingly insert malicious code and open it in their own browsers, right?

The scheme is straightforward. The attacker creates a malicious link and then distributes it to potential victims using various messaging apps, email, and other channels. Additionally, they use social engineering techniques to persuade the victim to click the link.

Reflected XSS vulnerability has the following characteristics:

  • Attacks using GET requests are quite common. Attacks using POST requests are also possible under specific conditions and by employing more sophisticated delivery methods.
  • The attack is essentially one-time, meaning it only works if the victim opens the received link. In practice, this doesn't happen frequently.
  • The success of the attack heavily depends on the delivery methods and applied social engineering techniques since the attack only works if the victim clicks the link.

 

Search and Testing for Reflected XSS

The procedure for testing and searching for vulnerabilities consists of several steps, which are initially best performed in Burp Suite or ZAP, although sometimes you can use just a browser:

  1. Identify entry points, or in other words, places where you can input and send data to the server:
    1. URLs with GET parameters.
    2. Different parts of the URL that represent a directory/folder (path).
    3. Various forms, such as a search or filter form.
    4. HTTP headers:
      • Cookie
      • Referrer
      • User-Agent
      • Any custom headers.
  2. Enter alphanumeric values into the discovered entry points and check if the server returns the entered data. If it does, proceed to the next step.
  3. Add characters such as quotes, angle brackets (<,>), and analyze in what context these data are returned. For example, they may be wrapped in certain HTML tags like <div>, <h1>, <span>, etc. In this case, an exploit may work. However, the server may escape them with special escape characters, and in that case, the code/exploit won't work.
  4. Inject simple JavaScript code. If the response message returns it unchanged, the vulnerability is confirmed. If the code is altered or truncated, filtering is in use on the server. In this case, try to modify the code.
  5. Compile the required exploit and test it in the browser.

Let's look at a simple example based on the BodgeIT application from the OWASP BWA virtual machine. The first thing that stands out is the site's search function:

Search option in BodgeIT app

If you enter the word "test123" in the input field of the form, it will become apparent that the application uses a GET request, and the server returns the word without making any changes to it:

Testing XSS in Search option in BodgeIT app

Be sure to open the developer panel to see in what context the returned value is displayed. The thing is, on the server, for security purposes, message output on the page may be escaped. Now let's enter a simple exploit and see how the application responds: 

<script>alert(1)</script>

Successful XSS in BodgeIT app

XSS exploit of BodgeIT in dev panel

As we can see, the exploit worked - the server does not filter the input data.

What should you do if no suitable URLs with GET parameters were found during the visual inspection of the application in the browser?

For manual vulnerability discovery, directory scanning tools, commonly known as spiders or crawlers, can be invaluable assistants. They allow you to fully scan a website and map its structure. However, crawlers are not all-powerful, and in some cases, manual site exploration is necessary. Therefore, I would recommend combining both methods. Once the structural examination of the website is complete, you can easily identify suitable URLs and then test them using tools like Burp Suite or ZAP.

Let's consider another example based on the Juice Shop application. This application has several XSS vulnerabilities. One of them was discovered through manual site exploration.

The vulnerability is present in tracking purchased items. To do this, you need to log in to the website. You can use an existing account and log in using the SQL Injection vulnerability on the login page, or you can register as a new user. Then, make a purchase and go to Account -> Orders & Payments -> Order History. Next, to track the order, click on the truck icon:

Tracking order icon in Juice Shop

Pay attention to the id parameter, the value of which is displayed in the URL and on the page itself:

Detecting XSS in tracking history option of Juice Shop

If you replace the current value of the parameter in the URL with another value, for example, "Test123," it will also be displayed on the page:

Testing Reflected XSS Juice Shop

It should be noted that Juice Shop is not a typical application. The entire frontend of the application is created using JavaScript scripts, and communication with the server is done using AJAX. Therefore, if you go back to Burp Suite or ZAP, you won't find the link you see in the browser.

To see the actual URL, enable request interception in Burp Suite; in the browser, enter a different value for the "id" parameter, for example, "HelloHello", and press Enter. Sometimes you may need to press Enter twice. Burp Suite will intercept the first request, but don't be surprised if you don't see the entered request. The application is designed to send a series of requests. So, simply click the "forward" button until you see the URL that contains the familiar "HelloHello":

Intercepted request with vulnerable URL

Here's what the request and response look like in Burp Suite:

View of request and response in Burp Suite

So, the browser sent a request to the server via AJAX, and the server responded with JSON. The scripts loaded in the browser will then insert the received response onto the browser's page. If you try to insert a well-known exploit like <script>alert(1)</script>, it doesn't work, even though it's not wrapped in special escaping tags:

Unsuccesful XSS in JuiceShop

This is because modern browsers have built-in protection mechanisms that prevent the execution of JavaScript code. Let's try another exploit, for example, <img src="/1" onerror="alert(123)">.

The <img> tag is used to insert images, and the src attribute is used to specify the source where the file is stored, while the onerror attribute runs JS code if an error occurs. In other words, we intentionally specify a non-existing source to trigger the code execution. This technique can also be used with other tags, such as <iframe>.

This time, the exploit worked:

Succesfuly exploited XSS in Juice Shop

 

 

Stored XSS

Stored cross-site scripting is a much more dangerous vulnerability than Reflected XSS. It occurs on websites where all visitors are allowed to post various messages that can then be read by all other visitors to the site. Examples of such websites typically include forums, social networks, and comment sections.

The principle is very simple. A malicious actor posts a message with malicious code and saves it. Unsuspecting users read the message, while the saved code does its work. Since the malicious code is stored on the server, it executes every time someone visits or refreshes the vulnerable page.

 

Search and testing for Stored XSS

Search and testing for Stored XSS vulnerabilities are similar in many ways to searching for Reflected XSS:

  1. Identify entry points:
    • Various parts of the URL, which represent directories/folders (path)
    • HTML forms and other elements that send data to the server
    • HTTP headers
  2. Enter alphanumeric values into the identified entry points and then check how the server returns them.
  3. Add characters such as quotes, angle brackets (<, >), and analyze whether the server escapes or filters them.
  4. Enter simple JavaScript code, and if it executes, the vulnerability is found and confirmed.

As a demonstration example, we can use the Peruggia application, which allows users to upload and comment on images. On the main page, a photo is displayed that we can comment on. Therefore, let's enter any text along with JavaScript code:

Testing Stored XSS in Peruggia app

We post a message, and immediately after the page loads, a familiar dialog box appears, indicating that the exploit has triggered. Any users can visit this page, and the entered code will execute every time the page loads. This is exactly how the stored XSS vulnerability operates.

 

Stored XSS via HTTP Header

Now let's consider a slightly more complex example. Imagine that the server uses filtering, or we don't have the ability to enter comments, but there is an option to upload and display files on the website. A vivid example of this is uploading images to a gallery that site visitors can view.

Once again, we'll use Peruggia, but this time we need to log in to upload files. Go to the Login page and enter admin/admin. An additional menu item, Upload/Delete, appears in the top panel. Let's try uploading any text file to see how the application behaves. In the text file itself, write any arbitrary message.

The upload was successful, and an icon with an unloaded image appeared on the page:

Testing stored XSS with file uploading

Moreover, the filename is displayed in two tags immediately: <a> and <img>. If we adjust the filename of the uploaded file, theoretically, we could inject code into one of the tags. Let's enable request interception in Burp Suite and upload the file again:

Intercepted request with file uploading to Peruggia  app

As you can see from the figure, the filename is passed in the Content-Disposition header under the filename parameter. Let's try entering some JavaScript code and see how the server displays it:

Adding JS exploit in Burp Suite Intruder

Unsuccesful exploit of stored XSS

The server accepted the new filename and displayed it on the page without any changes. However, the tag syntax was broken. Let's experiment with quotes to achieve the correct syntax. After entering the following exploit, the code worked:

onerror="alert(123)""

Succesful stored XSS exploit via HTTP header

 

If you go to the main page, the exploit will also work there because it displays the recently uploaded files.

 

DOM-based XSS

This type of vulnerability is slightly different from the Reflected/Stored XSS discussed earlier. The key difference is that in the case of Reflected/Stored XSS, malicious code is injected into the web page from user input on the server side. In DOM XSS, malicious code is injected in the browser itself, on the client side, by JavaScript scripts that create and modify the page, or in other words, manipulate the DOM.

To understand how this vulnerability works, let's consider a common example implemented in many modern web applications. Let's open the Acunetix test application and click the "Next" button several times. Pay attention to the URL and the page that displays the page number:

Inspecting page for DOM XSS

If you manually change the URL value, for example, to "3test," it will also be displayed on the page:

Inspecting page for DOM XSS

At first glance, it may seem like a typical Reflected XSS vulnerability, but it's not. The URL contains a hash symbol (#), followed by the rest of the URL. Everything that comes after the hash symbol in the URL is not sent to the server by the browser. In other words, this part of the address is processed locally in the browser itself. You can easily verify this by opening the developer panel and switching to the Network tab. Then click the Next button several times or simply refresh the page. In the network requests, you will see a completely different URL value:

Sending requests via AJAX

Pagination (page navigation) on this website is implemented using JavaScript. The scripts analyze the URL's value and then send a corresponding request to the server via AJAX in the form of a numerical value, while also inserting the URL value onto the page. If you were to insert a test exploit into the URL right now, it would trigger, and the exploit wouldn't even reach the server:

Successfuly exploited DOM XSS on site

In other words, if a web page contains JavaScript scripts that dynamically and unsafely manipulate elements of the web page, there is a high likelihood of a DOM-based XSS vulnerability.

The example described above is just a specific case. Not all applications use the hash symbol (#) in the URL, but they can still be vulnerable to DOM-based XSS. Furthermore, an exploit in the URL string can still reach the server, but server-side filtering will not prevent the attack.

 

Search and testing for DOM-based XSS

Search and testing for DOM-based XSS vulnerabilities can be a challenging task. Nevertheless, there are steps that can help identify vulnerabilities:

Identify entry points, including:

  • URLs, paying special attention to the hash symbol (#).
  • Headers:
    • Cookie
    • Referrer
    • Non-standard custom headers

Analyze whether parts of the URL or header values are reflected on the page. If so, observe how they are displayed on the page when their values are changed.

Check for scripts on the page and analyze the scripts for functions that manipulate URLs and headers. Avoid analyzing library files, as they provide functionality to the scripts built on top of loaded libraries and frameworks.

Here are some functions to check for in loaded scripts:

  • Properties that manipulate HTTP headers and URLs:
    • URL
    • URLUnencoded
    • location
    • referrer
    • location
    • name
  • Properties and functions that manipulate the DOM:
    • write()
    • writeln()
    • domain
    • innerHTML
    • outerHTML
    • insertAdjacentHTML
    • onevent
    • eval()

Let's go back to the Acunetix test application. It contains several vulnerabilities, and we've already found one. Now, let's try to investigate it more thoroughly. Open the page's source code and look for scripts:

Inspecting the list of scripts on page

The first three included scripts are third-party libraries, so we won't examine them, although sometimes they may also contain vulnerabilities. We will check the remaining scripts for the presence of unsafe functions. To do this, open the developer console and switch to the "Sources" tab (for Chrome) or "Debugger" tab (for Firefox):

View of source file Dev panel of Chrome

View of source JS files in Dev panel of Firefox

Open any script, for example, post.js. Then use the Ctrl+F keyboard shortcut to open the search panel. Next, sequentially enter the names of functions into the search field that can lead to DOM XSS. As a result, we get the following results:

Found new possible DOM XSS on site

In line 104, the Cookie value is extracted, and in line 109, the content of the Referer header is extracted. Then, the content of both headers is added to the page using jQuery in line 114.

Furthermore, starting from line 117, there is another block with potential vulnerability. In this block, the URL of the page is stored in the variable hrf. Then, a check is performed. If the URL is not www.facebook.com, a block of code with <div> tags is added to the page. The block itself contains a custom attribute data-href, with the value being the extracted URL.

In theory, there are two potential vulnerabilities. We cannot control the Cookie header, but we have partial control over the URL and the Referer header.

As you know, the Referer header is added by the browser when you navigate from one site to another using links. If a malicious actor places a link on their site to the vulnerable site, they can pass a JS exploit in the Referer header when users click on the link, potentially triggering it on the vulnerable site.

In the second case, we also have the opportunity to add an exploit to the URL, which theoretically could also work. However, you should consider how the embedded exploit is rendered by the browser, and keep in mind that browsers encode certain characters. Therefore, creating a functional exploit will be challenging, if possible at all.