Same-Origin Policy Part 2: Server-Provided Policies?

justin February 17th, 2007

Last week I presented an overview of the same-origin policy and different attacks against it. This week I’m going to take a cue from Robert Seacord and propose a solution to the problem. It’s probably not the ideal solution, but maybe it will start some discussion and lead to something more complete. I had also intended on exploring some related proposals first, but I’ve decided I’ll present my own idea before I start a debate on other suggestions.

The Same-Origin Problem

The same-origin policy enforces segmentation between scripts executing in the context of different origins (domain, port, and protocol). This segmentation is intended to prevent a malicious site’s document from interacting with a trusted site. The problem is that the same-origin policy is a one-size-fits-all approach that includes a number of holes—intentional and accidental—dating back to the earliest browser implementations. Simply put, the current policy doesn’t adequately support the differing communication and security needs of modern web applications.

The Site Should Dictate The Terms

The relationship between a browser and multiple sites creates the potential for transitive trusts. In this situation the browser effectively becomes a gateway between multiple sites. Right now we have the same-origin policy functioning as the rule-set for this gateway relationship. I would roughly equate that to having a firewall pre-configured, with no options for changing the rule-set.

It only makes sense that a site should be able to dictate the policy for how it handles communications initiated by other sites. This places the decision in the hands of the developers and maintainers, and allows them to selectively increase or reduce the security as needed. For example, a site could enforce additional measures against cross-site reference forgeries by refusing POST requests from other sites. The same site could also loosen restrictions to allow mash-ups originating from other sites.

The Browser Is The Gatekeeper

If you’re sold on the notion of a server provided policy, the next thing to consider is how to enforce it. Normally security issues are enforced at the server, but the fact is that the server lacks the context to make most of the necessary origin decisions. There’s no requirement to pass the Referer header, so there’s no way to reliably determine the domain intiating the request. There’s also no good way for the server to identify if a request is initiated by the user, through script, or through a background request like an image or link tag.

After a little consideration it becomes pretty apparent that any rules need to be enforced at the browser. This idea may seem awkward given the mantra of server-side security, but it fits in context. Attacks against the same-origin policy are directed at the user. These attacks violate the rules imposed by the browser as a gateway between sites. While the rules for access can be provided by the site, they can really only be enforced at the browser.

Delivering The Policy

The policy must be enforced at the browser, so that means establishing a protocol for delivering it. Fortunately, there’s already something of a precedent for delivering information like this via a specially named file in the root of the site (eg. crossdomain.xml and robots.txt). This approach has the added advantage that it allows an easy fallback for backwards compatibility. The browser would attempt to retrieve the policy file on its initial request (per browsing session) to an origin. If the browser failed to retrieve the file it simply reverts to the standard same-origin policy.

There are still a few problems to consider with this delivery method, however. In particular, attackers may attempt to deny access to the policy file, forcing the browser to fall back to the original same-origin policy. For example, cache poisoning attacks could be used to inject a false policy file into the cache. There’s always the possibility of requiring that the policy file be signed by an SSL certificate (or delivered over SSL, which is a bit weaker protection). This seems reasonable given that SSL certificates can be obtained for as little as $20 a year for a single domain. The problem is that I’m not sure that the additional signing requirement is that useful. Depending on the effectiveness of the cache poisoning exploit, the attacker may be able to craft a series of pages that still perform an attack without violating the server-provided policy.

I’m also not completely how to deliver policy with respect to authentication. With this form of delivery, the policy file needs to be the first thing retrieved from the site. However, you can make a very legitimate argument that you would want a different file for pre-authentication and post-authentication. After all, there’s no reason to give out information on site structure to an unauthenticated user. I think the only reasonable fix is to add an HTTP header or meta tag update directive that forces the browser to refresh it’s policy file. This breaks the simplicity a bit, but I think it’s needed. This would of course require some additional server-side logic to determine which version of a file to deliver to a logged in user, but it wouldn’t be necessary for the majority of sites.

Request Rules

