Faking votes on Hacker News

Hacker News is a site where users vote on links. When someone clicks on an up or down vote, their browser sends a GET request that looks like this:

  GET /vote?by=joesmith&dir=up&for=8765309

A user can only vote if the by parameter matches their Hacker News login cookie. So for a third party to fake votes, they would need to get a visitor's login name.

That turns out to be very easy to do. I made a form that looks like this:

Clicking "Generate" goes to a page that embeds an image with a HREF of the upvote link, creating a fake vote. People didn't hesitate to submit their info in the hopes of getting a nifty graphic.

After submitting it to Hacker News, and making it upvote itself when triggered, the link got over 50 (fake) upvotes in the 10 minutes before it was killed. People were confused.

Until this is fixed, I don't recommend giving out your Hacker News username.

update Here's the Hacker News discussion of this issue. "We deliberately don't put that much effort into security, because this is a community based on trust, not a bank."

update Some people have asked how this could be prevented. One technique is to compute the cryptographic hash of the parameters concatenated with some secret value:

  • secret is "fribble dibble"
  • hash is sha1("fribble dibble?by=joesmith&dir=up&for=8675309") update: don't use this; use hmac
  • link is "/vote?by=joesmith&dir=up&for=8675309&hash=f3a8c2241"
  • when processing requests to /vote, recompute the hash independently from the request parameters and compare to what was submitted; if it matches, accept the action.

It would be difficult for a third party to compute the hash and make it part of an unwitting request.

Using POST alone wouldn't help. It's pretty trivial to put a form in the HTML and submit it with JavaScript.

Comments

(Anonymous)

?

What do you mean giving out the username? Correct me if I'm wrong, but anybody can find that information out by simply viewing a thread! Pick any username there.

(Anonymous)

Re: ?

The request has to come from the user who is logged in with the 'known' username. CSRF

Re: ?

If a Hacker News user visits my site, I have no way to know their specific username unless they tell me. There's no way to use just a random, arbitrary username in the unwitting GET request - you have to use that specific user's username in the URL for the GET.

(Anonymous)

Why didn't you send them a warning first?

Not that you have to, but it would show some respect.

Not that amazingly cool hack or discovery, btw.

Re: Why didn't you send them a warning first?

Submitting to hacker news was the only thing that worked last time.
That's why you use POST for anything modifying the database. I've seen this type of attack way too many times.
I don't think that would help in this case.
If Hacker News used POST for that, as they should, it would had been harder for you to generate the bogus requests. You'd have to inject markup, or better yet, JavaScript to forge the POST via XMLHTTPRequest.
Not really harder, no. JavaScript makes it pretty easy.
I'm sorry I wasn't complete. You use POST data and form protection like random hashes to validate POST requests. Ruby on Rails does this quite well.

For example:
<input name="authenticity_token" type="hidden" value="b05a38708f61b1657e2acb9e2a103fe635686b11" />

This effectively stops POST request spoofing. There are some techniques mentioned on wikipedia you might want to look at. http://en.wikipedia.org/wiki/Cross-site_request_forgery
That would work for GET as well, but yeah, signing the request is one way to avoid the problem.
If you use POST then the authenticity token can be simply an arbitrary value matched to the user’s session id, rather than a cryptographic signature of the parameters you want to protect.

For example, say we’re making a form for a survey. A user is logged in and requests the survey page. We generate a random number, and store it both in a hidden authenticity_token in the form, and a database that keeps the state for our user sessions (or, in HN’s case, probably a continuation).

Now there’s no way for a third party site to get both the user’s cookie and the authenticity_token. If you forge a request, you won’t have access to the authenticity_token that match’s the user’s session. If you requests\ the page directly, then you’ll get your own authenticity_token, and it won’t match anyone’s else’s cookie.

Since there’s no way for other sites to get both the value in this hidden field and the user’s cookie, we can now authenticate that the POST does not come from a third party site. This is what Rails does automatically.This is important because, without JavaScript, there’s no way we could hash or sign the user’s survey input itself.

Great post.
Some time ago I saw a technique that avoided any session-specific tracking; it was something like this:

<input type=hidden name=sig value='nonce:timestamp:digest'>

e.g. '317190525.75664:1237320613:94218caae9904e93a3d7'

The digest is calculated from concatenating the secret, nonce, and timestamp together. Then, without consulting any server-side state except the secret, the server can determine if the request is kosher and within a certain expiration time.

(I wish I could find the original article, it was somewhere on IBM DeveloperWorks IIRC.)

(Anonymous)

Don't understand why everyone's on your case for the hack, it shows an insecure system quite well, with a very simple social engineering trick. It's good you brought it out in the open because it guarantees a quick fix. Besides, it's not that hard to fix this one anyway.

Any of the people above who are blasting you for the explanation/going public obviously don't understand how these companies work. You HAVE to bring it in the open or nobody realizes how insecure these things are.

Anyways, long story short: Great post.

(Anonymous)

Yeah, except this is a small forum and not a company, and if brought to the attention of someone like pg before full disclosure, probably would have been fixed without causing so much useless discussion all over the place - although if one were trying to educate people on CSRF..nah, it's still stupid.

Besides, full disclosure without attempting to first contact the vendor is irresponsible. It's something that should be done only AFTER disclosing it to the vendor in question and preferably only when they sit on it forever and do nothing about it (and yes, "forever" is subjective, but at least a day or two, honestly...). That is the ultimate issue at hand here - nobody is debating the usefulness of full disclosure, only what didn't happen (but should have) before that.

(Anonymous)

hmm, wish i could edit my comment ;)

Just saw the backstory/old, old XSS story about HN, so I suppose there was a history of ignoring issues...but still, doesn't change much else..
You just like being mean to Paul Graham, don't you? :)

April 2015

S M T W T F S
   1234
567891011
12131415161718
19202122232425
2627282930  
Powered by LiveJournal.com