mnot’s blog

Design depends largely on constraints.” — Charles Eames

Thursday, 20 April 2006

DOM vs. Web

Back at the W3C Technical Plenary, I argued that Working Groups need to concentrate on making more Web-friendly specifications. Here’s an example of one such lapse causing security problems on today’s Web.

Safety in HTTP

HTTP methods have a property known as safety; RFC2616 describes it in section 9.1.1;

Implementors should be aware that the software represents the user in their interactions over the Internet, and should be careful to allow the user to be aware of any actions they might take which may have an unexpected significance to themselves or others.

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered “safe”. This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.

This philosophy can be seen throughout HTTP; for example, if you redirect an unsafe method (say, POST), the client has to get permission from the user before following it automatically.

Safety isn’t just a nice idea; it’s one of the foundations of the Web, making things like search spiders, caches and GET as the default method for URIs possible. Probably the most widely-known example of the consequences of messing with safety in HTTP is the pain seen by Web sites that did unsafe things with GET once Google Web Accelerator came around.

HTML (and other Web formats) also take advantage of safety; tags like IMG, IFRAME, and SCRIPT only use GET. That way, a page can’t trick your browser into doing something unsafe without you knowing about it. In fact, the only way to get a POST out of a browser is with an HTML form. Almost.

The form.submit() attack

Unfortunately, that design didn’t carry through into the DOM Level 2 HTML spec, which included the HTMLFormElement.submit() method without any security considerations. Once bound to JavaScript as form.submit(), this allows any site to POST any form to any other site.

For example, imagine if this was included in a random Web page;

<html>
   <body>
      <p>Nothing to see here...</p>
      <form action="http://some.other.site/your/bankaccount" name="f">
         <input type="hidden" name="amount" value="$1,000,000">
         <input type="hidden" name="destination" value="mnot's secret bank account">
      </form>
      <script type="text/javascript">f.submit();</script>
   </body>
</html>

Since the request will be sent with any cookies or HTTP authentication you have for some.other.site, if you’re logged into your bank account, I get your money (presuming the form is well-crafted enough).

This hole is real, and present in all browsers today. Web sites that want to protect against this have to take special measures to assure that the request actually came from the user, rather than from another site.

Oops.

“No New Vulnerabilities”

This is by no means a new problem; it’s been widely known for some time [pdf]. However, instead of actually trying to fix it and preserve the benefits of safety, the attitude now seems to be that we should use it as an excuse to intentionally standardise a bunch of other holes, because there are “no new vulnerabilities”.

For example, a proposal to allow XmlHttpRequest to send a POST to other sites specifies that there should be an “access check” to determine if the contents of the response should be made available to the browser. This will catch some classes of XSS attacks, but as far as this hole is concerned, the unsafe cat is already out of the bag by then*.

I don’t think this approach to security is well-thought-out. Unfortunately, in today’s never-mind-the-long-term-let’s-just-implement atmosphere (a similar problem is seen in JSONRequest), it’s too easy to ship things that will have lasting negative impact, and then standardise them because they’re “current practice”. I’m reminded of the way that Cookies were pushed onto the world.

A Modest Proposal

HTMLFormElement.submit() is clearly at odds with the Web architecture.

The right thing to do is to require user interaction before sending any unsafe method. For scripted requests, you could say that a request to the same site is exempt, perhaps as a configurable option; there could also be a whitelist of allowed sites for automatic POSTs.

In this way, you’re getting permission from the user before the request itself, in a way that’s conceptually similar to the way that most browsers store credentials now (you know, like “save this password.”).

Now, I’m not claiming that this approach will solve all of the security problems to do with cross-site scripting; clearly there’s more than safety involved. However, having a consistent, simple model for interaction on the Web is a whole lot better than piling hack upon hack (which is often a clever solution to a problem in a product, but a ticking time bomb in a longer-lived standard).

