Stealing Data in Great style – How to Use CSS to Attack Web Application.

This article will show you an example of how you can use the ability to inject your own CSS rules into a web application to exfiltrate data. This attack can be particularly practical for stealing tokens that protect against CSRF attacks.

In this text we will see that CSS injections can be used to steal data placed inside the attributes of HTML tags and to steal any data on the site using the magic of WebKit browsers, scroll bars styling and font ligatures.

Exemplary Vulnerable Site 

For the purpose of the examples of this article, we will assume that we have a page where there are two CSRF tokens that we will want to steal.

In the example above, we have a simple page where two CSRF tokens are defined:

  • One of them is in the <input> field, which is very common in real web applications,
  • The second one is inside the <script> tag.

Additionally, we can add the parameter ?css= in the URL to this script, and it will be placed inside the <style> tag.

Our goal is to extract both of these tokens using CSS.

To run this PHP application, you can use the server built into the PHP interpreter. So, in the folder where you place the file with the code above, you can call the command:

Then the above page will be launched on port 12345 on the localhost.

Picture nr 1 Exemplary “vulnerable” site HTML code

In addition, we will need a virtual machine on which we will host the server part of the attack. For the purpose of this article we have prepared a Vagrantfile file, which will run the virtual machine available in host-only mode at 192.168.13.37 and install the necessary software (node and fontforge).

To turn on virtual machine one just needs to save the code above in a file named Vagrantfile and run commands:

By default, the /vagrant directory on a virtual machine is a shared folder with the host, through which you can easily exchange files.

Extracting the Token from the Attribute

So let’s try to extract the token value from the hidden type field.

It turns out that this task is quite simple because CSS selectors come directly to our aid. The selectors, in short, allow us in CSS to choose exactly the element that we want to stick to. We can search for elements by class, identifiers, tag names or values of any attributes. Below are some basic examples of CSS selectors:

A clue to further course of our attack is the last of the above mentioned examples of selectors: CSS allows us to search for an element according to the beginning of the attribute value. Consequently, we can prepare different styles for all possible values of the first attribute character. Let’s see it in the following example:

What is happening here? We can see that depending on the first character of the value of the input element, a different background is set for it. By default, browsers try to minimize the number of external HTTP requests sent to the servers, which means that the browser will send a request only about the background image to which the selector will be matched. This means that the first token character will hit the attacker’s server!

We can easily check this by inserting the appropriate CSS into the page and looking into the development tools of the browser (Picture nr 2).


Picture nr 2 In development tools, you can see one request to an external server. Conclusion: the first character of the token is “6”

For the time being, we have extracted only one token mark; in a practical case, of course, we want to extract all of them. To do this, we will have to extract more characters iteratively; i.e., we first extract the first token character. When we have the first token character, we generate a new CSS and try all the possibilities for the second character. When we have the second character, we generate another CSS to extract the third character, and so on.

Of course, we won’t do it manually, but we’ll help ourselves with an appropriate JavaScript code, which will automate the whole thing. We make assumptions:

  • On the attacker’s server (192.168.13.37) there will be an HTML page with JS code to automate the attack.
  • An attacker’s server will also accept feedback connections to tokens extracted through CSS. We assume that the return connection will be made to the /token/:token URL; e.g., if we pull out the second token character, a request may be sent to the server to /token/6b, which means that the first two token characters are 6b.
  • The communication between the server receiving the return connections and the JS code will be carried out via cookies. This means: if there is a return connection to the server with the information that the first two token characters are “6b”, then a cookie will be set: “token=6b”. The JS code will regularly check if a cookie has been set; if it has, it will use its value to generate a new CSS to extract another token character.

Next, in the same directory we use command:

Thanks to this, all dependencies necessary to run the server will be automatically downloaded from the server. The server part must answer only three HTTP queries:

  • /index.html – an index.html file from the same directory will be served.
  • cookie.js – a js-cookie library file will be served, which enables convenient operations on cookies from the JS level.
  • /token/:token – return connections with subsequent token characters will be accepted at this address. In response, a “token” cookie of the same value that was transmitted to the server will be set.

Let’s create an index.js file that will perform all these functions:

Now we need to prepare an appropriate HTML file in which all the magic will really happen and we will steal all the token characters. Looking at the problem we want to solve at a fairly high level, we know the following things:

  • We want to extract a token consisting of 32 characters from the 0-9a-f range.
  • We have a page that is vulnerable to CSS injection: we can refer to this page from the <iframe> element in HTML.

The attack itself will consist of the following steps:

  1. If the length of the token we have managed to extract is less than expected, we perform the following operations.
  2. We clear the token cookie containing any previously extracted data.
  3. We create an iframe element in which we refer to a vulnerable page, considering the appropriate CSS code, which will allow us to extract another token character.
  4. Wait until the return connection to the attacker’s server sets us a token cookie.
  5. When a cookie is set, set it as the current known token value and return to step 1.

At the level of HTML itself, we can write the above reasoning as follows:

