Simultaneously sandbox and add JS/HTML to iFrame

3.8k views Asked by At

I am trying to create an iframe that has JS fire inside of it but does not have access to the parent document. The goal is to have a simple implementation and not include any special libraries. I found that the HTML5 Sandbox attribute is a viable solution; however, it is proving to be quite difficult to simultaneously sandbox an iframe and put JS inside of it.

I have tried:

  1. Adding JS to iframe and then sandboxing it from the parent document. This does not work because JS in iframe fires before the iframe sandbox is applied
  2. Creating iframe and setting HTML with srcdoc attribute (ex. <iframe sandbox="allow-scripts" srcdoc="<body><script src='sample1.js'></script><script src='sample2.js'></script></body>"></iframe>) This does not work in IE according to here
  3. Creating iframe and setting HTML with src attribute (ex. <iframe sandbox="allow-scripts" src="javascript:&quot;<html><body><script src='sample1.js'></script><script src='sample2.js'></script></body></html>&quot;"></iframe) This does not work in Firefox 38.0.5 (Mac OS X Yosemite) but works in Chrome 43 & Safari 8.0.4 ..."not working in Firefox" means that it will indeed allow sample2.js to add <span></span> to the iframe DOM, but it does not set the innerText of the span; therefore, it does not work as intended.
  4. Finally, I have just tried to dynamically create an iframe via JS. After adding the iframe to the DOM (without sandbox attribute), I write the HTML to it, which contains my scripts that I want to be sandboxed in the iframe. In the iframe head, I have a script to sandbox itself. Unfortunately, this does not seem to work. (Code below)

I created a JSFiddle, but it doesn't give the proper behavior because it is in a nested iframe... I set up a simple page and am testing on my localhost. Here is the code I am using for testing:

<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>

<!-- Case #2 ~ Does not work in IE -->
<iframe srcdoc="<body><script src='sample1.js'></script><script src='sample2.js'></script></body>" sandbox="allow-scripts">
</iframe>

<!-- Case #3 ~ Does not work in Firefox -->
<iframe src="javascript:&quot;<html><body><script src='sample1.js'></script><script src='sample2.js'></script></body></html>&quot;" sandbox="allow-scripts">
</iframe>

<!-- Case #4 ~ Sandbox maybe not applied? -->
<script>
var i   = document.createElement('iframe');
i.src   = "about:blank";
i.id    = "bryce_iframe";
document.body.appendChild(i);
// iframe added to page

var j = document.getElementById('bryce_iframe').contentDocument;
j.open();

var js = "<html><head>" +
         "<scr"+"ipt>window.top.document.getElementById('bryce_iframe').sandbox = 'allow-scripts';</scr"+"ipt>" +
         "</head><body>" +
         "<p>Hi</p>" +
         "<scr"+"ipt>" +
         "var p = document.createElement('p');" + 
         "p.innerText = 'should throw error'; console.log(p);" +
         "window.top.document.body.appendChild(p);" +
         "</scr"+"ipt>" +
         "</body></html>";
// "window.top.document.body.appendChild(p)" should throw an error because of sandbox

j.write(js);

j.close();
</script>

</body>
</html>

My sample1.js file looks like this:

var s = document.createElement('span');
s.innerText = "hi";
window.top.document.body.appendChild(s);

My sample2.js file looks like this:

var txt = "this doesn't try to break out of iframe";
var s = document.createElement('span');
s.innerText = txt;
document.onload=document.body.appendChild(s);

I've been looking for helpful resources about this issue for weeks. It would be amazing to have another set of eyes looking into it with me.

I look forward to collaborating with whoever reaches out. Thank you in advance.

3

There are 3 answers

6
SilverlightFox On

Just wondering why you are adding the sandbox attribute in your inner script in example #4?:

"<scr"+"ipt>window.top.document.getElementById('bryce_iframe').sandbox = 'allow-scripts';</scr"+"ipt>" +

The sandbox attribute should be added to the parent code:

<!-- Case #4 ~ Sandbox maybe not applied? -->
<script>
var i   = document.createElement('iframe');
i.src   = "about:blank";
i.id    = "bryce_iframe";
i['sandbox'] = 'allow-scripts';
document.body.appendChild(i);

This will prevent parent access.

0
RonJRH On

I'm faced with the same problem. I don't have a good solution either but have 2 more options that don't work on Edge:

Option 1: Blob URL

var blob = new Blob([html_string], {type: 'text/html'});
var url = URL.createObjectURL(blob);
var i   = document.createElement('iframe');
i.src = url;
i.sandbox = "allow-scripts";

Option 2: Data URL

var url = "data:text/html,<html_string>";
var i = document.createElement('iframe');
i.src = url;
i.sandbox = "allow-scripts";
0
Sean On

If you look at the HTML specification of the sandbox attribute, it warns:

These flags only take effect when the nested browsing context of the iframe element is navigated.

This implies that dynamically adding/removing/updating the sandbox attribute of an iframe after it loads will not have any effect.

The proper way to do this is to host a different HTML file on a separate domain that loads the third party JS. You can reference that HTML file in the src attribute of your iframe, and you can include the sandbox attribute statically, rather than trying to dynamically add it in JavaScript. Make sure you include allow-scripts as a value for the sandbox attribute so that the child page is able to load the JS.

The reason you should host the other HTML file on a separate domain is for the browsers that don't support the sandbox attribute. Browser support is documented here