Of course, this would take some time to roll out, but at the current pace of innovation (especially with IE7 on the horizon), it would take perhaps 3-5 years before sites could start to rely on this behaviour. The alternative future is one where there are multiple holes and sites have to take truly convoluted measures to overcome them, or just give up on using forms altogether.

I’ve talked to a few people about this issue and raised it to the www-dom mailing list, even though the Working Group doesn’t exist any more; there’s discussion of doing some more work in DOM, so there is an opportunity to fix it. In the meantime, there’s nothing stopping implementations from fixing it; when reading the HTML and DOM specs together, the right thing to do is clear. Having this interaction noted in the DOM specification itself would just make it harder to use as an excuse for other holes.

* Ironically, there are other people in the Web APIs Working Group arguing that XmlHttpRequest should be able to change the Referer header — even though using it is one of the ways to mitigate this attack.


Filed under: Protocol Design Standards Web

24 Comments

Ian Davis said:

Hear hear. Especially to the part about requiring user interaction for POSTs and other unsafe operations. It's worth pointing directly to http://www.w3.org/2001/tag/doc/whenToUseGet.html too (it's indirectly linked via WEBARCH)

Thursday, April 20 2006 at 1:36 PM +10:00

Stefan Eissing said:

It is one thing to tackle the client side, the other would be to document good practise to protect against it on the server side. The owasp web page did not hand out a good recipe it seems.

I could think of some things like tying the value of a hidden variable to the internal user session, but security is a tricky thing to verify. Where would one look for a good solid solution to this?

Thursday, April 20 2006 at 2:50 PM +10:00

Mark Nottingham said:

Stefan,

Exactly. Make no mistake, having unsafe methods require user interaction will still leave Web sites that accept them open to certain kinds of attacks; it's just that the mere act of navigating to a site can't automatically do something on my behalf.

