Yes. I made an unbelievably stupid mistake in the code that generates forms with labelled fields. It was basically functional programming taken a little too far: I generated the same form whether the fields were editable or not, and then later if there were no editable fields I just omitted the submit button. So anyone looking at the source of one of these pages could find a fnid that would work to modify the object displayed on it. (There's still a fnid, but it no longer does anything.)
Actually on closer examination the bug was subtler than that. I did check whether the incoming request was allowed to modify each field it was trying to modify. But the code that decided whether a field was modifiable was using a four variable list to destructure on a five value description of the field. This caused the variable determining whether a field was modifiable to be bound instead to the value saying whether or not it should be displayed. Which meant every field that was displayed was modifiable.
In a strongly typed language one would normally want to reify the number in the type system whenever possible, i.e. the use of (a,b,c,d,e) rather than [a,b,c,d,e] in Haskell (that's a 5-tuple rather than a 5 element list).
I think that people used to dynamic programming languages would normally pick the list rather than the tuple simply because it at first appears easier. I think Dijkstra had something to say on that particular subject :)
Agreed. But it sounded like, in this case, you were using a list to represent that which would have been represented by a record in a statically typed language.
I'm imagining you'd have some kind of Field record type, with isDisplayable and isModifyable fields. Instead of destructuring as a list (or tuple), you would explicitly look for isDisplayable and isModifyable.
But, of course, you could still make the same mistake in a statically typed language if you chose not to set up the data structure this way.
That's actually pretty interesting to me. I didn't notice that when I was looking at the Hacker News code. Perhaps Arc shouldn't allow a list to be destructured except with a list of the same length? Allowing differing lengths seems likely to cause subtle bugs like this.
I generally tried to err on the side of flexibility, and it seemed more flexible not to require the lengths to match. I could imagine cases where you might want to use patterns both longer and shorter than the lists they were matched against. On the other hand, I'm not sure I've ever ended up taking advantage of this.
It sounds like you're treating list destructuring like CL's mvbind. While it's subjective of course, I'm more comfortable with the runtime doing what I mean when I control the number of values (modulo `(apply values ...)').
I'm not comfortable with destructuring values "flexibly" when I don't control the data coming in.
That would suggest, then, that the comment points and submission points are also controllable within some form, since they were tampered with also. Is that the case? (I'm not asking because I don't believe you - I'm just wondering how those numeric fields were altered.)
With hindsight, do you still feel fnids are a good way to go? They present an obvious bottleneck in that the server has to remember many closures for a period of time as opposed to having each client remember their share. I know you drastically reduced the use of fnids some time ago. I wonder if the remainder are a vestige that's OK to live with because life's too short, or there's fundamentally a strong argument for them I'm missing.
Sometimes these closures have more stuff in them than you could conveniently put in a url. For example, they might refer to another closure describing what to do next. When you try to do something that requires you to be logged in, for example, you're given the login page, with a fnid referring to a closure describing what you were in the middle of doing. That's why after logging in you're able to continue doing whatever you were doing.
Agreed. How about making the fnid a signed cache of often used environment variables(instead of a random string), which could also be looked up via a more time consuming method if needed. Depends really on your traffic profile how much data could reasonably and cost-effectively be cached, but it could be a very sizable portion saved by retrofitting the current method.
Escaping is needed when you pass strings to a program that parses and interprets them. That's not part of HN's architecture AFAICT. There shouldn't be any opportunity to introduce a vulnerability similar to SQL injection.
Whoever did this, please post a Tell HN or otherwise an article on how you did it. I'm sure others are curious (and would make a good starting point for patching Arc)
From the source, it looks like there was a vulnerability in which the fnid (I'm guessing a string that authenticates a user to edit an item?) was searched for on PG's profile page (using the regex /<input type=hidden name="fnid" value="([^"]+)">/. Then a POST request was made on the standard profile saving resource news.ycombinator.com/x, with the fnid which authenticated the user's permission to edit the page, along with the about text, as parameters.
Edit: PG says the fnid just points to a closure on the system. See above. Which means... all you needed was a randomly generated fnid, and that's all that you needed to edit anyone's page. Apparently?
Clever, or just poor authentication design. But that's only one half of the exploit. How were the points done? I'm going to rule out millions of accounts created.
The points were editable through similar means. Pages like http://news.ycombinator.com/edit?id=519433 contained a form that allowed all of a story's details -- including title, url, and score -- to be edited. Just like with profiles, the story editing forms didn't have fields for non-admins, but just like with profiles, it was possible to submit a request with the fnid from the form regardless of admin status.
I'm going to disagree. Chances are it was XSS or - shudder - simple injection. It was a basic vulnerability and it got fixed. We don't need to know the details - though I'd like if whoever did this got banned for a while for being an obnoxious jerk.
(When you think about it, this was pretty simplistic. All anybody did was edit text fields on the site. That hints it was injection.
Dodged a bullet there I'd say. At least your "hacker" seems to be a reasonable guy. Seems like he could have done a lot more damage. I hope your backups are up to date and verified recoverable; a more malicious intruder might not be so kind.
While I appreciate and admire the sentiment that this is a "community of trust", security still must be taken seriously. There are plenty of guys out there with the ability to pull such tricks; they may not care about trust, and the website is accessible to anyone, good or bad.