Closed Bug 1455898 Opened 6 years ago Closed 4 years ago

Complicated CSS effects and :visited selector leak browser history through paint timing


(Core :: Layout, defect, P1)

59 Branch





(Reporter: mismith, Unassigned)



(5 keywords, Whiteboard: [pixel-stealing][layout:backlog])


(2 files)


An attacker can combine complicated CSS effects and the :visited selector to
reliably determine the visited status of arbitrary URLs, by forcing the browser
to complete expensive repaint operations if a test URL is visited, and comparing
performance measurements with those taken for a known-unvisited "control" URL.

(1) The attacker constructs an element that is complicated to render using CSS

In the attached exploit, we use the following inline style (crafted through
manual trial and error) to produce a link element that is costly to paint:

    display: inline-block;
    width: 5px; font-size: 2px; text-align: center;
    outline-width: 24px; text-shadow: 16px 16px 10px #fefffe;
    filter: contrast(200%) drop-shadow(16px 16px 10px #fefefe) saturate(200%);
    transform: perspective(100px) rotateY(37deg);

To further increase the burden placed on the browser's rendering engine, the
element is filled with a long string of Chinese characters. The exact
configuration is less important than the overall idea that the element is made
costly to draw by stacking on these challenges.

(2) The attacker forces the browser to repeatedly repaint the element if the
    test URL is visited.

We use the CSS :visited selector to apply different color styles to the link
element if it points to a visited URL. For example:

    a { color: white; background-color: white; outline-color: white; }
    a:visited { color: #feffff; background-color: #fffeff;
                outline-color: #fffffe; }

In the attached attack, we use the DOM to oscillate the link element's href
attribute between the test URL and a randomly-generated known-unvisited URL,
forcing a repaint with each oscillation if the test URL is visited.

An attacker could alternatively point the link to the test URL, then rotate the
colors of the CSS rules under :visited through a set of different values. This
similarly forces the browser to repeatedly repaint the element if the test uRL
is visited.

(3) The attacker measures paint performance during the process in step (2) and
    compares it with measurements taken for a known-unvisited control URL.

We measure the frequency of `requestAnimationFrame` callback executions over
time. The forced repaints cause a drop in this measurement if the test URL is
visited, relative to one collected for the control URL; thus we can make
a visited vs unvisited determination.

Experimentally, 70-80% fewer `requestAnimationFrame` callbacks fire in the case
of a visited test URL vs the control URL, with the effect observable down to
100ms measurement periods (producing measurements on the order of 4 vs 15). An
attacker using an intelligent search strategy could rapidly extract
visited/unvisited status for each of a set of test URLs.

VERSION (tested on 2 systems)

Firefox Version: 59.0.2 (64-bit) stable
Operating System: Windows 10 Pro Version 1709 (OS Build 16299.371)

Firefox Version: 59.0.2 (64-bit) stable
Operating System: macOS 10.10.5


`attack.html` demonstrates inferring visited status of arbitrary URLs through
the technique described above. If it does not seem to work, ensure that your
Firefox installation can properly display Chinese characters.
David: does this attack fall into the known :visited info leaks or is this enough of a performance improvement to be worth addressing?
Group: core-security → layout-core-security
Component: Security → Layout
Flags: needinfo?(dbaron)
I'm not sure off the top of my head.  Basically, that requires going through the existing bugs we have on such attacks (some of which include proposed solutions) to figure out what the state of them is.  It's been long enough since I've looked through them closely that figuring that out would be a decent amount of work... which is why I haven't responded yet.  I'm probably unlikely to be able to do that in the next few weeks as well.
(This should also involve evaluating which attacks work against which browser engines.)
I can confirm this particular attack is cross-browser.

(Vague statement to be careful of disclosure policies...)
Making it a P1 for investigation.  We can update the priority once we know more.  David -- I know you're traveling. IIUC you're the one who needs to make this evaluation? (Referring to the question in Comment 1.)
Priority: -- → P1
A variant of this attack is possible with a complex SVG image inside the link instead of placing CSS transforms on the link itself. The signal is again observable down to 100ms measurement periods, and is even stronger than the CSS variant over higher durations. A demo file is attached (most of the 2 MB size is the embedded SVG image).
Bug 1239897 is similar and has a solution proposal.
Depends on: 1239897
Not a very good solution. Bug 557579 has better ideas (and descriptions of more sneaky attacks).
Whiteboard: [pixel-stealing]
Keywords: testcase
Whiteboard: [pixel-stealing] → [pixel-stealing][layout-secscrub-fix]

Marking this as stalled pending continued discussions in the CSSWG on preferred method for resolving this type of history leak. Keeping in layout backlog icebox to take action on it once we have more consensus.

Keywords: stalled
Whiteboard: [pixel-stealing][layout-secscrub-fix] → [pixel-stealing][layout:icebox]

Sean, did anything ever come of the CSSWG discussions?

Flags: needinfo?(svoisen)

No, there's still not really been any movement on this in the CSSWG to my knowledge.

Flags: needinfo?(svoisen)

the "stalled" keyword refers to the security investigation; it's not appropriate when we have full working steps to reproduce. The engineering design of a fix may be pending other work or decisions but it still counts as a valid security bug hanging over our heads.

Given the importance of "Privacy" as a brand value maybe we should forge ahead with a unilateral solution rather than waiting for a standards change, as we did in 2010 with the first major fix for :visited history leakage. Maybe get off the whack-a-mole train of trying to eliminate timing attacks in graphics and change our approach to change what :visited means -- completely out of the realm of any decision by CSS standards. Project Fission is trying to resolve cross-origin leaks through site-isolation so maybe history needs to be part of that. For example, only style same-origin or same-"site" links as :visited, or double-key history and only show :visited if you've gone there from the current site.

[FWIW I didn't have much success with the attack.html testcase (on Mac) but svgattack.html was reliable enough in Firefox and Chrome.]

Type: enhancement → defect
See Also: → 557579, 1239897

I changed the rating to match bug 1239897 and other variants.

Per bug 1239897 comment 15 this issue has been published by the author in a paper at and got some press attention at the time. Maybe it's time to unhide this bug.

Group: layout-core-security → core-security-release

For what is worth, I am looking into these at the moment in bug 1506842. We have several dupes of this bug, any preferences for which one should be the canonical?

Flags: needinfo?(dveditz)

I don't have a strong preference for which bug you pick, a weak preference for "oldest" (that fits anyway; bug 557579 is perhaps too prescriptive of the solution, but bug 1239897 might work). But if you're the one doing the work and you strongly prefer bug 1506842 that's fine too. My main concern is that the different bugs use different techniques to detect the timing differences, and that in duping them all we might not actually fix one of the cases (short of a solution that simply stops showing cross-origin visited links or some such). So I tend to prefer keeping these all as "depends on" so they get independently verified once they're marked FIXED.

Flags: needinfo?(dveditz)

I'm going through all our P1s. The other see also bugs related to this are P3, so I'm going to set this at the same priority. Emilio is actively working on it anyway.

Priority: P1 → P3
Whiteboard: [pixel-stealing][layout:icebox] → [pixel-stealing][layout:backlog]

Now that we triage by severity, setting priority to P1 to reflect backlog prioritization on this bug as either in-progress, or planned development in the near term. See

Priority: P3 → P1

Removing employee no longer with company from CC list of private bugs.

My understanding is that bug 1632765 should've fixed this. The PoC no longer works.

Closed: 4 years ago
Flags: needinfo?(dbaron)
Resolution: --- → FIXED

The bounty committee believes this is a good testcase for a long-known issue, which was fixed in other independently reported bugs.

Depends on: 1506842
Flags: sec-bounty? → sec-bounty-
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.