IFRAME sandbox attribute is blocking AJAX calls

18,834

The reason it affects Ajax is because Ajax is governed by the Same Origin Policy rules, and when you sandbox it you're effectively telling the browser to treat the iframe contents as if it were from a different origin. Quoting the same article:

  • Unique origin treatment. All content is treated under a unique origin. The content is not able to traverse the DOM or read cookie information.

This means that even content coming from the same domain is treated with the cross-domain policy, as each IFRAME content will be viewed as a unique origin.

Embedded content is only permitted to display information. No other actions can be done inside the IFRAME that could compromise the hosting website or take advantage of the users’ trust.

In other words, if you omit the allow-same-origin in the sandbox attribute, it will treat the sandboxed page as belonging to a different domain (in fact, it will treat as having a null origin). Since it doesn't make sense to make Ajax requests to null, sandboxed pages can not make Ajax calls at all (if making them to localhost were allowed they would be indistinguishable from the calls from the parent page, defeating the purpose of sandboxing).

Additional info

If you try to make an Ajax call to a different domain, it will obviously fail:

<script src="http://code.jquery.com/jquery.min.js"></script>
<script>
    console.log(location.host);
    $.post('https://google.com/',{},function() { });
</script>

However, how it will fail will depend on the sandbox attribute used. If you embed the page above in an iframe with allow-same-origin it will print this to the console:

localhost
XMLHttpRequest cannot load https://google.com/. Origin http://localhost is not allowed by Access-Control-Allow-Origin.

...and if you embed it without allow-same-origin:

localhost
XMLHttpRequest cannot load https://google.com/. Cannot make any requests from null.

Note that, while both reported location.host as localhost, one considered the origin to be http://localhost while the other considered it to be null (showing the same error message you experienced in your example).

Reasoning

Why is it so important to block Ajax calls from sandboxed contents from the same domain? As explained in the article:

It kind of makes sense that content on the same domain should be safe. The risk here primarily stems from user-generated content that is re-hosted in the IFRAME.

Let's make up an example: suppose Facebook decides to allow users post little HTML5 animations in their pages. It stores them in its own servers and, when displaying, sandboxes them as allow-scripts only (because scripts are needed for the animations to work) but leave everything else denied (in particular allow-same-origin, since you don't want user code messing up with the parent page). What would happen if Ajax calls weren't also blocked by default?

Mallory creates an "animation" that consists of:

  1. Performing an Ajax call to Facebook, using its API (say, Open Graph); the server will happily accept the call, since for all it knows the request came from a page with https://facebook.com as origin.

  2. Create a URI pointing to her own server, with the returned data as query strings, and set it as the src of a picture in the sandboxed page.

When Alice visits Mallory profile, and sees the animation, the script above runs:

  1. The Ajax call runs in Alice's browser, while Alice is logged on; since the server does not know where the call comes from (main page or embedded page) it will do whatever it's asked to - including retrieving personal info.

  2. When the img element is created with Mallory's URI, the browser will attempt to load the "image" normally, since images are exempt from the Same Origin Policy.

  3. Since the URI has Alice's private info in the query string, Mallory's server can just save it and return whatever image it wants. Now Mallory has Alice's personal info, and Alice suspects nothing.

Share:
18,834
vtortola
Author by

vtortola

Updated on June 05, 2022

Comments

  • vtortola
    vtortola almost 2 years

    I have an application (http://localhost/MyApp), where some of the parts are rendered through IFRAMES. These iframed parts has no business with the rest of the application's DOM, so I applied the sandbox attribute.

    The IFRAME is declared like this:

    <iframe src="/MyApp/en/html/action?id=1" sandbox="allow-forms allow-scripts" seamless="seamless"></iframe>
    

    The iframed page has a button that makes a AJAX call to the same web application, but then rather than a HTTP GET, the browser issues a HTTP OPTIONS that appears as Cancelled, and an error happens:

    XMLHttpRequest cannot load http://localhost/MyApp/en/data/action?id=1. Cannot make any requests from null.
    Ajax State 0 Error: HTTP 0 
    

    If I add the allow-same-origin to the sandbox attribute, it works.As far as I read here, it was not supposed to affect AJAX calls.

    Why is this happening? Is considering the path /MyApp/en/html/action as origin of the whole IFRAME and blocking the request to previous levels?

    Cheers.

  • vtortola
    vtortola over 11 years
    So you mean that because parent frame is using host A, even if the child frame is using also host A as origin, it will be unable of perform AJAX to their own origin because the parent frame is using it? That sounds like a wacky implementation, the browser should know which calls is doing each iframe.
  • mgibsonbr
    mgibsonbr over 11 years
    The browser does, but what about the server? If the server did not explicitly grant same origin privileges to the sandboxed page, how do you know that it's expecting to receive Ajax calls from it? What if your server is a cajoling server, and the code running in the iframe comes from an untrusted third-party? The domain they're running on will be the same, but that doesn't mean the third-party code should have the capability to make Ajax calls to the server (potentially leaking sensitive info). IMHO that's the correct design, security-wise.
  • vtortola
    vtortola over 11 years
    The server should not care where the request is coming from. Actually who is blocking the request is the browser not the server. Sorry, it is that this issue looks a little bit strange to me.
  • mgibsonbr
    mgibsonbr over 11 years
    Please check my updated answer. The whole point of sandboxing is to deny everything that wasn't explicitly allowed. Since the browser doesn't know what the server cares and what not, it should err on the safe side, and assume Ajax calls (and well as other same origin privileges) are not ok. If you wanna accept Ajax from the sandboxed content, but not let it mess up with the parent page, I'm afraid you'll need another strategy, like postMessage or CORS.
  • vtortola
    vtortola over 11 years
    But... Did you notice that in my scenario both the page and the iframe are pointing to the same host and application?
  • mgibsonbr
    mgibsonbr about 11 years
    Look, I totally agree with you that confounding "access to parent page" and "ajax to the same domain" was a a bad idea. And disallowing sandboxed code to do any ajax was a terrible idea (that could be useful in many cases, with CORS). But I'm not a security expert, so I don't know what the designers behind iframe sandboxing had in mind. Trust is a hairy business.. You say "These iframed parts has no business with the rest of the application's DOM", but what does that mean? That they're untrusted in that regard? What makes them trusted with ajax but not with the parent page's DOM?
  • Jeff
    Jeff over 10 years
    I would like to point out, that adding the allow-same-origin tag to the sandbox attribute actually DOES NOT make a foreign origin to be handled as the same origin. It only makes the browser to handle this iframe as a normal iframe would be handled by the same origin policy. So if you apply this attribute to a foreign domain page, it would still not be able to access the DOM of your main page. Only if you apply it to a url on the same domain (or any other domain allowed by domain policy headers), the page would be able to access your DOM or make AJAX requests to your domain.