Iframes. Every time I hear about them it reminds me of the good old days when websites were collections of static pages and internet speeds were measured in kilobits per second. These things called framesets and frames were used to split up a webpage into separate sub-documents—a menu bar frame, a side bar frame, a footer frame—that could each reload on its own so that pages wouldn’t have to be reloaded entirely with every click of a hyperlink. You had the choice of either building your HTML page out of frames or a single body tag—never both. As a compromise, inline frames (iframes) were introduced as a way to embed frames in an HTML document just like any other element, without being forced to use a frameset. These days, with AJAX and WebSockets providing all the interactivity and partial page refresh behavior we could ever need (for now), framesets and frames have long become unnecessary and don’t even exist in HTML5. Yet, iframes are still around, and every indication shows they’re here to stay. In fact, they are the easiest and safest way to embed content from other sites into your page. Of course, as with many web technologies, there is a right way and a wrong way to use iframes, so I’d like to go over how to securely embed other sites’ contents with iframes, and how to prevent others from attacking your site by embedding your content as an iframe.

Why iframes

Everywhere you look, you see embedded YouTube videos, tweets, like buttons, and of course, the Tinfoil Security badge.

All of these things are embedded via iframes, or at least should be. Browsers keep the context of the iframe and its parent document totally separate by default. Neither the parent document nor the iframe document has access to each other’s DOM, CSS styles, or JavaScript functions if they’re not from the same domain. If the iframe document overrides a String.prototype function, for example, the parent document won’t see this override. If a parent document tries to style its iframe’s contents with something like the following, nothing will happen.

Also, neither the iframe nor its parent can access the other’s cookies or local storage. The browser effectively treats them like separate tabs. If, alternatively, you add a ‘+1’ or ‘like’ button on your page by pasting some third-party JavaScript directly on your page, that separation is no longer there. Surprisingly, Google only offers the “embed our JavaScript in your site” option for its +1 button, and a “you’re on your own” policy if you want to embed it as a hosted iframe, which you would have to host.

Any third-party code that you add directly to your site without the protection of iframes would have all of the same access as your own code. Iframes are the way to go.

Sounds good, so what could go wrong?

If you embed an iframe from your own domain, the browser does not provide any protections against it. Presumably, this is safe since it is your own domain and you likely trust (or wrote) this code. If the iframe comes from a different domain, a browser’s cross-domain policy would kick in, preventing the iframe from accessing cookies, local storage, or the DOM from its embedding document. Even so, cross-domain iframes still have the ability to trigger alerts, run plugins (malicious or otherwise), autoplay videos, and present submittable forms in an attempt to phish users’ information. If all you want the iframe to be able to do is present a social network button, there should be a mechanism to prevent it from doing much else. Thankfully, the ability to restrict iframes is supported by IE 10, Firefox, Chrome, and Safari. It’s called the sandbox attribute.

Just adding the sandbox attribute is enough to severely lock down an iframe.

With this attribute set, the document inside the iframe cannot do any of the following:

Run any JavaScript, even if it would only affect contents of the iframe.

Change the parent’s URL

Open pop-ups, new windows, or new tabs

Submit forms

Run plug-ins

Use pointer lock

Read cookies or local storage from the parent, even if it’s from the same origin

This is a good starting point for securing an iframe. Then, as you determine what things it needs to perform its normal functions, you can enable just those features. The sandbox attribute gives us this capability, by allowing you to specify a space-separated list of permissions, with one or more of the following choices.

allow-same-origin- allows the iframe to access cookies and local storage from the parent, as if it came from the same domain.

allow-top-navigation – allows the iframe to navigate the parent to a different URL.

allow-forms – allows form submission

allow-scripts – allows JavaScript execution

allow-popups – allows the iframe to open new windows or tabs

allow-pointer-lock – allows pointer lock

In the case of the Tinfoil Security badge, the iframe contains a single image that links to a page. This page opens in a new tab or window and shows that your site is verified. The iframe doesn’t need to perform any additional task, so it shouldn’t be allowed to do anything except open pop-ups.

Be careful about giving the iframe too much power. If you sandbox the iframe but give it the permissions allow-same-origin and allow-scripts , the iframe can now run JavaScript and access your DOM, and it could easily use this power to remove its own sandbox attribute and reload itself with no restrictions.

It gets worse

By default, an embedded iframe has an 2px inset border around it. If you don’t like it, you may have been tempted to use a new HTML5 iframe attribute called seamless to get rid of it. (It’s so new, only the latest version of Safari and Chrome support it as of this writing.)

A few top Google search results for “iframes without borders” suggest using this hot new iframe attribute as the way to make iframes look, well, seamless. Unfortunately, this attribute does way, way more than remove borders. A seamless iframe has access to all of the following from the parent document:

CSS styles

JavaScript functions and objects

The whole DOM

Ability to change the parent’s URL. (In fact, all links in the iframe navigate the parent frame by default.)

The iframe gets access to all of these things even if it is cross-origin. In essence, a seamless iframe acts as if it’s just part of the parent document, and it is granted the same level of trust and power. Sure, the borders are removed, but so is any security the iframe provided in the first place. If all you want is for your iframe to look seamless—without any borders or scrollbars—there is a way to do that without handing over complete control of your website to a third-party. Just use CSS.

<iframe src = " http://example.com " allowTransparency = " true " scrolling = " no " frameborder = " 0 " style = " border : none ; overflow : hidden ; " > </iframe>

Only use the seamless attribute if you trust the iframe’s content as if it were your own.

You’ve been framed

Your site could be at risk even if you don’t include third-party content. If you don’t protect against it, other sites could use you as an iframe. Since the parent site embedding the iframe gets to control the look and feel of the element, they could use a technique called clickjacking to steal your users’ information. Potentially, a malicious site could load your site as a full-page iframe and make it seem like they’re actually visiting your site, say your login page. In front of this iframe they will overlay an invisible form, with a text field in front of both your site’s username and password field. When an unsuspecting user tries to log in to your site, they’ll actually be filling out this malicious form and sending their login credentials to the malicious site.

The best thing you can do to protect yourself is to prevent your site from being embedded as an iframe. These days every browser since IE8 supports the X-FRAME-OPTIONS response header. If your site sends this header with its value set to DENY when the page is requested, browsers will refuse to allow the page to be rendered in an iframe. The other supported option is SAMEORIGIN , which allows the page to be embedded in an iframe only if the parent document is from the same domain, which presumably is also your code.

Alternative solutions I’ve seen involve using JavaScript in your site to detect if you’re being embedded in an iframe and doing something to prevent it, like redirecting the parent.

if (window.top !== window.self) window.top.location.replace(window.self.location.href);

I wouldn’t recommend this, since the attackers have all of the same tools you do, and they can use sandboxing to prevent you from running JavaScript or redirecting the window.

Stay Safe

I hope this has convinced you to switch over to using iframes whenever adding untrusted content to your site. If set up properly, they provide the best protective separation between your content and third-party content, especially on new browsers that support sandboxing. Iframes are here to stay. Have any questions or other ideas about embedding third-party content? Sound off in the Hacker News comments.