Home Blog Index
Joseph Petitti —

How CSS can leak your browser history

Despite the huge effort that goes into making browsers secure, there are still a few ways they can expose your private data to attackers if you aren't careful. Most involve some kind of complex fingerprinting using JavaScript, but there is a way to trick users into giving up their browsing history using only CSS.

The :visited pseudo-class

In addition to regular selectors for HTML tag name, ID, and class, CSS also provides a few special selectors callde pseudo-classes. For example, the :hover pseudo-class represents an element that the user is hovering over with their cursor.

This privacy-invading trick involves the :visited pseudo-class. It's a way to style links that the user has already visited, making long lists of links easier to read.

A list of links where the ones that have already been visited are a darker color
This makes it easy to tell which posts you've already read

The fact that the styling is based on your browser's history makes this pseudo-class an obvious security issue. Because of this, browsers implement a number of measures to make it difficult to exploit.

The only rules you can apply to visited links are color, background-color, border-color, column-rule-color, and outline-color. Further, it's normally impossible to detect whether the rule is being applied with JavaScript because indow.getComputedStyle always acts as if all links are unvisited.

If it weren't for these security measures, you could just include a hidden link to www.shady-site.com and then automatically test whether the link has the visited style applied to it to figure out if a user has gone to the shady site before.

So how can you exploit the :visited pseudo-class when you can't check if the style is being applied? Just trick the user into telling you.

A meme from Arthur
        showing Brain saying 'Have you ever realized how frighteningly easy it
        is to manipulate someone into doing whatever you want?'

Proof of concept

It's surprisingly easy to get people to give up their history with a little bit of trickery. I made a simple proof of concept that you can try out now. Don't worry, it only reports your history to you, check the source if you don't believe me.

The page simply assigns a common English word for each of the top 50 most visited sites in the US, and colors them black if the link is :visited. Unvisited links are white and therefore invisible, and a few decoy words are thrown in to make it less obvious how it works. Then we just put an invisible div over the whole thing to prevent the user from clicking or selecting the links, which would give away the trick.

The user types in all the words that are visible to them, and some JavaScript reports all the matching sites.

This example is kind of silly, but it's not too hard to imagine implementing a CAPTCHA service that only shows certain letters if you've visited a particular site. In fact, Fräntz Miccoli has already implemented a demo that does exactly that.

How to protect yourself

These types of tricks are difficult to detect, so the best thing to do is just disable styling visited links entirely. You lose the benefit of seeing what links you've already clicked on some sites, but it makes you more secure.

In Firefox, just add this line to your user.js file to disable the :visited pseudo-class:

user_pref("layout.css.visited_links_enabled", false)

If there is a way to disable it in Chrome I couldn't find it, but you shouldn't really be using Chrome for security anyway.