(Update Feb 25, 2018: Google Drive deprecated web hosting, so the PoCs below don't currently work. I'll fix them soon, in the meantime you can see the source code here)
I just came back from a very fun trip around Europe. Among other places, I visited Hamburg, to attend HackPra 2013, which was hosted in AppSec Europe. In there I gave the presentation Matryoshka - titled after the famous Russian dolls.
Today I'm blogging about one of the subjects of that presentation, an information leak introduced by a "side channel" present in iframes. I didn't give much detail in the presentation since I was afraid I was gonna run out of time (this was just one part of the presentation). This blogpost is meant to add more detail, as well as give a couple more details I wasn't sure worked at the time of the presentation.
A quick summary of the problem is that, under certain circumstances, it is possible to know when text inside an iframe wraps to the next line. Text wrapping is when a line is longer than the width of the area it can be displayed into, so it needs to wrap to a second line.
Being able to detect text wrapping is an interesting problem, as it allows us to learn some information about the framed website, which might be particularly dangerous under some circumstances.
To show a small example, the following iframe is hosted in a different domain than this blog post:
We are disallowed to know what the contents are because of the Same Origin Policy. It's important to understand this, we can iframe anything, including third party sites to which you are authenticated to. This might include sites that contain secret information, like your email inbox, or your bank statements.
Now, let's see what we can do:
- We can, change the width and height of this iframe at our will.
- We can navigate the inner iframes from that page.
- We can change the style of your scrollbars or detect their presence.
Navigating Child Iframes
The reason for this is because the window.frames[] property is exposed cross-origin, and it's allowed by the Same Origin Policy to navigate child frames, even those that are cross domain.
The reason this matters, is because once we control a child iframe in our target page, we can know the position of the iframe relative to the browser/screen. This will let us know if the content of the page wrapped, as we'll discuss later on.
Detecting when the text wraps to the next line is the corner stone of this attack.
Detecting Iframe Screen Coordinates
In Trident and Gecko based browsers, it is possible to detect the position of an iframe relative to the screen. This is interesting, as it allows us to know exactly when an iframe moves down because of text wrapping.We detect this with one of two properties, either window.screenTop for Trident based browsers, or window.mozInnerScreenY for Gecko based browsers (and mouse events in general). These properties are only readable from within the target iframe, but as we explained before, it is perfectly possible to navigate our target site child iframes, and once we do that, we can reduce the width of our iframe until text doesn't fit in the line anymore, and moves the rest of the line to the next line, displacing our iframe.
Please note this proof of concept doesn't work in all browsers (notably, it only works in firefox/ie), so it might not work for you, but feel free to give it a try.
Detecting Scrollbars Presence
This is interesting, in some browsers, it is possible to apply CSS to the scrollbars of the iframe. This is important because this would leak whether the text wrapped contains a background-image, which would be requested when the scrollbar is shown. In some browsers you need to change the backgroundImage after the creation of the iframe for the image to be requested.Please note this proof of concept doesn't work in all browsers (notably, this only only works webkit-based browsers), so it might not work for you, but feel free to give it a try.
What will happen when you click that button is:
- The width of the iframe will be slowly reduced pixel by pixel.
- When the word "dusk" wraps to the next line, it will display the vertical scrollbar.
- When the vertical scrollbar is shown, the background image will be requested.
- We detect when such requests happen by checking document.cookie.
Measuring Word Width
- Resize the target iframe to:
- Slowly reduce the width until the text wraps. (You can reduce in fraction of pixels).
- If you are detecting a scrollbar, ensure to increase the height to make the scrollbar disappear.
- If you are detecting text wrapped from a child iframe, detect changes from the new current position.
- Repeat
- First wrapping: hello, my name is bond, james bond.
- Second wrapping: the secret is on the island!
- Third wrapping: bond, james bond.
- Fourth wrapping: name is bond,
- Fifth wrapping: bond, james
- Sixth wrapping: the secret
- Seventh wrapping: my name
- Eighth wrapping: secret is
- Ninth wrapping: name is
- Tenth wrapping: is on
- hello, my name is
- hello, my james bond.
- hello, my name is bond.
- hello, is bond, james bond.
- hello, my bond, james bond.
- ...
Turns out that usually, all letters have a different width, and as long as such width is unique, it's almost trivial to calculate which letters are in each wrapping (although we don't get the order of such letters).
The solution to this problem is the classic knapsack problem which I won't go into details of. While we won't be able to get the order of the letters, we can get which letters to a reasonable degree of accuracy, which should be sufficient to have a very good guess of the value.
It's unclear what's the best solution to this problem. This information leak isn't a vulnerability per-se in browsers, but rather a well known and understood feature. This blog post will hopefully trigger some discussion around this subject and we can come up with solutions.
One challenge is that if we stopped providing mozInnerScreenY / screenTop, then it would be significantly harder to detect and protect against clickjacking. So whichever solution we come up with needs to take that into consideration.
And that was it! This attack allows you to steal contents from websites that don't use X-Frame-Options (or have a fixed CSS width) in all major browsers with browser standard functionality (no vuln up my sleeve!). I felt inclined to use an acronym for this attack (WOLF) since Thai Duong/Juliano Rizzo's attacks (POET, BEAST, CRIME) sound better than "cross-BLAH-jacking", and I like wolves (although, not as much as cats), specially since it kind of feels like insanity wolf to me.