WRT server-side mitigation, see the paper I referenced (http://www.net-security.org/dl/articles/AdvancedXSS.pdf); it mentions two methods (using the Referer header and .crumbs).

Thursday, April 20 2006 at 8:10 PM +10:00

Aristotle Pagaltzis said:

Do you think it would be feasible, if user interaction is required on all unsafe actions, to lift the same-host restriction on XMLHttpRequest? There are tons of cool things that could be done using XHR to stitch information from other sites into the one you're viewing (see Book Burro and legions of other Greasemonkey scripts), which are currently not implementable due to said restriction.

Friday, April 21 2006 at 11:53 AM +10:00

Mark Nottingham said:

No.

There are many other client-side attacks that can take place; e.g., if there's any way to examine the Cookie or Authorization headers, I could steal credentials from you.

There are folks considering how to allow cross-site XHR in a limited fashion; the question (and a large part of the motivation for this post) is how it should be limited.

Friday, April 21 2006 at 1:00 PM +10:00

Aapo Laitinen said:

I agree, form.submit() is a problem. However, I also doubt a confirmation would work. How can a non-technical user determine whether an attempt to do a cross-site submit is legimate or not?

Related entries in Bugzilla:

https://bugzilla.mozilla.org/show_bug.cgi?id=246519
https://bugzilla.mozilla.org/show_bug.cgi?id=246476

Maybe the practical solution is to make Referer mandatory for POST:s regardless of the method they were generated. Since most browsers already send the Referer, the only breakage would be limited to people with overly zealous firewalls. And these people are probably capable of making sense of confirmations. This wouldn't help unmaintained legacy applications, but it wouldn't break them either, and would also help in the situations where the user clicked something malicious ("Cool stuff here!").

Sunday, April 23 2006 at 3:22 AM +10:00

Mark Nottingham said:

Aapo,

Thanks for the bug refs!

There are a lot of different aspects to this issue, and requiring user interaction definately doesn't solve all of the problems.

It does, however, catch situations where the user otherwise wouldn't even *know* that a form had been submitted; with form.submit() and some suitable hiding techniques, I don't even have a chance to be suspicious about a form.

Another possible situation would be to bring form.submit() inside of the Same-Origin Policy and not allow it cross-site at all for POST requests.

WRT referer, that's what a lot of sites do, AIUI. The problem is that there's a multitude of resources on the Web that accept POST without taking elaborate steps to assure that they're legitimate. If the browsers took some small steps to restrict form.submit(), it would offer them some limited protection.

Overall, though, my real concern is the fact that this oversight is being used as an excuse to open yet more holes. That's a terrible approach to security.

Sunday, April 23 2006 at 9:04 AM +10:00

Yaron Y. Goland said:

(Not speaking for my employer)

Sigh... my head hurts. Cross domain postings is too useful to lose. It's tempting to say "just stick a warning on everything" but in reality the user will hit the "don't show me this warning again" button without even reading the text. Besides, in a scripted context the warning dialog will just seem bizarre as its likely to appear out of nowhere (from the user's perspective).

It's tempting to argue that we should use referer but referer is largely evil and there are very good reasons why decent security proxies strip it out.

Perhaps we should introduce a new HTTP header that does NOT include the source page URL (too many security nasties there, besides you can just try to include referer and see if it gets through) but acts as a flag saying "this POST did NOT come from a page in your domain"?

Sunday, April 23 2006 at 3:24 PM +10:00

Mark Nottingham said:

A couple of the proposals on the table include a "Domain" or "Referer-Root" which just contains everything up to the path; e.g.,

Referer-Root: http://www.mnot.net/

I don't think this is really the answer, because it puts the onus on the server. Every site that I interact with on the net has to properly protect against this, or I'm at risk.

Sure, the same argument could be made about host security, but the best practices -- and professionalism -- of system administrators make that much more acheivable. In contrast, Web scripters continually struggle with XSS attacks (and this one isn't even on the radar for most people after more than three years).

A better solution would be an enforced site-wide access control check, similar to P3P policy or robots.txt.

Monday, April 24 2006 at 7:28 AM +10:00

Yaron Y. Goland said:

My guess is that the motivation for script based POSTs is to send more data than can be safely encoded into a URL on a GET. So perhaps what we need is a SAFEPOST method which allows a site to fire off data but makes it clear that the user isn't held responsible? This would be the only form of method that could be fired off by a script (even to the same domain as the one the script is hosted in). This all sounds more than a little bit familiar so I suspect this argument has gone the rounds in the HTTP community before but I don't remember the outcome.

Tuesday, April 25 2006 at 11:41 PM +10:00

Yaron Y. Goland said:

My guess is that the motivation for script based POSTs is to send more data than can be safely encoded into a URL on a GET. So perhaps what we need is a SAFEPOST method which allows a site to fire off data but makes it clear that the user isn't held responsible? This would be the only form of method that could be fired off by a script (even to the same domain as the one the script is hosted in). This all sounds more than a little bit familiar so I suspect this argument has gone the rounds in the HTTP community before but I don't remember the outcome.

Tuesday, April 25 2006 at 11:42 PM +10:00

Pete Dapkus said:

SAFEPOST doesn't really cover the bases. It's not just the result of the post thats a concern; it's also the data being sent.

This is a hard problem -- who do I trust to receive/send data to my page when I'm not even sure I trust my page (ie maybe this is just code someone injected in my page).

Mark's suggestion of a robot.txt file seems like an interesting idea. Or, maybe pass a list of allowed domains via a header in the HTTP response? You want a source of metadata about the page that the browser can reasonably trust.

Wednesday, April 26 2006 at 12:52 PM +10:00

Yaron Y. Goland said:

How can the issue be the data being sent? A script can always leak any data it is given. It can do it the official way using a form with method="GET" or the unofficial way by emulating the method="GET functionality in script. No headers or configurations can reasonably stop this. Therefore doesn't this leave "user responsibility" as the only viable issue?

Sunday, April 30 2006 at 10:02 AM +10:00

Pete Dapkus said:

The browser may be maintaining state (e.g. in the page or in cookies) that you would rather it didn't share (e.g. your session ID).

Monday, May 1 2006 at 8:21 PM +10:00

Yaron Y. Goland said:

Yeah the cookie issue is, I think, relatively easy to get around and given all the POST hacks everyone should be doing it. Which is, require that the post value include in it a value taken from the cookie. If the cookie value in the post data and the cookie value in the actual HTTP header don't match then you know the source of the post wasn't running in your domain.

Thursday, May 4 2006 at 10:13 PM +10:00

Julian Reschke said:

IMHO, there's a related problem with XHR even if the source of the script and the target URI are on the same host.

The problem is once you have a server that supports unsafe methods with known-ahead behaviour such as PUT or DELETE, and multiple authors changing documents on the same server, user A can easily trick another user B to visit an HTML page that automatically submits DELETE or PUT requests against URIs identifying resources that user B can modify.

Note that this is not a WebDAV problem (although it was raised during RFC2518bis last call), and also not an HTTP problem per se (because that's why RFC2616 warns user agents to invoke unsafe methods without making sure this is what the user wants).

So it seems to me that user agents must not allow XHR requests with unsafe methods, unless as a result of user interaction (such as in an onload event), right?

Sunday, May 7 2006 at 5:10 AM +10:00

Julian Reschke said:

IMHO, there's a related problem with XHR even if the source of the script and the target URI are on the same host.

The problem is once you have a server that supports unsafe methods with known-ahead behaviour such as PUT or DELETE, and multiple authors changing documents on the same server, user A can easily trick another user B to visit an HTML page that automatically submits DELETE or PUT requests against URIs identifying resources that user B can modify.

Note that this is not a WebDAV problem (although it was raised during RFC2518bis last call), and also not an HTTP problem per se (because that's why RFC2616 warns user agents to invoke unsafe methods without making sure this is what the user wants).

So it seems to me that user agents must not allow XHR requests with unsafe methods, unless as a result of user interaction (such as in an onload event), right?

Sunday, May 7 2006 at 5:11 AM +10:00

Yaron Y. Goland said:

Julian, to make sure I understand your scenario are you saying:

1. User A goes to Website X and using Website X's script creates a file
2. User B goes to Website X and Website X's script delete user A's file

I don't think this scenario can ever be stopped. If a website isn't going to behave well then it won't behave well. If we shut down DELETE the website will just issue a delete equivalent comment via get. After all, it's X's script against X's back end system.

If your scenario however is:

1. User A goes to Website X and using Website X's script creates a file
2. User B goes to Website Y and using Website Y's script deletes user A's file on Website X

That isn't possible with XHR because of the cross domain restrictions.

So I'm not sure I understand your scenario.

Tuesday, May 9 2006 at 8:56 AM +10:00

Julian Reschke said:

Yaron,

it's the first scenario: User A places an HTML page on website X, sends a link to User B, who opens that page in a web browser. HTML page contains Javascript that executes "onload", and sends a DELETE request to a resource owned by User B, which B's credentials (he/she authenticated before).

IMHO that's a bug in the user agent; it executed an unsafe method without explicit user interaction.

I don't think the comparison with GET is accurate; if sending a GET causes data to be lost, that's a server bug.

Best regards, Julian

Tuesday, May 9 2006 at 1:36 PM +10:00

Yaron Y. Goland said:

Julian, what I believe you are describing is what is known as a fixation attack (thanks to David Ross for pointing this out to me). The scenario is as follows:

Step 1 - User logs into to site X and gets the right cookies for site X authentication.

Step 2 - User goes to evil.com who sends a 'blind' form post to site X saying delete a file. Even though evil.com can't see the response to its 'blind' form post it doesn't matter, site X will still do what the form post requested because it contains the right authentication.

There are a couple of ways to deal with this situation:

Canaries - These are values that are generated on the fly and sent down with pages that contain forms. In the previous scenario evil.com wouldn't know what canary site X was using at that instant for that user and so its form post wouldn't contain the right value and would therefore be rejected. The upside about canaries is that they work with any arbitrary form post. The downside is that they require some server side work to generate and monitor the canary values. Hotmail, I believe, uses canaries.

Cookies - A variant on canaries is to use cookies where the page copies a value from a cookie into the form before sending it up. Since the browser security model only allows pages from the same domain to see that domain's cookie you know the page had to be from your domain. But this only works if the cookie header value isn't easily guessable so in practice it's really just canaries.

XMLHTTP - Using XMLHTTP it's possible to add HTTP headers so just throw in a header of any sort. Since forms can't add headers you know the request came from XMLHTTP and because of XMLHTTP's very strict domain security model you know the page that sent the request had to come from your site.

The key is that by making sure the page came from website X it is possible for website X to know what it presented to the user to get the user to send the POST and therefore it knows if the POST is safe or not.

Thursday, May 11 2006 at 7:36 AM +10:00

Mark Nottingham said:

There’s one more possibility — keep any eye on the referer.

Thursday, May 11 2006 at 9:47 AM +10:00

Julian Reschke said:

Yaron: the whole point here is that only one server is involved. Both users read/write resources on that server, both may even be successfully authenticated. User A creates an HTML page containing a "DELETE-one-of-B's-resources-onload" and gets user B to open that page. The user agent will send that DELETE request with B's credentials without any further interaction.

Mark: how would a referer check help here? WebDAV requests usually come in without Refererer. Is there any guarantee that an XHR request will actually contain an unmodified Referer header (must check the draft...).

Thursday, May 11 2006 at 10:55 AM +10:00

Yaron Y. Goland said:

Referer - Referer is evil and any reasonable person should have it blocked so sites shouldn't rely on it.

But more to the point I think that Julian's scenario is a lot more evil than I had realized. Julian is the scenario as follows?:

Step 1 - User A goes to document repository website X and posts a new document D.
Step 2 - User B goes to document repository website X and posts a new document E.
Step 3 - User B fools User A into going to document repository website X and viewing document E.
Step 4 - Document E loads on User A's browser, it turns out that document E contains script and that script immediately issues a delete command for document D. Because User A is logged in to document repository website X the request goes up with User A's credentials and the document is deleted.

If this is indeed your attack then it is a simple variant on an incredibly common attack whose name I forget so I'll make up a name and call it the 'code insertion attack'. I know of the attack from its use on page comments. Someone would post a comment to the page containing HTML that did something naughty. Users would then view the comment and end up running the naughty HTML. This is why blogs and others enforce strict rules both about the kind of text they will accept as well as the circumstances under which they display the text.

What you are describing is, I believe, a variant on that attack.

Thankfully this attack is relatively easy to stop. What is needed is both canaries (calculated on a per page basis) with a split domain. So for example one would view page E at http://view.x.com/pages/E. But one would edit page E (using a form or WebDAV) at http://edit.x.com/pages/E. The result being that XMLHTTP couldn't be used by page E because the domain wouldn't match and a fixation attack wouldn't work because there would be no way for page E to find out what page specific canary had been randomly generated for page D (the target page) since XMLHTTP isn't available to download the edit form for page D.

But I recognize that this is all hackery. What we have here is a broken security model. A real security model would be to allow pages to describe who they trust and who they should be allowed to talk to. So one could imagine that website X would be able to include a disabling statement on all of its pages saying "This page may not issues form requests or XMLHTTP requests anywhere but itself." That would allow the page to host potentially hostile content but minimize the damage without creating a crappy user experience.

Or we could just introduce POST and SAFEPOST and go back to user warnings for all POSTS. :)

Either would work for this example.

Friday, May 12 2006 at 11:35 PM +10:00

Mark Nottingham said:

A related reference:
http://en.wikipedia.org/wiki/Cross-site_request_forgery

Wednesday, January 3 2007 at 10:30 AM +10:00

Creative Commons