When a software developer first gets exposed to web security, he will inevitably memorize his first acronym: XSS! It stands for “cross site scripting”, and it is one of the oldest vulnerabilities around. Its origins are way back in the 90’s when Javascript was the new kid on the block. XSS (back then it was CSS) was its evil little brother, and it still thrives on its sibling’s success. One may wonder “Why is it called cross site scripting?”. Well, initially it was cross-site: a page had the ability to load another one and have read/write access to its DOM (Wow, can you believe that? More about this here) These days are long gone, now we are armed with various browser security features to combat this attack. And yet, XSS is still a dominant vulnerability, and it is not going away anytime soon…
Anatomy
Cross site scripting refers to the execution of malicious code in the browser. I included multiple browsers and mobile devices as well, to illustrate that this attack is not constrained to any browser or device. It affects everything that is capable of running Javascript. It is important to note that while the actual exploitation happens on the client side, the server may also play a role in introducing the vulnerability! As you will see later on most mitigation mechanisms must be deployed on the server. Let’s not rush ahead of ourselves. First, we need to be clear on what we are defending against.
Types of XSS
We, humans love categorizing and naming things. In this case, we came up with three categories. I’ll stick to the original categorization as that is widely accepted (more about this later). Let’s look at each one in detail.
Reflected XSS
The first, most benign one is called “reflected cross site scripting”. This happens when a malicious input is reflected back in the page content without the correct encoding (note that encoding here, does not refer to character encoding). As an example, think about a website which has a simple search form. You enter your search query and submit it. The server will receive your request and send back the results with a header stating what you searched for. The following image illustrates the point.
The process starts with the client sending a specially crafted request to the server. In our case, it is a simple GET request with a malicious query string. When the vulnerable server processes this, it will echo part of the malicious content into the page’s source unencoded. Now, think about what happens next: The client’s browser receives the tainted response and parses it, to render the site for the user. During this procedure, the injected unencoded input will be hit, and the browser will interpret it as a script tag and execute its content causing a popup alert. Boom, you are hacked!
There are a couple of key concepts we must address.
Firstly, the attack has two phases: injection and exploitation. In this case, both of these happen in a single request-response cycle. The injection phase sends the malicious input to the server, while the exploitation phase occurs when the browser processes the tainted response. From a hacker’s point of view, this is a strong constraint. Note that the injection has to be done by the victim. This makes it hard to scale the attack as it will only succeed if each victim goes through the injection phase and visits the maliciously crafted URL. If it is a regular request not containing the evil code, nothing will happen. So, why would anybody in their right mind visit such a URL? Ideally, nobody would do so! In reality, it is a much more complicated question. Users are manipulated into clicking such links. Even better, these are requested automatically by the browser without the user’s knowledge let alone consent.
The attacker will need to find a way to execute the injection phase, mainly to get the victim to request the specially crafted URL. Users won’t just hack themselves, or will they? I will leave it to the reader as an exercise to think about possible distribution methods (hint: genuine-looking-site.com/index?parameter=
Thirdly, the malicious request can contain the payload anywhere (it depends on how the server processes the request). For an attacker, it is best if the payload is in the query string as that is easily transferred in the URL.
However, it can be in the body or even the headers as well. Needless to say, the attack is not constrained to the GET verb either.
Persistent XSS
This category is a bit more sophisticated than the previous one. It is referred to as “persistent cross site scripting”. The difference from the reflected type is that this persists across request-response cycles making it a much more useful vulnerability for a hacker. The simplest example is a comments page. You can post your comments and read what others have to say. Let’s take a look.
It all begins with a new comment. A hacker comes along and submits a malicious remark to the server. Upon receiving it, the server will insert it into its database. If a client wants to look at the comments, the server will load them and add them to the page’s source unencoded. You can guess what happens next: the browser parses the response and executes the malicious code inside it. Boom…
The two categories look very similar. The devil is in the details, let’s take a look.
The attack consists of the same two phases discussed above. The significant difference here is that they are not constrained to a single request-response cycle. In fact, the injection step is usually carried out by the hacker and victims only experience the exploitation. The attacker can inject the payload into the database once using a single request and infect multiple clients without the need to directly communicate with them. Once the payload is inserted, it will be delivered to every victim visiting the comments page from then on. This is a huge win for the hacker as this makes the attack much more scalable.
As you have seen the capabilities are the same (both achieve malicious code execution in the browser). However, a persistent XSS is a lot more useful for an attacker.
DOM based XSS
This is where it gets interesting. The last category is coined as “DOM (document object model) based cross site scripting”. Like its name suggests it is something exotic. While the previous two types required a vulnerable server, this one does not. This attack relies on the inappropriate manipulation of the DOM by client-side Javascript. Think about an HTML editor with a real-time preview feature. Here is a highly simplified version.
The very first thing you should notice is that this type of injection does not involve a vulnerable server, it requires vulnerable client-side code. Injection, as well as exploitation, happens client side without the need for a request-response cycle. To exploit the simple example above, the victim must type in the payload to the input to trigger an update. The vulnerable update code will fetch the input’s value and assign it directly without any escaping to the live preview’s content. Once the content is set the browser will parse it and boom, you know the drill…
Okay, so this looks pretty useless. It seems the hacker needs direct user interaction to trigger this vulnerability… unless he does not. The principal here is that unvalidated input is injected directly into the DOM without proper encoding. The malicious payload can come from various sources, say the URL of the page (this looks very similar to the reflected case). Or even more exotic ones, like a postMessage’s payload. Direct user interaction is not a requirement; I only used it as an example.
Note that everything is happening client side and it is possible for the malicious payload never to hit the backend, making it nearly impossible to detect or mitigate this server side. These properties make this type of attack extremely useful for hackers.
A short note on types
The above three categories are the most commonly used. However, as I mentioned at the beginning of the post, there is another categorization which is starting to get traction among security professionals. Here is an image summarizing it, from the OWASP website:
The idea is rather simple. Let’s take two orthogonal properties of XSS: its persistence and the point of injection and create a matrix out of it. This makes it a lot easier to reason about the vulnerability; you can read more about this at the OWASP website.
I have covered most of the technical parts; now it’s time to zoom out and see the landscape.
Food for thought
It should be pretty evident now that XSS is a serious vulnerability. However, you may still wonder just how bad could it be. After all, it’s only in the browser, or so you may think…
Let me just draw a parallel here: the browser is the operating system of the web. It mediates communication between the user and the network. It provides a lot of “low-level functionality” needed to run the web, like DNS queries, a rendering engine, cookies, local storage, etc. It also happens to apply strict security rules, like sandboxing webpages, enforcing policies, etc. So it is indeed very similar to a “regular” operation system we have on our machines. Let’s take all this one step further and apply the analogy to Javascript. As it so happens, this has been done before: “Javascript is Assembly Language for the Web”. This is where XSS comes in. Which, if you think about it, is the “remote command execution” (a.k.a. RCE) vulnerability of the web. Exploiting an RCE against a patched operating system today is a lot harder than finding an XSS vulnerability in a web application. There is light at the end of the tunnel. I’ll talk about it in the countermeasures section down below.
To conclude, XSS is a severe vulnerability that every developer must know about and be able to counter. Let’s take a look what happens if one fails to mitigate it correctly.
Impact analysis
To accurately assess the impact of this vulnerability, let’s look at why would someone exploit it.
Some attacks focus on monetization. A great example is spreading malware through a legit website or even worse, using visitors as “zombies” taking full control over them. This is very useful for an attacker as it is a stepping stone for other attacks including denial of service, intranet hacking, deanonymization, attacking other systems, etc. Keep in mind that it is also possible to attack browser vulnerabilities via XSS, leading to the compromise of the user’s machine. This directly relates to the next major incentive.
Another goal for attackers is data exfiltration. Using XSS, an attacker can get his hands on confidential information. This may include session identifiers, authentication tokens or sensitive documents only available after authentication. Sometimes it is even possible to hijack the user’s session entirely.
Ultimately the injected script has complete control over the DOM of the page. It is possible to deface the website completely, causing brand reputation damage. In the end, exploiting an XSS vulnerability yields code execution in the context of the running site. Your imagination is the limit on what can be done…
Now, that we are clear on the impact, let’s see how to avoid it.
Countermeasures
There are some ways you can protect your visitors from XSS. Some of these techniques are general secure coding guidelines while others are browser features. Different countermeasures are located at various stages of the attack. Each has its advantage and disadvantage. It is imperative to understand them in depth, and choose which one(s) to apply.
I talked about two phases: injection, exploitation. We must tackle the problem on each front. Let’s first start by looking at preventive measures in the injection phase.
Injection countermeasures
The goal here is to stop the injection of harmful content. This can be done in many ways throughout the lifecycle of the request. All of the following techniques must be implemented on the server side. DOM based XSS will be tackled in the next section.
Your first line of defense comes before the payload hits your server: it is a web application firewall. There are some vendors out there providing such solutions. These work by intercepting and analyzing requests before they hit your server. Most of these products have a ruleset which tries to detect whether an input is malicious. If a request is flagged as dangerous, it may be blocked, never reaching your server.
Having a WAF can significantly reduce the chance of a malicious payload getting through. However, there are various techniques to evade such filtering, so it is not a silver bullet in itself.
Continuing on the lifecycle of a request, the next stop is your server.
Once the payload hits your server, you are in complete control, with all the responsibility as well. You have two choices when you encounter a malicious payload: either reject the input and stop processing or sanitize the content and remove possibly dangerous parts. Whichever you choose depends highly on your business needs. If done correctly these techniques can highly decrease the possibility of a successful attack. Let’s continue in the request lifecycle.
Sooner or later the processing will reach a point where you have to render some content to the user. This is your next major line of defense, and also the last one during the injection phase. Chances are you will insert non-static data in your response. Depending on where you place this dynamic data you must use the appropriate encoding mechanisms to make sure they are safely embedded. Always use context-sensitive encoding, if I had to name a silver bullet, this would be it.
Injection countermeasures and DOM based XSS
When defending against this exotic injection, you can not rely on your WAF or server, as they may never see the malicious payload. However, the two concepts described above, validation and sanitization, still apply. The only difference is that they need to be implemented client side, in Javascript.
Even if an injection gets through, it is still possible to block its execution. Let’s see how…
Exploitation countermeasures
This stage happens entirely in the user’s browser. Therefore any countermeasures deployed must also materialize there. Obviously, you cannot rely on the user whitelisting scripts one-by-one, allowing their execution. Luckily you do not have to; most modern browsers come with a built-in defense against reflected XSS. The effectiveness of these so-called filters/auditors has dramatically improved over the years. It is possible to influence the exact behavior you want, server side. To get the most out of this feature you must correctly configure it, by sending a header called X-XSS-Protection.
The previous measure is only effective against reflected attacks. Let’s take a look at a more general defense called Content Security Policy or CSP.
With CSP it is possible for websites to specify a security hardened environment in which they can be rendered. Using CSP, you can very effectively block XSS attacks. As always its effectiveness highly depends on its configuration. Creating and maintaining a secure configuration is pretty time-consuming, not to mention that you may need to adapt your application to be CSP compatible. This may require significant resources.
Even if all blocking mechanisms fail, you can still mitigate the damage by using a well known, widely supported defense factor called the same origin policy (enabled by default in browsers).
The best way to learn is by doing
XSS is very popular. It is among the top 10 web application security risks since ages. As you saw, there are many types of it. Most of them are showed with exercises here.

Discovery
The best defense is a good offense. There are various tools you can use to discover XSS in your applications. It is a good practice to scan your application against possible vulnerabilities regularly. In the long run, it is a lot cheaper if you discover and fix a flaw instead of letting an attacker exploit it.
Summary
We have covered a lot of ground here. You have seen the anatomy of the attack with two phases, injection, and exploitation, as well as its different types. Namely, reflected XSS, which is constrained to a single request-response cycle, requiring the victim to submit a payload, and its more dangerous variation, called persistent XSS, which breaks the above constraint by being able to persist through requests. A bit different from the above two is DOM-based XSS, which happens entirely on the client side making it rather hard, if not impossible, to mitigate it server side. You should now be able to categorize the next XSS vulnerability you come across.
Knowing the inner workings of the attack allows us to judge its impact very precisely. We have established that XSS is the RCE of the web as it is essentially code execution in the victim’s browser. This makes it an extremely potent vulnerability for hackers to exploit.
Finally, we looked at possible countermeasures to effectively mitigate this. As always, no single countermeasure is a silver bullet. However, a combination of them will yield a highly competent solution. The key takeaway here is the concept of defense in depth. If one control fails, and it surely will; you will still be covered.
This article intended to provide you a 360 view touching all the important aspects of this vulnerability. It is not an in-depth technical documentation, but a starting point to deepen your security knowledge. I encourage you to take a look at all the references and refer to this post for clarity.
This post originally appeared as a three-part series on the Craftlab blog.