CSS History Leak or “I know where you’ve been” (Edge)

Hello fellow bug hunter!

I’ve been thinking this morning on the classic trick originally discovered by Jeremiah Grossman back in 2006, where you could find out which sites were visited by the user. If you are not familiar with this beauty, I recommend you reading his original post. I will do my best to quickly explain, just in case. The following trick works on MS Edge, but I haven’t tried hard on other browsers.

Regular links have by default different colors depending if the user has visited them or not. For example, if we place here a link to google.com and another one to sarabastalanga.com, it’s likely that they will have different colors because you never visited this second site (a word that just came up to my mind) but for sure you’ve been in Google. An image will describe what I mean, much better than my words:

Visited vs Non Visited

They have different colors because I’ve been in google but not in sarabastalanga. Easy and apparently harmless, correct? Let’s see.

Before the Cascading Style Sheets era you could control the color of the links using the <body> attributes vlink, alink, and link, but -as far as I remember- there was no way to get the current color via scripting. In other words, the user could see the difference between the visited/non-visited links but the attacker didn’t have a way to get that information.

The arrival of CSS gave browsers a lot of functionality which essentially replaced the old way to style pages. Developers could now have a clear separation between code/markup and style and one of the greatest features of CSS were the pseudo-classes, which allowed dynamic changes of the style depending on user interaction like mouse over links, active elements, etc. For example, you could use :visited pseudo-class which conveniently allowed you to set a different color to the links(<A>tags) that were previously visited. How nice! And this feature is still with us.

The big problem was that you could get the color of those links, via scripting! It allowed attackers to know if you’ve been on a site, depending on the color of the links. Essentially the attack goes like this: you render all the sites that you want to probe inside <A> tags, setting a :visited with a red color otherwise, blue. Then, by checking the colors of the links you know the red ones are the visited by the user.

// Quick Pseudocode
function linkVisited(linkElement)
   // Returns true when visited, false otherwise.
   return getComputedStyle(document.links[i]).color == "red";
for (var i in document.links)
{  // Go over links and alert the ones that were visited.
   if (linkVisited(document.links[i]))
      alert("Link visited:" + document.links[i]);

In any case, Jeremiah shared his discovery with the world, presented it everywhere, and browsers fixed the problem. Today when you request the browser the color of a link, it always returns the original, non visited one. So, it does not matter how much you ask via scripting, you will always get the same value anyway.

Different variations of this issue arrived using the most amazing techniques like timing, cache, and others. Browsers kept patching and fixing, but there’s always a way around it. And here’s what I found this morning: a simple variation that works on MS Edge. It is based on the original by Jeremiah, so all credits to him. What I did was a simple check, having one link to bing.com and saving its CSS state, then changing that same link to bingadunga.com (a site that I never visited) and compared the new CSS state with the old one. In other words, quite simple. The pseudocode is something like this:

// Quick Pseudocode
<a href="http://www.bing.com">Bing</a> (Visited link)

visitedCSS = getComputedStyle(document.links[0]);

document.links[0].href = "http://www.bingadunga.com"; // Not Visited.
notVisitedCSS = getComputedStyle(document.links[0]);

// By comparing both cssTexts I stumbled upon a difference: webKitTextFillColor
// How nice. Let's build an exploit!

By finding a difference between both CSSs we got a new way to achieve the same trick thanks to webkitTextFillColor. So essentially, the only change from Jeremiahs original is the property “color” to “webkitTextFillColor”. Hard work on my part hehehe =)

Let’s see this in action. If you are a Linux user you can just watch the video below. If you are in Windows test it live on Edge! Here’s the PoC. Need the files ? Just grab them from here.

Update: this bug was silently patched on MS16-145.


Have a nice day, bug hunter! And remember: persistence and passion will bring you browser bugs (and even a great wife like the one I have!)