In the next step let’s implement the next functions performing our attack. First we clear the cookie with the token. This operation is very simple:

We simply use the js-cookie library and delete the token cookie. Next, we have to assign the appropriate URL to the <iframe> element:

And, of course, implement the function generating the appropriate CSS. This function simply iterates after all possible characters in the token (i.e., 0-9a-f) and generates appropriate rules in CSS:

Finally, we need to implement a function that waits for the cookie token to be set via the return connection. We will use the Promise mechanism in JS to build an asynchronous function: our code will check every 50ms if a cookie has been set; and if so, the function will return the value immediately.

Finally, the code realizing the attack looks like this:

Let’s save it under the name index.html in the same folder as index.js, and then let’s call the command:

Now you can just go to go to http://192.168.13.37:3000/index.html from the browser level and watch as we steal further token characters.

In the browser’s development tools on the right side, you can clearly see alternating requests: first containing CSS injections, and then a return connection to the attacker’s server with the token value.

To sum up: if we have an injection of CSS code on the website, we can extract from it any data placed in HTML tag attributes using selectors of the type:

Extraction of a Token from the Website Content

In the previous chapter, CSS selectors limited us to the attributes themselves. We could identify the element based on the beginning of the attribute value, but we can’t do the same for the text placed in the tag itself. CSS simply doesn’t have selectors of this type. So you may ask yourself: how do I extract the token placed inside the <script> tag? E.g., in a code such as the one below:

In advance of the facts: such an attack is, of course, possible, but it is much more complicated and requires more preparation and learning more about theories. But thanks to that, it is much more satisfactory. This time we will need to define our own font with ligatures and style the scroll bars.

Let’s start with ligatures. What is ligature? In short, a font ligature is a sequence of at least two characters that has a graphic representation. Probably the most common ligature in serif fonts is the “fi” sequence. In the picture below, you can see letters “f” and “I” separated by a space, and below the same letters written directly after each other. It is clear that in the second line we have a different graphic representation of the sequence of these two letters—the top of the letter “f” is connected with a dot above “I”. Ligatures should not be confused with kerning: kerning defines only the distance between letters in the font, while ligature is a completely independent glyph (graphic symbol) of a given sequence of characters.




Picture Standard example of ligature: “f” and “i” characters sequence

With the help of additional software such as fontforge, we can create our own fonts as well as our own ligatures in the fonts. Fontforge is an advanced tool for creating fonts and we will use it to change fonts from SVG format to WOFF. This is necessary because browsers have stopped supporting the SVG format in fonts (hence the need to use the WOFF format), while from a our point of view, defining fonts in the SVG format is by far the most convenient. FontForge is already installed on a VM, to which I provided a vagrant file in an earlier text fragment. FontForge allows you to define scripts to perform conversions between different font formats. Let’s prepare a file named script.fontforge with the following content:

The script can then be triggered as follows:

The script will create a file named <file>.woff. In this simple way we have created a simple converter between font formats.

So let’s see how the font definition in SVG may look like. Below is an example of a simple font in which all small letters of the Latin alphabet are not assigned any graphic symbol and their width is 0 (attribute: horiz-adv-x=“0”;). While the sekurak ligature is also defined, it does not have a graphic symbol, but is a very wide character.

Now let’s try to see how this font behaves in the example of a simple page: we have one <input> type field and below an <span> type element, to which the <input> field value is rewritten. The <span> item has a blue background and a font created from the SVG file above.

Let’s see what’s going on. When I enter digits, they are displayed normally because they are in no way defined in the font. When I use any letters, nothing is displayed: the letters do not have a graphic symbol or any width of the sign. However, when I type the word “sekurak”, you can clearly see that the blue background has become longer: precisely because the font has defined the ligature for such a sequence of characters.

At this stage, some of the readers may start to wonder what we are aiming for. The good news is that we are already close to explaining the matter.

Let us now move on to a completely different subject: the scroll bars. In browsers based on WebKit or one of its forks, we have the ability to style scroll bars using the CSS pseudo-element –webkit-scrollbar. Let’s try to define it as follows:

In the first body block I set the white-space directive to nowrap. Thanks to this, if the text is wider than the width of the window, it will not be broken to the new line. As a result, it will force the scroll bar to appear. For the scroll bars to be styled at all, you must first add a pseudo-element –webkit-scrollbar; only then can you style a particular bar. In the third directive I set the style for the horizontal bar so that an attempt to retrieve its background would execute a request to the server under the control of an attacker. What’s important here is that, like the selectors, browsers minimize requests to external servers, so the browser will not try to download the background to the horizontal scroll bar if you don’t need it. This can be seen in the example below.

When I use “sekurak” ligature, a horizontal scroll bar appears and the browser immediately sends a request to the server. The conclusion is that the attacker is able to detect if this bar is appearing. The issue that remains is the proper use of this information.

Let us return to our earlier example. We have a token in the following form:

