Skip to main content
  1. Blog Posts/

Cross-Site-Scripting

··1369 words·7 mins

Guide 02 in Sarah’s Welcome to Web Security Series #

In this series, Sarah discusses some common vulnerability classes found in web security and how you can find and exploit them. Today’s focus is cross-site-scripting (XSS), a common injection vulnerability in web security that can have serious consequences.

Introduction #

Cross-Site Scripting, better known as XSS, is an injection vulnerability that allows an attacker to execute arbitrary JavaScript code in some portion of the website. It does this by circumventing the site’s same origin policy.1 When testing for a proof of concept, it is common to call the relatively harmless print() or alert() function. There are three types of XSS attacks and while the underlying principle is the same, the manner in which they are executed is slightly different. These variants will be explored below.

Finding XSS Vulnerabilities #

As there are three types of XSS, there are three things you should look out for, depending on the variant. However, for all the variants, you can test to see if it’s vulnerable by injecting a simple payload such as <img src=1 onerror=alert(1)>, <script>alert(1)</script> or javascript:alert(1). If the site behaves unusually or executes that code, it indicates that it is XSS-vulnerable. Note that some systems employ obfuscation or tag-blocking so it may not be that simple! This will be looked at in the exploitation section below. But first, the three types of XSS attacks.

Arguably the simplest type of XSS is called ‘reflected XSS’. Reflected XSS occurs when information is received in a HTTP request and then that information is included in some direct response in an unsafe way. Think about a search bar that displays what you just searched for, as in Figure 1.

Figure 1: A simple search bar that reflects user input (Source: PortSwigger WebSecurity Academy)
Figure 1: A simple search bar that reflects user input (Source: PortSwigger WebSecurity Academy)

So, a good way to check for a reflected XSS vulnerability is to look out for places where a user can enter data and have that data be immediately ‘reflected’ back to them.

The second simplest form of XSS is called ‘stored XSS’ or ‘persistent XSS’. It is very similar to reflected XSS but rather than having the data be immediately returned to the user, it is stored somewhere in the site and then included in subsequent HTTP responses. A classic example would be comments on a blog page as the comment is stored on the website and then loaded every time someone visits the page.

The third type of XSS is called ‘DOM-based XSS’.2 This type of XSS is more complicated than the previous two as it requires some client-side JavaScript that processes data from an untrusted source in an unsafe way. A typical, vulnerable JS script will write back to the DOM which can cause unexpected behaviour. Such scripts can be seen in a variety of scenarios and one must simply look at the code thoroughly to determine if it is XSS vulnerable.3 An example of a vulnerable script could look something like this:

var search = document.getElementByID('search').value;
var displayResult = document.getElementByID('display-result');
displayResult.innerHTML = 'You searched for: ' + search;

As you can see, the display-result is neither encoded nor properly escaped, so it will simply execute any code that is entered through search.

Exploiting XSS Vulnerabilities #

As aforementioned, regardless of what type of XSS vulnerability exists, the payloads can be more or less the same (unless you are dealing with a more complex vulnerability). Consider the search bar in Figure 1. If we know it is XSS vulnerable with zero additional protections, we can simply inject one of our basic payloads.

Figure 2: The vulnerable search bar calling alert(1) when injected with a basic payload (Source: PortSwigger WebSecurity Academy)
Figure 2: The vulnerable search bar calling alert(1) when injected with a basic payload (Source: PortSwigger WebSecurity Academy)

And the same would go for a similarly-unprotected stored XSS vulnerability. For DOM XSS, it is often necessary to escape out of the original string first by prepending "> or similar.

In cases where certain key characters are blocked or encoded (e.g. ‘<’ being encoded as ‘&lt;’), you will have to find functions that do not have those characters (such as javascript:alert(1) or '-alert(1)-'). Similarly, if many tags or attributes are blocked, you may have to brute force all possible options to see if any have been overlooked. Fortunately, BurpSuite’s Intruder function combined with PortSwigger’s cheat sheet can make both of these processes a little easier. Much like SQLi (because they are both injection attacks), there is a great deal of trial and error in XSS as you have to find the payload that will work. Throw pasta at the wall and see what sticks, right?4

More complex DOM XSS attacks, such as those that contain vulnerable addEventListener() functions, require a close analysis of the code. Once you can identify how the input (hidden or direct) is processed, you can craft a suitable payload to bypass any defences or to escape any strings and deliver your payload.

Defending Against XSS #

Once again, there are a number of different ways one can defend against XSS, some of which have already been listed. OWASP also has an excellent cheat sheet that details a number of XSS defences.

As aforementioned, encoding key characters is a very simple defence against XSS that can be easily bypassed. Encoding can occur on arrival or on output (though it is more common for input to be filtered, not encoded). HTML tags and attributes can also be outright blocked, though it is possible that some will be overlooked, given the sheer volume of them. In cases where users actually need the ability to write (safe) HTML (such as on a blog post), OWASP recommends the use of the DOMPurify library, which can sanitise the HTML. Furthermore, it can simply be good practice to avoid using ‘dangerous sinks’ for user input. A common dangerous sink is innerHTML, which the OWASP recommends replacing with textContent or value instead. The use of appropriate response headers (such as Content-Type) can also help, as can a good content security policy (‘CSP’).5

Conclusion #

XSS vulnerabilities can be unfortunately difficult to identify and remove (the simple ones described in this post are not indicative of the majority of vulnerabilities). Moreover, XSS attacks can be quite destructive as attackers can exploit the vulnerability in many different ways. Therefore, it is crucial for cybersecurity professionals to be aware of them so they can discover them in a security review. In the end, the moral of the story is never trust your users: If they can modify the HTML output through a HTTP request, then there’s a chance that a vulnerability exists.

Credit to PortSwigger’s WebSecurity Academy for the images. If you are interested in learning more about XSS and web security, the WebSecurity Academy is a fantastic resource. You can create a free account and explore their labs here.


  1. Put very simply, same origin policy means that all the stuff on a webpage has to come from the same source, including scripts. This means if you have some data on Page A, Page B can access it, but only if it has the same origin (defined by a combination of URI, host name and port number). XSS bypasses this through the use of HTML tags, which is unaffected by the same origin policy. ↩︎

  2. The DOM (short for ‘Document Object Model’) is essentially a Web API, typically used in tandem with JavaScript but it can be used with other languages. It’s a data representation of all the stuff on a web page and programmers can manipulate this representation through code. For more visual people, the DOM is typically referred to as a ’tree’, with the entire HTML document being the parent node, then you have the children nodes of the head and body elements, then the titles, headers, paragraphs, anchors and so forth as child nodes of them. MDN has a good technical breakdown of the DOM if you are interested. ↩︎

  3. Or just spam every possible endpoint. But don’t tell anyone that I said that. ↩︎

  4. Please do not throw pasta at the wall. It’s a waste of good food. ↩︎

  5. For those who are unfamiliar, a CSP is basically a list of content that a web page can load. The problem with it is that it’s actually quite hard to implement it well. Therefore, it shouldn’t be viewed as a panacea. ↩︎