Update: Check out the comments! Supposedly it’s patched but I tried it again and it worked. I probably caught them in the middle of a fix, so it’ll probably be fixed soon, maybe by the time you read this. OK, it’s patched, for-real now!
The post is still worth reading if you’re interested in CSRF vulnerabilities, though. A friend of mine read it, with no previous knowledge and then found an even better vulnerability in an even bigger app than this in what seemed like minutes. I’m totally jealous.
The quick and dirty:
By viewing a page on a completely unrelated site while logged into Twitter, you can be forced to post something to your Twitter that you did not intend to post.
If you have a valid login session with Twitter going right now, click here to make yourself post an inspirational message about me. Nevermind the certificate: the same can be done with a good cert.
This still works, but I’m going to go ahead and post about it for a few reasons:
- It’s not easy to figure out where you’re supposed to submit or email security concerns to Twitter. I submitted something to http://twitter.com/help/, but I don’t even know if anyone read it.
- Regarding (1), if you search Google for: twitter “security contact” right now, it turns out that I’m the first hit.
- If someone manages to trick you into posting something to your twitter, it’s bad, but it’s not exactly the end of the world.
- It’s fairly easy for you, as a Twitter user, to avoid it.
- It’s a good, simple, real-world example of CSRF and might get you thinking about how handle this in your own applications.
One good way to protect against CSRF attacks is to have an unpredictable, generated token as a hidden value in each form presented to the user, and to check that token when the form is submitted. If the page that is submitting a request to the other site can’t guess what the token is, then the request fails. If you look at the Twitter interface’s update form, it looks like they implement this kind of protection:
<form action="/account/update_send_via" id="send_via_form"
method="post" onsubmit="new Ajax.Request('/account/update_send_via',
parameters:Form.serialize(this) + '&authenticity_token='
return false;"><div style="margin:0;padding:0"><input
type="radio" value="im" />
<label for="current_user_send_via_im">im</label><br />
I actually didn’t even try to CSRF the above. There’s an “authenticity_token” input, which looks to be the token I mentioned above. It’s all very complicated looking, and I didn’t feel like bothering with it when I knew what I really ought to be looking at: the mobile interface.
Twitter’s mobile interface, which you can switch to at the bottom of your Twitter page, has a much simpler form for updating:
<form action="/status/update" method="post">
<input type="hidden" name="source" value="mobile" />
<input type="text" name="status" id="status"
maxlength="140" class="i" value=""/>
<input type="submit" value="update" class="b"/>
Much easier. I whipped up this test HTML and put it on my web space:
<input type='hidden' name='source' value='mobile'>
<input type='hidden' name='status' value="I truly believe
@McGrewSecurity to be the nicest guy I've ever known. http://tinyurl.com/3fjvn5">
Then, I navigated to it, and was surprised to see…
Bad news and good news for the attacker. Bad news: it checks the referrer. Good news: it comes right out and tells us what we need to do. There are a few situations where HTTP_REFERER is suppressed. The easiest way we can make this happen is by hosting the code at an https:// URL.
If you have a valid login session with Twitter going right now, click here to allow a page to post a message about me to your Twitter. For the example to work, you’ll need to accept the certificate, however there the same trick will work if the site has a properly signed cert. There are other situations where HTTP_REFERER is not passed: local files and FTP URLs, for example.
If you want to avoid falling prey to this until it is patched, it’s pretty simple: use a third-party Twitter client and leave yourself logged out of the web-based interface unless you need it at the moment. If you’re a serious Twitter user, you’re probably already doing this.