I’ve tried to isolate only the necessary fields for the request rules, so that they aren’t overly complex. I think that individual requests can be covered with the following fields: request direction (inbound, outbound, or both), requesting origin (domain, port, and protocol), request method, request initiator (user, image, script, link, etc.), and path. The following is an example table of rules (the * is a wildcard character):

Domain Authorization Rules
# Access Direction Requesting Domain Port Proto Method Initiator Destination Path
1 allow all *.mydomain.com 80 | 443 * * * *
2 deny inbound * 80 | 443 * * * /includes/* | /settings/*
3 allow outbound *.imageservice.com 80 HTTP GET | HEAD image *
4 allow all * * * GET | HEAD user *
allow inbound * 80 | 443 * GET | POST script /rpc/*
6 deny all * * * * * *

Assume that the above rules are are traversed as a typical ACL-style lookup where the first hit is a success. So, the rules in order are as follows:

  1. Allow all requests to and from any sub-domain of mydomain.com
  2. Deny all inbound requests from any domain for paths under /includes/ and /settings/ (pages that modify settings or just shouldn’t be used as an entrypoint)
  3. Allow outbound image includes via GET and HEAD methods to *.imageservice.com
  4. Allow user-initiated inbound and outbound GET and HEAD requests to and from anywhere (let the user click in and out of the site at anytime)
  5. Allow inbound script-initiated requests for paths under /rpc/ (to support cross-domain AJAX applications)
  6. Deny all rule at the end

The above rules could go a long way toward mitigating a number of different attacks. Cross-site request forgeries would typically target the /settings/ path, which is prevented by rule #2. Reflected cross-site scripting attacks become a lot more difficult because the rules drastically reduce the inbound attack surface and restrict non-user-initiated outbound requests to a set of known servers. The real advantage is that the policy-based approach allows fine-grained segmentation of the site.

Rule Granularity

I intentionally kept a few details out of the above rules table. I excluded the source path because it’s not very useful for inter-site requests. It’s safer to just assume that an external site has complete control over the source path. Intra-site (same-origin) requests are a different story; including the source path allows the site to enforce page flow and further mitigate certain types of attack. Right now I’m leaning toward the argument that such things should be handled in the application logic, not pushed out to a policy. I’m not very attached to that argument, however, and would be comfortable including the source path in the rules. It could certainly make for a much finer-grained policy.

Path Matching

I opted for simple path wildcards in lieu of more complex regular expression matching. The rational behind this is that it’s best to encourage a logical directory hierarchy that enforces segmentation between application functions. This makes the policy simpler and developers are less likely to  make mistakes or need to modify policy. I’m also hesitant about using regular expressions because they’re more harder to understand and I’ve witnessed far too many mistakes when developers try to use them for security purposes. That stated, I can definitely see the potential uses for a full regular expression implementation for path matching, and it may be worth the added complexity.

Information Disclosure

One drawback to this type of rule based approach is that a poorly structured site could end up distributing a policy that provides a lot of information on the site’s structure. This is something that already occurs with some sitemap and robots.txt files. For example, rule #2 above denies external requests for the include directory. Any web application built with security in mind simply doesn’t put include-only files under the web root. (Sorry Wordpress.) So, the intent of excluding /include/ is to prevent things like deep linking. However, an application that puts its include files in the web root is potentially exposing that information by explicitly listing the path in its policy file.

Global Settings

So far I’ve identified global settings for handling parent domain traversals and including additional policy files only. I consider parent domain traversal to be one of the most dangerous things included in the current same-origin policy, and it’s rarely necessary. Many common uses for parent domain traversals can be handled with server-side redirects to a single domain name. I suggest that the presence of a domain policy file would implicitly disable parent domain traversal, requiring a directive to explicitly enable it if needed.

Another important point to consider is that web applications are often delivered as a directory tree. Ideally, an application should include its own policy file, but that presents an issue if the application isn’t installed in the web root. The easiest solution is for the root policy file to provide paths to any additional policy files it needs to include. The browser can then request any include files and apply their policy to the appropriate sub-path. Working this out in the most portable manner might require a few additional tweaks, such as a wildcard character representing the destination domain (such as the @). There’s also the question of whether global settings (such as parent domain traversal) should apply to the relative path only or should be overridden.

Conclusion

As I stated at the beginning, I expect this isn’t the ideal solution, and I welcome anyone to provide feedback. However, I think something along these lines could go a long way to mitigating many of the vulnerabilities discussed last week, and provide a safer way of supporting the new crop of AJAX applications. One of the major advantages of this approach is that it doesn’t require a major change to the current web infrastructure. It can be added on to existing browsers as an extension, and phased in to sites over time. Once critical mass is reached, the old same origin policy could be deprecated, and eventually removed.

6 Responses to “Same-Origin Policy Part 2: Server-Provided Policies?”

  1. ntpon 18 Feb 2007 at 12:46 am

    NOTE: I apologize for this long post in advance. Here goes.

    1) Both content-restrictions and httpOnly could be extensions to browsers. I don’t see your technique as strong over content-restrictions. httpOnly, however, has problems.

    2) SSL Certs do NOT cost $20 (or even NameCheap’s $16). WWW.StartCom.ORG has certs available for free. The catch? The certificate for their CA is only default in Mozilla products (including Firefox) and KDE’s Konquerer. CAcert.ORG may also get included in Mozilla’s products and it is also free. It is my opinion that IE7 should immediate include both as well… and developers should start signing applications, containers, objects, and everything that they possibly can in order to proactively prevent many current and future web application security threats.

    3) I came up with a protection solution here:http://sla.ckers.org/forum/read.php?13,6918,7005

    This solution only currently works in Firefox, and it can be implemented either as:

    a) How you use your web browser

    b) How browsers are implemented

    This is probably the strongest protection methodology currently available for users. I use it myself.

    I’m using it right now. Firefox is open in this window to a totally locked down install (I’m even using Public Fox to lock down my settings with a password). A few other Firefox instances are open, but they have totally different profiles and they are running as different processes. This Firefox had a deny any policy on its NoScript plugin when started. I had to add access for this site to post this, so I temporarily allowed scripts only to http://taossa.com. I’m going to close this window and end this instance / Firefox process when I am done, clearing all private data.

    4) While my method does seem to prevent many current and future threats, especially threats that exploit trust relationships in the same-origin policy - I can see two other protections getting into browsers quickly and easily.

    a) You already mentioned one:http://myappsecurity.blogspot.com/2007/01/xss-filter-to-protect-from-xss-attacks.html

    b) RSnake is also working with the .NET-Fw Anti-XSS Microsoft guys to have their own XSS filters put into IE7:http://ha.ckers.org/blog/20070119/microsoft-engineers-are-paying-attention/

    I believe that each has their own immediate problems:

    A) they don’t solve any same-origin policy trust issues directly

    B) they work only as a protection against XSS

    C) same problems as WAF’s below

    5) Should I even mention WAF’s? Do we really want to put mod-security in browsers, Linksys routers, and in front of every web server in the world? The problem with Web application firewalls is two-fold:

    a) They would need to be updated to support new threats

    b) There is always going to be a way to sneak past their filters

    6) Input validation can fix every XSS vulnerability in every application.

  2. justinon 18 Feb 2007 at 3:45 am

    ntp,

    First off, no one has to apologize for long posts here. We’re not exactly known for our brevity (ahem… John). Anyway, you added a lot of interesting points, so I figured I’d respond in order.

    1) I’m still trying to decide on content restrictions. My first take is that they’re integrated into the page and they feel a bit too complex, which worries me a bit, but I can’t frame it as more than that yet. More concretely, content restrictions don’t prevent CSRF attacks (http://ha.ckers.org/blog/20060626/content-restrictions-and-script-keys/), which would be addressed in my suggested policy. They also don’t provide as fine-grained control over loosening origin restrictions. That said, I see the potential and am still mulling over the concept. In particular, content restrictions seem more appropriate than my suggestion for intra-site activity, so perhaps the right idea is a combination of the two?

    2) It looks like we’re both on the same page with cryptographic validation. Originator validation can be very handy and at this point there’s little reason not to do it.

    3) Your protection solution certainly guarantees same-origin sandboxing, and works with existing browsers. (Oddly enough, I’ve been using MOZ_NO_REMOTE=1 with multiple profiles to make cross-session exploit testing easier.) I’m adding your approach to my list and will be covering it in a future discussion. However, I doubt it’s a long-term solution because it’s just not reasonable for the average user. Also, it doesn’t address the need to allow cross-site communication where appropriate. I was targeting my proposal at a balance between security and functionality. In my experience developers want their jobs easy and most users want it even easier, but I might have missed my mark.

    4) Yeah, I pretty much agree on all points here.

    5) You and I seem to be in the same camp here. I’m really bothered by the “throw a device in front of everything” mentality. TCP/IP firewalls work because the rules are relatively simple, which is possible because they don’t dig into the application layer. I expect WAFs to never work very well because they’re the exact opposite of TCP/IP firewalls; WAFs seem intent on trying to duplicate the application logic in the firewall. That’s a role better served by a programming framework.

    I hope I didn’t come across like I was suggesting embedding a WAF in the browser. I was intentionally trying to keep the ruleset simple, and not try to dig into the application logic. In the end I think it comes down to encouraging a server-side function segmentation and letting the originator provide more fine-grained control over the restrictions enforced by the existing same-origin policy.

    6) You’re totally right that proper input handling can address XSS, and that’s always the right approach. I just figure an extra measure of policy at the browser can help mitigate client-targeted vulnerabilities that slip through the cracks.

  3. fschmidton 16 Oct 2007 at 7:04 pm

    This silly commenting system doesn’t allow edits, so please use this instead of my last post.

    “Worse yet, the rise of AJAX and mash-ups seems to have turned same-origin into something developers are trying to break.”

    I am a programmer, not a security guy. I see same-origin mostly as a huge nuisance to be gotten around. And using a policy file, as suggested here, doesn’t solve the problem because programmers providing widgets to be added to other people’s web pages can’t expect those people to go through the trouble of modifying a policy file.

    For this issue to be resolved, security people have to recognize that once JavaScript is injected into a page, the creator of that JavaScript has full control over that page and all security attempts to restrict that JavaScript are pointless. Any restrictions on what JavaScript can access from within a web page can be gotten around by having that JavaScript go through a server to get access. So limiting XmlHttpRequest with the same-origin policy makes no sense at all because JavaScript can always add a “script” element to the DOM to load another JavaScript file from a server which can itself load whatever XmlHttpRequest would have loaded and then pass it back in a JavaScript file. Similarly, if JavaScript loaded from xyz.com into some page in another domain wants to communicate with a frame loaded from from xyz.com, then it can do so through the server. The fact that one can work around these restrictions means that the security value of these restrictions is zero. But the nuisance value and the performance hit is big enough to make these restrictions a big negative for web development.

    The sensible solution is to allow document.domain to be an unrestricted list of domains. So I should be able to say:

    document.domain += “,xyz.com”;

    And then JavaScript in pages from xyz.com should have access to this page just as other pages in the same origin do. And the same-origin restriction on XmlHttpRequest should simply be dropped. This solution recognizes the fact that it is hopeless to try to restrict JavaScript that has made it into a page. And given this, it makes sense to make things easy for JavaScript and HTML widget programmers.

  4. justinon 17 Oct 2007 at 7:00 am

    fschmidt, See, that’s the tricky thing about security… you have to consider all the consequences of a decision. You’re suggesting that a script be allowed to dynamically add new valid origins. This might seem like a straight-forward solution, but it would result in unpreventable cross-site request forgeries.

    To illustrate, let’s walk through a hypothetical scenario. Assume that I browse to evil.com with the current same origin policy. Now, there are ways that evil.com can interact with other sites, such as including scripts, stylesheets, or images. However, scripts on evil.com are limited in how they can interact with this data; they can’t make HTTP posts; and included stylesheets and scripts must be properly formatted. Also, any script retrieved from another location is executed in the context of the requesting origin, not the serving location. So, it’s possible to get code in and data exfiltrated through URLs, but interaction with other sites is still fairly limited.

    Now let’s consider the same scenario with your suggestion, where scripts on evil.com can add domains as valid origins. For example, a script on evil.com could add myFinancialInstitution.com to document.domain, then script out a funds transfer to some offshore account. Assuming I’m currently logged in to myFinancialInstitution.com, my session cookie would be passed and the transfer would succeed. And speaking of cookies, this scenario would expose all cookies for reading and writing by any arbitrary site. One could imagine a malicious script that enumerates through a list of sensitive domains, checks for active session cookies, then attempts to perform some illicit activity.

    In case the dangers of this proposal are still unclear, I suggest rereading the original same-origin post at http://taossa.com/index.php/2007/02/08/same-origin-policy/ . Really though, I think your response only further emphasizes why a good origin policy needs to separate the code from the policy itself. With the current same-origin policy, a single vulnerability undermines the security of an entire site. By isolating the policy, sites can provide some mitigation against such vulnerabilities.

    Anyway, it’s always good to see developers curious about security, and hopefully you find the information helpful.

  5. fschmidton 18 Oct 2007 at 12:25 am

    “For example, a script on evil.com could add myFinancialInstitution.com to document.domain, then script out a funds transfer to some offshore account.”

    What I suggested was that if evil.com added myFinancialInstitution.com to document.domain, then myFinancialInstitution.com should have access to evil.com, but not the other way, meaning evil.com should not have access to myFinancialInstitution.com . Does this cause any security problem?

    Also, about your server-defined, browser-enforced security policy idea, I see how limiting inbound requests can prevent cross-site request forgery. But limiting outbound requests doesn’t really prevent cross-site scripting because injected JavaScript can simply change links on the page to go to a URL at evil.com with whatever info it wants to smuggle out included in the URL, so that when the user clicks on a link, evil.com gets the info.

  6. justinon 18 Oct 2007 at 5:28 am

    fschmidt,

    I suppose I reversed your suggestion, but the opposite still sounds very suspect to me. I admit that a cursory glance doesn’t reveal such an obvious vulnerability as the reverse case, but your approach does nothing to improve security and would actually introduce the potential for entirely new classes of vulnerabilities.

    From a practical standpoint, my first question is how you’d actually register the cross-origin exception? You can’t include the script directly, because it would execute in the context of the requesting origin. So, I would think that one site would need to have a frame executing the exception script on the other site. That seems like a very circuitous route, and the beginning of an administrative and auditing nightmare.

    Given the current prevalence of trivial web vulnerabilities, consider how much worse things would get if developers could scatter origin exceptions throughout their code. Imagine trying to identify origin bypasses in a moderate-size application when they’re embedded in JavaScript spread out across all the code in a site. I just can’t see how a situation like this would ever be preferable to a policy file that’s easy to identify and audit.

    Also, how long should the exception be valid? This is something that’s easy to specify in a policy file, but how would you handle it in your approach? Would you add a DOM properties or is it just valid for the duration of the browsing session? And how does a site refresh a bad exception when it’s explicitly initiated by the foreign origin? With these policy exceptions spread out it seems like stale and/or overly-permissive exceptions would become a prime target of attack.

    Finally, I’m not comfortable with the all-or-nothing approach of opening one site to requests from another site. It effectively means accepting the entire attack surface of a remote site as your own. In the case of public, general-use widgets, that’s basically the entire Internet.

    Now, on to your question about my origin policy ramblings. The first thing to consider is that not all XSS vulnerabilities are quite so straight-forward. There are often limits on the injected text size, location, and format. (Hence the value of things like the XSS cheat-sheet.) So, adding restrictions on external includes and navigation could often make an exploit impractical.

    More generally, however, the policy I proposed could white-list any requests to other origins, even user initiated clicks. Such a strict approach is probably not practical for all sites, but it would be a good idea for something like a banking application (and I’ve looked at enough of those to consider it a feasible solution in such cases).

Permanent Link | Trackback URI | Comments RSS

Leave a Reply