Testing Your Web Applications for Cross-Site Scripting Vulnerabilities

by Chris Weber, Casaba Security, LLC (chris@casabasec.com)

By now there’s no argument that cross-site scripting attacks are real and potently dangerous. If you know all about XSS and just want to see some testing ideas, just skip straight to the testing section. If this is new to you, please read on! Cross-site scripting attacks occur when a malicious person, the attacker, can force an unknowing user, the victim, to run client-side script of the attacker’s choice. The term cross-site scripting is sort of a misnomer, because it’s not just about scripting and it doesn’t even have to be cross-site. It’s a name that was branded upon its discovery and it has just stuck. We’ll use its common abbreviation “XSS” from now on.

XSS attacks involve three parties:

  • The attacker

  • The victim

  • The vulnerable Web site that the attacker exploits to take action on the victim

Out of the three parties, the victim is the only one who actually runs the attacker’s code. The Web site is merely a vehicle for an attack and is not typically affected. An XSS attack can be carried out in a number of ways. As an example, the attacker sends the victim a maliciously crafted URL through e-mail, IM, or some other medium. When the victim opens the URL in a Web browser, the Web site renders the page and the script is executed on the victim’s computer.

What does an XSS vulnerability look like?
As a Web developer or tester, you know that the technological foundation of Web applications consists of HTTP and HTML. The HTTP protocol is the delivery transport for HTML, the code used to lay out and form the Web page.

XSS vulnerabilities exist when a Web application accepts user input through HTTP requests such as a GET or a POST and then redisplays the input somewhere in the output HTML code. Here’s the simplest example:

1. Web request looks like this:

GET https://www.somesite.com/page.asp?pageid=10&lang=en&title=Section%20Title

2. The HTML returned by the server after making this request includes:

<h1>Section Title</h1>

You can see that the user input passed to the “title” query string parameter was probably placed in a string variable and inserted by the Web application into an <h1> tag. By providing the input, the attacker controls the HTML.

3. Now, if the site is not filtering input server-side (because client-side controls can always be bypassed), a malicious user could abuse this in many ways:

The attacker could inject code by breaking out of the <h1> tag:

https://www.somesite.com/page.asp?pageid=10&lang=en&title=Section%20Title</h1><script>alert(‘XSS%20attack’)</script>

The HTML output from this last request would look like:

<h1>Section Title</h1><script>alert(‘XSS attack’)</script>

Even with this most simple of examples, there are numerous things an attacker could do with this link. Let’s look at the potential threats and then move on to some more advanced testing methods.

How serious are the threats from XSS attacks?
With the ability to inject code into the Web page generated, potential threats are limited only to the imagination. An attacker can use XSS vulnerabilities to steal cookies, hijack accounts, execute ActiveX, execute Flash content, force you to download software, and take action on your hard disk and data.

All this can happen simply by getting you to click on some URL. How many times a day do you click on a URL you receive in an e-mail message you trust, through a message board or newsgroup you use?

Phishing attacks commonly exploit XSS vulnerabilities to masquerade as legitimate sites. You’ve seen this a lot, where an e-mail message from your bank lands in your inbox, informing you that some changes have been made to your account and enticing you to click some hyperlink. If you look more closely at the URL, it might actually exploit a vulnerability in your bank’s Web site, and look something like https://mybank.com/somepage?redirect=\<script>alert(‘XSS’)</script>, where use of the “redirect” parameter has been exploited to carry out the attack.

If you’re really wily you can target the administrators with an XSS attack, perhaps by sending mail titled “Help! This website URL keeps giving me an error!” After the administrator opens the URL, you can do a lot of damage like stealing his or her credentials.

Okay, so we understand the dangers -- hack the users, hack the administrators, stir bad PR for the firm. Now let’s look closer at the main point of this article -- testing your site for these problems.

Testing for XSS vulnerabilities
I’ve been a full-time security consultant for many years and have been through this exercise many times. I can sum up a good test plan in two words: be thorough. For you and me, finding these vulnerabilities is not about getting bragging rights on Bugtraq or Vulnwatch; it’s about doing a good job. If that means going through every single query string parameter, cookie value, and POST data value in the application, it just means we’ll be working a little harder.

Granted, a full security review usually involves more than just seeking out XSS vulnerabilities; it also involves overall threat modeling, testing for overflows, information disclosure, error handling, SQL injection, authentication, and authorization bugs. The nice thing is that doing a thorough job in any one area often overlaps with another. Like, while testing for XSS vulns, you will very often identify error handling and information disclosure problems as well.

