Modern browsers do an excellent job defending web applications against reflected Cross-site Scripting (XSS). They do so by using XSS filters that allow them to reliably block such attacks in the majority of cases.
Though these filters were often bypassed in the past, modern versions constitute a huge step toward a secure web, free from one of its most prevalent vulnerabilities. The way these filters work is rather simple, yet highly effective. When your browser issues a request to a website, its inbuilt Cross-site Scripting filter checks whether:
In theory, this works well, but the consequence is that there is no protection from stored XSS attacks. To defend against such a common vulnerability, it's important to implement a proper Content Security Policy (CSP).
The procedure described above is closely tied to the X-XSS-Protection header. For a long time, the default header value has been '1', which translates to 'block the malicious script'. But lately, Google Chrome changed this to '1; mode=block', meaning 'prevent the whole page from loading'. This change in default behaviour comes at a time in which most browsers have caused hackers immense frustration by drastically reducing the opportunity for exploiting a reflected XSS vulnerability.
Hackers Are Determined to Find Ways Around All Types of Web Security
A dangerous, if admirable, characteristic of most hackers is that they don't accept defeat. Some are even pleased when they encounter a Web Application Firewall (WAF). The restrictions they then need to bypass are what makes them so fascinating to attackers.
Attackers are forced to get creative when encountering a filter. And they learn more each time they succeed.
So if browsers lock the front door by blocking reflected XSS, hackers will simply investigate whether they can smash some windows instead!
Mike Gualtieri, a Cyber and Information Security expert from Pittsburgh, seems to be the kind of hacker that perfectly matches this description. But he took it one step further, as outlined in Stealing Data With CSS: Attack and Defense..
Stealing Your Private Data With Cascading Style Sheets
An everyday CSS selector may look similar to this one:
This will select all <a> tags and set the color of their link text to red. This doesn't allow a great degree of flexibility though, and may even interfere with the rest of your web design. It's possible that you may want to set the color of internal links to a different color than external ones, in order to make it easier for visitors to see which link will navigate away from your website. What you can do is create a class like the one below and apply it to all anchor tags that point to internal links:
This is not necessarily an ideal situation; it adds more HTML code and you need to manually check that the correct class has been set for all internal links. Conveniently, CSS provides an easier solution to this problem.
Selecting CSS Attributes
CSS Attribute Selectors enable you to set the color of every link that begins with 'https://netsparker.com/' to green, for example:
This is a nice feature, but what does this have to with data exfiltration? Well, it's possible to issue outgoing requests by using the background directive in conjunction with url. If we combine this with an attribute selector, we can easily confirm the existence of certain data within HTML attributes on the page:
<input type = "password" name = "pin" value = "1234">
This CSS code will select any input tag that contains the name 'pin' and the value '1234'. By injecting the code into the page between the <style> tags, it's possible to confirm that our guess was correct. If the pin was '5678', the selector wouldn't match the input box and no request would be issued to the attacker's server. This example does not describe the most useful attack out there, but it may be used to deanonymize users.
This is another example of how such exfiltration may work. It is directly taken from Mike Gualtieri's previously mentioned work.
Username: <input type="text" id="username" name="username" value="<?php echo $_GET['username']; ?>" />
<input id="form_submit" type="submit" value="submit"/>
<a id="aa"><a id="ab"><a id="ac"><a id="a_"><a id="_a"><a id="ba"><a id="bb"><a id="bc"><a id="b_"><a id="_b"><a id="ca"><a id="cb"><a id="cc"><a id="c_"><a id="_c">
What happens here is that there is a username inside the value field of an input box once the page loads. This seemingly cryptic piece of CSS and HTML code can actually provide the attacker with a decent amount of information.
If the username begins with 'a', a request containing 'a_' will be sent to the attacker's server. Should it end with 'b', the server will receive '_b'. If the username contains 'ab' on the page where the malicious stylesheet is embedded, the browser will issue a request containing 'ab'.
This is can become complicated. According to Mike Gualtieri's calculations, a combination of lower and uppercase characters, numeric characters and 32 symbols might result in a CSS payload that is more than 620 KB in size. It may be possible to extract the data with a smaller payload, if the casing doesn't matter, by appending the 'i' modifier to the end of the attribute selector. In this case, it's only necessary to use lowercase letters.
There are a few problems with this method, though, as it requires some prerequisites:
- The data must be present when the page is loaded, so that it's not possible to live-capture user input using CSS Exfil.
- There must be enough elements that can be styled using CSS. One element can't be used for two different exfiltrations. What you can do is use one element to find out the first letter and one element that you exclusively use for the last letter, since there is only one possible first and one possible last letter. However, for all other letters, this is less easy.
- The elements you use for exfiltration must allow CSS attributes that you can use url on, such as background or list-style etc.
Also, it isn't easy to reassemble the data. For example, if you would try to exfiltrate this rather weak password of a fan of the Swedish pop band ABBA, you would run into a serious problem.
This password begins with an 'a', ends with an 'a' and contains 'ab', 'bb', 'aa', as well as 'ba'. But that doesn't help you to reassemble the password. There is still much guesswork. You don't even know for sure how long the password is. 'abbaa' matches this description too, but it's still not the password we were looking for.
Mike Gualtieri's blog post gives us much to think about. Even something as simple as a programming language that was only meant to style documents can be abused by clever researchers in order to attack an application. If CSS continues its current development course, it will become even more useful for users – and attackers.
How Can You Prevent a CSS Injection Attack?
There are a few simple steps you can take to ensure your application is free from bugs that could allow attackers to include external style sheets of their choosing:
- Apply context-dependent sanitization. This means that you have to use different forms of encoding in different situations: for example, hex encoding within script blocks or HTML entities within other HTML tags. There might be situations where you need to use other forms of sanitization as well, like HTML encoding, or with the help of a white list.
- Scan your application with a vulnerability scanner, since the vulnerability is essentially an injection of HTML code that can be detected by most web application security scanners. Just like XSS, this attack requires an injection of code. Netsparker can easily detect the underlying vulnerability, which is similar to Cross Site Scripting.
- Implement a proper Content Security Policy (CSP) if you want to be absolutely sure that an attacker can't abuse this vulnerability, even if you forgot sanitization once. We recommend that you also implement a proper CSP that restricts from where images and stylesheets are allowed to be loaded. This enables you to instruct the user's browser to only load stylesheets from your own domain or trusted third parties, which would ensure such an attack would fail.
Each of these recommendations is essential to prevent the vulnerability across your entire code base.