When I use “sekurak” ligature, a horizontal scroll bar appears and the browser immediately sends a request to the server. The conclusion is that the attacker is able to detect if this bar is appearing. The issue that remains is the proper use of this information.

Let us return to our earlier example. We have a token in the following form:

Let’s assume that we will create four fonts in which each alphanumeric character has zero width with the exception of single ligatures:

  • In the first font the ligature “0 will have a large width.
  • In the second font the ligature “1 will have a large width.
  • In the third font the ligature “2 will have a large width.
  • In the fourth font the ligature “3 will have a large width.

Each of these fonts will be placed in a very narrow element <iframe> and the scrolling bars will be styled. So what will happen now? Exactly in one of these iframes there will appear a scroll bar, in which the ligature has been defined for: “0. What is the conclusion for an attacker? That the first character of the token is 0.

However, before we move on to programming a practical attack, we have to face one more problem. Browsers treat fonts as a relatively small priority resource. So it may turn out that in a iframe where it should not happen, a horizontal scroll bar will appear for a fraction of a second, as the browser will already interpret the CSS style, but it will not yet be able to download the font. You can see it in the following video in slow-motion, where I refresh the page. When refreshing for a short while, the scroll bar appeared, which caused the query to be unnecessarily sent to an external server.

So I had to solve the problem and modify my attack to make sure that the scroll bar would not appear even for a fraction of a second in an iframe where it should not appear. The solution turned out to be a cache! If we use Chrome and download some cache oval resource twice in a relatively short time, Chrome may use a so-called memory-cache. If only the data is in the memory-cache, the loading time goes down from a few microseconds to several dozen microseconds (this is shown in the picture below).

Picture Downloading data from memory-cache takes microseconds>

So to make sure that the font will be downloaded from the memory-cache, I have to create an iframe where I will refer to it, and I will add caching headers in the server response. Unfortunately, it turns out that Chrome will use memory-cache only if the resource was downloaded from the same origin and in the same context (i.e., I cannot download the font as an image to cache it). This means that before attempting to extract a token, I need to send another query with the style defined in a particular way to make sure that it will be cached.

Let’s think for a moment how to make an attack more efficient. The simplest possible variant seems to be such an attack in which we create sixteen iframes and in each of them we define the ligature for one possible token character (i.e., 0-9a-f). This means that for each character we want to pull out, we need to create sixteen fonts and sixteen iframes. It can cause quite a large margin on our attack. Instead, let’s try to use the “divide and conquer” method:

  1. In the first step, we prepare two fonts: in one of them the ligatures are defined for: “0,  “1,  “2,  “3,  “4,  “5,  “6,  “7, and in the second for: “8,  “9,  “a,  “b,  “c,  “d,  “e,  “f.
  2. The scroll bar will be displayed on exactly one of these fonts. Assuming that this will be the first set, we have to divide it into two fonts: in the first ligature for: “0,  “1,  “2,  “3, and in the second for: “4,  “5,  “6,  “7.
  3. And so on, until we have two fonts with single ligatures, which will allow us to extract a specific token sign.

Preparation of the Proof-of-Concept Attack

On the server side we will only need an endpoint, which will generate a font with the appropriate ligatures and an endpoint to receive information on what characters may be present on a given token position. Let us start from programming this part.

Below is package.json file:

After running the command “npm install” in the same directory as package.json file, all dependencies necessary for the code to work will be downloaded automatically.

The function generating the font must take only the prefix for which it is to create ligatures and from which characters the ligatures are to be created. We will use the js2xmlparser library, which allows us to generate an XML file based on JSON (so we will build a font in SVG), and then we will use fontforge to convert SVG to WOFF.

Then we will program the endpoint receiving the return connection and serve statically js-cookie and index.html. Below is the full code of the server file index.js:

From the browser JavaScript side, we have to launch the following attack:

  1. We have a token of known length (32 characters) and known set of characters (0-9a-f), and all operations below are performed as long as the extracted token is shorter than expected.
  2. Using the “divide and conquer” method, we divide the possible character set for a given token character into two and prepare two fonts with appropriate ligature sets.
  3. In the first place we make sure that the fonts are in the cache before we carry out further attacks.
  4. Create two iframes referring to two different fonts. The scroll bar should appear in exactly one of them.
  5. The feedback from the scroll bar sets a cookie to indicate which character set it appeared at. If there is still more than one possible character, return to step 2.
  6. If you know what the next token character is: add it to the token and go back to point 1.

Below is the full, commented code carrying out the attack.

And the live attack looks like this:

Summary

In this article, I showed two ways in which an inconspicuous action such as injecting one’s own CSS content into a website can result in the possibility of stealing content from the website, with the example of extracting CSRF tokens. The first, relatively easy way concerned tokens sewn into attributes; the second one allowed to extract virtually any content from the site—in a more complicated way, with the use of generating your own fonts and styling the scrolling bars in Chrome.

The conclusion of this is that while injecting your own CSS styles does not give you such powerful attack capabilities as XSS, it still allows you to do real harm to the user in the context of a given web application.