Plugging the CSS History Leak

Sid Stamm


Privacy isn’t always easy.

We’re close to landing some changes in the Firefox development tree that will fix a privacy leak that browsers have been struggling with for some time. We’re really excited about this fix, we hope other browsers will follow suit. It’s a tough problem to fix, though, so I’d like to describe how we ended up with this approach.

History Sniffing

Visited and Unvisited LinksLinks can look different on web sites based on whether or not you’ve visited the page they reference. You’ve probably seen this before: in some cases, visited links are purple instead of blue. This is just one of the many features web designers use to make the web the best it can be, and for the most part that’s a good thing.

The problem is that appearance can be detected by the page showing you links, cluing the page into which of the presented pages you’ve been to. The result: not only can you see where you’ve been, but so can the web site!

Originally specified as a useful feature for the Web, visited link styling has been part of the web for… well, forever. So this is a pretty old problem, and resurfaces every once in a while to generate more paranoid netizens.

The most obvious fix is to disable different styles for visited versus unvisted links, but this would be employed at the expense of utility: while sites can no longer figure out which links you’ve clicked, neither can you. David Baron has implemented a way to help keep users’ data private while minimizing the effect on the web, and we are deploying it to protect our users. We think this represents the best solution to the problem, and we’ll be delighted if other browsers approach this the same way.

Technical Details.

The biggest threats here are the high-bandwidth techniques, or those that extract lots of information from users’ browsers quickly. These are particularly worrisome since they enable not only very focused attacks, but also the widespread brute-force attacks that are, in general, more useful to a variety of attackers (potentially including fingerprinting).

The JavaScript function getComputedStyle() and its related functions are fast and can be used to guess visitedness at hundreds of thousands of links per minute. To make it harder for web sites to figure out where you’ve been without radically changing the web, we’re approaching the way we style links in three fairly subtle ways:

Change 1: Layout-Based Attacks
First of all, we’re limiting what types of styling can be done to visited links to differentiate them from unvisited links. Visited links can only be different in color: foreground, background, outline, border, SVG stroke and fill colors. All other style changes either leak the visitedness of the link by loading a resource or changing position or size of the styled content in the document, which can be detected and used to identify visited links.

While we are changing what is allowed in CSS, the CSS 2.1 specification takes into consideration how visited links can be abused:

“UAs may therefore treat all links as unvisited links, or implement other measures to preserve the user’s privacy while rendering visited and unvisited links differently.” [CSS 2 Specification]

Change 2: Some Timing Attacks
Next, we are changing some of the guts of our layout engine to provide a fairly uniform flow of execution to minimize differences in layout time for visited and unvisited links. The changes cause all styles to be resolved on all links for both visited and unvisited states, and it is stored; then, when the link is styled, the appropriate set of styles is chosen making the code paths for visited and unvisited links essentially the same length. This should eliminate some of the easy-to-mount timing attacks.

Change 3: Computed Style Attacks
JavaScript is not going to have access to the same style data it used to. When a web page tries to get the computed style of a link (or any of its sub-elements), Firefox will give it unvisited style values.

What does this mean for users?

For the most part, users shouldn’t notice a change in how the web works. A few web sites may look a little different, but visited links will still show up differently colored. A few sites that use more than color to differentiate visited links may look slightly broken at first while they adjust to these changes, but we think it’s the right trade-off to be sure we protect our users’ privacy. This is a troubling and well-understood attack; as much as we hate to break any portion of the web, we need to shut the attack down to the extent we can.

We have to be realistic, though: there are many ways all browsers leak information about you, and fixing CSS history sniffing will not block all of these leaks. But we believe it’s important to stop the scariest, most effective history attacks any way we can since it will be a big win for users’ privacy.

If the remaining attacks worry you, or you can’t wait for us to ship this fix, version 3.5 and newer versions of Firefox already allow you to disable all visited styling (immediately stops this attack) by setting the layout.css.visited_links_enabled option in about:config to false. While this will plug the history leak, you’ll no longer see any visited styling anywhere.

Enhancing Privacy on the Web.

We want to bridge the gap between our users’ expectations of privacy and what actually happens on the web. Sometimes users have an expectation that we preserve their privacy a certain way, and if we can, we want to live up to it. Privacy isn’t a feature that can simply be added to a browser, though; it often comes at the expense of utility. We think we’ve found a fix that will balance flexibility for web developers while providing a safer experience for our users on the web.

Sid Stamm, Mozilla Security

