My Roadshow Demo Site and XSS Vulnerability

I had a nightmare the other night, well not exactly a nightmare but at least a shiver down my spine; that I'd published my Roadshow Demo with an XSS vulnerability. And, TBH, if it wasn't for ASP.NET's built-in protection, it would have an XSS vulnerability. That doesn't trouble me when I'm just using it to demo stuff on my own machine but when I publish it for anyone to download and use, I feel more of a responsibility.

Cross Site Scripting (XSS) is an all too common form of vulnerability (I was going to post a recent example I came across as I thought it had been fixed but it seems not) where some form of code injection occurs in a page on your site, typically by a malicious user. A very common form of XSS is a known as a reflected XSS vulnerability and that's what I'm guilty of (almost) creating on my demo site. Fortunately, ASP.NET saves me from my sins but it's not a good idea to rely solely on ASP.NET's security mechanisms. Take advantage of them of course, but make sure you're building your own defenses too. So to the problem...

The Problem

A reflected XSS vulnerability occurs when some piece of user data is re-used to generate part of your page output. If I fail to check that user data thoroughly, what's to stop someone entering some malicious script? That script would then be rendered on the page, thereby causing some unexpected and almost certainly undesirable behaviour. In the case of my demo site, I take a (user defined) stock ticker symbol and use that to get a stock quote. As part of the page rendering, I also reflect back onto the page the ticker symbol entered by the user (in my defence, this came about as a result of 'hacking' the service to make the demo work without a live connection - the site didn't suffer from it before). Now say instead of entering the ticker symbol 'MSFT' some practical joker instead entered '<script>alert('hello');</script>'. What's to stop that arbitrary piece of script running on my page? Well in my case, just ASP.NET's built in security mechanisms. Phew. But to see what can go wrong, we'll need to switch those off (temporarily).

Dropping our Defences

So, if you have the demo to hand, browse to the page 'StockPrice1.aspx' and, instead of 'MSFT' enter '<script type="text/javascript">alert('hello');</script>' in the stock symbol field and hit 'Get Quote'. You should see a message like this:

That's ASP.NET detecting potentially malicious input and preventing it getting through. Thank you ASP.NET. To switch off this ASP.NET security feature for the site, modify web.config and set the validateRequest attribute of the pages element to false (as below). Make sure you switch this back again later! (You can also disable validation for a particular page in the page directive).

Now run the page again with the malicious input and you'll notice that the 'malicious' alert pops up once (and only once). This is because the HeaderTemplate of the DetailsView control contains a Literal control bound to the companyName field of the ObjectDataSource control (and our service just returns the Ticker symbol we entered as the company name as we don't do a real lookup). Note though that the companyName field in the DetailsView looks like this:

And that's because by default, this DetailsView field is HTML encoded. Edit the properties of the DetailsView, select the companyName field and set the HtmlEncode property to false. Now run the page again and you'll find that the alert pops up twice - once for the header and once for the companyName field.

Raising our Shields

So what can I do to defend against such an attack? Firstly, you *must* validate all user input. And if you're going to use that input as output (ie you're going to render user input on a page) then HTML encode it. If you can't HTML encode it (eg some HTML need to be allowed) then you'll need to make sure you filter it before you write it. For an in depth look at preventing XSS attacks in ASP.NET, see this MSDN article.

In my case, I can add a RegularExpressionValidator to the page and set its ValidationExpression to something like '[a-zA-Z]{3,4}' to allow 3 or 4 character ticker symbols with only alpha characters. On the 'output' side, I could remove the declarative ObjectDataSource parameter and instead replace it with the following in the Page_Load handler to HTML Encode the user input:

In my case, I could also modify the service to HTML encode the user input (I'll repeat again, this problem wouldn't have occurred with the 'real' service as the companyName field was not mapped to the user entered 'ticker' field but to a field returned from the remote stock quote web service):

Finally, make sure you re-enable the HTML encoding on the DetailsView and requestValidation on the site.

In Summary

  • It's very easy to get caught out by a XSS vulnerability (and the one I describe above is only one of the possible XSS vulnerabilities)
  • As always, defence in depth should be your mantra
  • ASP.NET has some great security features to prevent you getting caught out. Make use of them, but don't depend on them
  • Do make sure you validate *all* user input (critical from a security perspective and a user experience perspective)
  • If you're going to echo some user input, *make sure it's safe*. HTML encode it, filter it, be very, very careful with it...

Some Further Reading

Wikipedia Entry on XSS

MSDN Article: How To: Prevent Cross-Site Scripting in ASP.NET

ASP.NET Anti-Cross Site Scripting Library

How To: Protect From Injection Attacks in ASP.NET

Patterns & Practices: Design Guidelines for Secure Web Applications

Use Regular Expressions to Constrain Input in ASP.NET

Take Advantage of ASP.NET Built-in Features to Fend Off Web Attacks

Technorati tags: asp.net, xss, security