I’m assuming that you are a part of the team that develops and tests the Web application. In that fortunate position, you can work from a mixed black box/white box perspective. Each has its advantages, and together they even support each other.

  1. Get your toolkit in order
    Testing can be automated, but we’re not covering that here. Must-have tools for focused manual testing include:

    So I’ve listed at least three Web proxies here. Well, check out some different ones because each has its own character. The point here is that you’re going to need to intercept the HTTP requests your Web browser makes before they get sent, and then modify them to inject your XSS test. Each of these tools will do that job, and some will also show you the source code of the HTML being returned if you choose to intercept the server responses.

    Intercepting the client GET and POST requests is extremely important. This will let you bypass any sort of client-side javascript input validation code that may have been pushed down. A note for all Web developers -- client-side security controls are worthless. Validation should always be done on the server.

  2. Map out the site and its functionality -- talk with developers and PMs
    Create some simple data flow diagrams that describe the pages on the site and their purposes. This is a good point to schedule some meetings for threat modeling with the developers and project managers. Use that time to drill into the application as much as possible. Does the site expose Web services? Is there an authentication form? A message board? A user settings page? Be sure to list out all the pages.

  3. Identify and list out every point of user-supplied input
    Take the site map a step further. I usually create a spreadsheet to do this. For each page, list out all of the query string parameters, cookie values, custom HTTP headers, POST data values, and other forms of user-supplied input passed. Don’t forget to search out Web services and similar SOAP requests, and identify all fields that allow user input.

    List every input parameter separately because you’re going to need to test each of them independently of the others. This is probably the most important step! If you can read the spreadsheet below, you’ll see I’ve identified a bunch in the sample site. Query string values like forwardURL and lang. POST data values like name, password, msgBody, msgTitle, and even some cookie values. All of these are interesting and important to test.

  4. Think through and list out your test cases
    Take the spreadsheet you have and list out the different ways you’ll try to test for XSS vulns. I’ll talk about variations in a minute, but for now take a look at the screen shot of my spreadsheet, and notice how I list out the page with each value it allows and each type of test per value. This method of recording the tests is just my style and you can surely come up with your own. I just like to record everything so I know what was and what wasn’t tried.

    Think through and list out your test cases

  5. Start testing and pay attention to the output
    The most important part of finding a vulnerability is not whether you found it. It’s whether or not you really know what’s happening. With XSS, just look at the HTML output and find where your input made it in. Is it in an HREF tag? An IFRAME tag? Is it in a CLSID? An IMG SRC? How about the PARAM NAME of some Flash content?

    I’ve seen all these cases, and if you understand exactly what the input is doing, you’ll be able to adjust your test to identify a problem. That means you may need to stick in an extra closing bracket > to escape out of a tag, or add a double quote to close an element inside a tag. Or you may need to URL or HTML encode your characters, such as by turning a double quote into %22 or ".

  6. Hmmm it’s not so easy, this site is tight. What now?
    So maybe your simple test case of <script>alert(‘hi’)</script> isn’t producing the alert box you were expecting. Think this through and talk to the developers if you can. Maybe they’re filtering input for brackets, single quotation marks, or parentheses. Maybe they’re filtering for the word “script.” Again, study how the input is making it to the output, and figure out exactly what each one of the values (query string, cookie, POST data) is doing. A query string value like “pageId=10” may never even make it to the output, so it may not even be worth your time testing. Sometimes it’s better to just try injecting single characters like the bracket, the double quotation mark, or the parenthesis just to see how the application is filtering those characters. Then you’ll know what level of filtering you’re dealing with. You can adjust your tests to encode those characters and try again, or find other ways to inject.

Varying the test cases
I can’t say it enough: Studying how the input values make it to the output HTML page is extremely important. If they aren’t making it, don’t waste your time. If they are, study where because you’ll need to vary your test accordingly. I use a number of variations to identify parameters that accept and display scriptable code. There are far too many to list here, but these are a few:

  1. >"'><script>alert(‘XSS')</script>

  2. >%22%27><img%20src%3d%22javascript:alert(%27XSS%27)%22>

  3. >"'><img%20src%3D%26%23x6a;%26%23x61;%26%23x76;%26%23x61;%26%23x73;%26%23x63;%26%23x72;%26%23x69;%26%23x70;%26%23x74;%26%23x3a;alert(%26quot;XSS%26quot;)>

  4. AK%22%20style%3D%22background:url(javascript:alert(%27XSS%27))%22%20OS%22

  5. %22%2Balert(%27XSS%27)%2B%22

  6. <table background="javascript:alert(([code])"></table>

  7. <object type=text/html data="javascript:alert(([code]);"></object>

  8. <body onload="javascript:alert(([code])"></body>

There are many variations to try. The key is to understand how the input is being processed and rendered on the output page. As these examples show, the variations often involve preceding the scriptable code with “>”” in order to try closing tags that the Web site might produce on output. They also involve URL encoding of the code in an attempt to bypass input filters on the server-side. Additionally, since brackets “<>” are commonly filtered during input or escaped on output, XSS not requiring brackets must also be tried, such as ”&{alert('XSS')};”

Persistent vs. dynamic
Identifying a successful XSS is tricky, because the XSS attack may not be so obvious at first. As an arbitrary example, when adding a new message to the site and injecting into the “msgTitle” value, you may not see the scriptable code execute immediately after the data is submitted. However, when you visit the message board, the “msgTitle” value may be used in the HTML of the page and executed as scriptable code. This would be referred to as a persistent XSS attack that occurs when the value containing the scriptable code would be saved to the client or the backend to be executed at a later time.

This is in contrast to dynamic XSS attacks that execute immediately and only once. For example, when a link or GET request was formed with scriptable code in one of the query string values that controls output on the page, then that output might only be displayed once when the link was clicked.

Conclusion
Testing for XSS is usually one small part of a full Web application security review, but it warrants being done thoroughly. After doing this for many years, I stress that using a spreadsheet or something to document all pages on the site, along with all input values each page accepts (query string, cookie, POST data, SOAP), is a critical step before you even start testing. Equally important is to follow the input and understand how it is rendered on the output HTML page. If you know how the application is using the input, you’ll get a lot done much faster. Don’t waste your time testing input that never gets displayed as output. Talk to the developers and PMs and do a good job with threat modeling before you get started.

See other Security MVP Article of the Month columns.