67 responses

  1. Colin Dean wrote on :

    Using a checkmark image after/before a visited link is a popular thing. I understand the security precaution, though. Will data: URLs still be allowed in order to preserve this great usability enhancement?

    Case in point: uses a small blue checkmark after visited links. Our user testing has found that people like this.

  2. Alex Stapleton wrote on :

    How will these changes effect sibling selectors? You mention sub-elements of links but you can restyle siblings of visited links too…

  3. Giorgio Maone wrote on :

    Congratulations to David Baron and the others involved.
    Very well thought fix :)

  4. Matthew wrote on :

    It would be helpful to know what other alternatives you considered and threw out. This article, presenting only your final choice, makes me worry that Mozilla is just picking this approach because it’s easier for you to make people’s websites look worse than to find a better solution.

    Please provide users an option in their browser to disable this and revert to the original behavior of allowing visited links to have all the styles they wanted.

    For me, I’m going to have to retest all my sites for yet another Mozilla-specific CSS style issue and JS behaviior that will retroactively go bad. I have to worry about silly things like where the designer wanted to make his links a point smaller or a new font when they’re clicked.

    1. Sid Stamm wrote on :

      @Matthew: Much of the history of this bug is available on bugzilla (Bug 147777). Most of the options considered are debated in the comments of that bug. We did consider many different approaches, but most of what we looked at either made the user’s experience much worse than this fix, or they turned off too many features that developers rely on (outside of just :visited styles).

  5. Wladimir Palant wrote on :

    What I am missing out here are the implications for canvas – it can be used to make a “screenshot” of your webpage and read out pixel data, there must be some mechanism preventing visited links from showing up there.

  6. Adam wrote on :

    I’m curious what the scale of high bandwidth and quickly are in places like: “The biggest threats here are the high-bandwidth techniques, or those that extract lots of information from users’ browsers quickly.”

    There’s obviously room for disagreement when researchers find things, and I think clarity about the goals would be really helpful.

    Is 10ms per link high-bandwidth? 10 seconds? I’m guessing it’s somewhere in between.

    1. Sid Stamm wrote on :

      @Adam: I consider hundreds of thousands of links per minute to be high-bandwidth. Many of the timing attacks tend to take a while to obtain a statistically significant result. Those are low-bandwidth, and finish in the order of tens of links per minute.

  7. helenrae wrote on :

    You mention positioning the content itself, but what about a background image?

    Will it still be possible to position an already loaded and applied background image via CSS (i.e. an image sprite containing multiple states) ?

  8. thornmaker wrote on :

    Will this fix address non-js based history leaking… e.g. ?

    1. Sid Stamm wrote on :

      @thornmaker: yes, change #1 takes care of that.

  9. Ulrich wrote on :

    A solution for Colin Dean’s problem would be to allow custom background images if the visited links point to the same domain.

  10. Tony Mechelynck wrote on :

    Does “Change 2” mean that links won’t change colour anymore if I click “Open in new tab” in the context menu of an unvisited link, then come back?

    @Colin: “Change 1” above seems to imply that adding some “after” element only on visited links (which would change the size of the space occupied by the link) would be forbidden. But maybe displaying a child element of the link as √ with “invisible” colours (fg=bg) if unvisited and “visible” (fg≠bg) if visited would be permitted?

  11. Roger wrote on :

    I can’t remember the details now, but I remember seeing reference to some API (maybe related to SVG?) that allowed JS to query the color of individual pixels. It is very easy to position links at absolute positions, so the history leak could be exploited through that… or have you guys also fixed it so it returns the color of unvisited links?

  12. David Baron wrote on :

    Allowing data: URLs still allows cases where some images would be slower to paint than others, leading to detectable performance differences.

    One possible way (a bit hacky) to make such images still show up is to have a checkmark image that has the same background color as the page, on a transparent background. Then rules like (supposing the page background is white):
    :link:after { content: url(checkmark); background: white }
    :visited:after { content: url(checkmark); background: blue }
    would still work.

    Depending on how much feedback we get about this, we might try to figure out a way to make this possible more easily. (It might be easier to support changing background-image than content.)

  13. David Baron wrote on :

    (My previous comment was a response to Colin Dean in comment #1; seems there’s a good bit of caching delaying the comments showing up, so the ordering might be more confusing than I expected.)

  14. Alex wrote on :

    I could still use color to steal history data, just less of it.

  15. Damian wrote on :

    Colin: Alas this will no longer be allowed as it’s very easy to use that same method to then extract the user’s history.

    However, I don’t see why there can’t be an exception for visited sites of the same website, surely the website can see what the user clicks on anyway within their own site.

  16. Remco wrote on :

    I think it would be possible to keep the feature of an image for a style. Basically it goes like this:

    * always load any graphics that are used in visited and unvisited achors.
    * use the same content box for visited, as for unvisited anchors. Then, clip the visited image if it’s too big for the unvisited content box. Pad the content box if it’s too small. This would mean that sites need to make the image a child of the anchor (so not style it with :after, like on, or use a background image. So some sites would break. But it would at least keep the possibility.
    * give javascript functions the ‘unvisited version’ of the page.

  17. Steve Krenzel wrote on :

    While I like the approach taken here, the usability changes and impact on web developers can be significantly minimized with a minor change to the implementation:

    Only apply these rules to URLs not on the current domain.

    Most pages that legitimately use visited styling do so for links on their own domain. You shouldn’t need to restrict the styling on these links in any way.

  18. David Baron wrote on :

    @Wladimir Palant (comment #5): the ability to make a screenshot using canvas is available only to chrome and extensions, not to Web pages; it would have other significant security and privacy implications other than this.

  19. Apphacker wrote on :

    Maybe allow for same domain policy so that web developers can still add fancy styling for links within their own domain.

  20. Michael wrote on :

    As a person with a color vision deficiency Change 1 will make Firefox completey useless for me. I hope the Chrome people are a bit wiser and don’t follow this nonsense.

More comments:1 2 3