- Play safely in sandboxed IFrames
- Originally by Mike West
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: Gesj – yean
- Proofread by: TUARAN, Rachelcdev
How can I use the sandbox attribute of the inline frame element IFrames to improve security?
To build an experience-rich site, it’s almost inevitable to embed components and content that you have no real control over. Third-party components can increase user engagement and play an important role in the overall user experience, and sometimes user-defined content is even more important than site-native content. We can’t give up on using third-party components and user-defined content, but both methods increase the risk to the site. Every component you embed — advertising, social media — can contain malicious attacks:
Content Security Policies (CSP) allow trusted sources in whitelists to embed scripts and other content to reduce the risk posed by third-party components and user-defined content. This is a correct and important step, but it is important to note that most CSP directives are either allowed or not allowed. Sometimes I run into this paradox: I’m not sure if the source of the content is trustworthy, but I want to embed it in the browser.
The minimum privilege
Essentially, we’re looking for a mechanism that gives them the minimal permissions they need to do their job. Access to window.open is disabled if the component does not need to pop up a new window. If the component does not need Flash, turn off support for the Flash plug-in. When we follow the principle of least privilege and turn off permissions that are not directly related to functionality, we are safe.
The IFrame element is the first step in building a good framework for such a solution. When untrusted components are loaded in an IFrame, it provides separation between the application and the loaded content: the embedded content cannot access the PAGE’s DOM or locally stored data, nor can it draw anywhere on the page; Its scope is limited to the embedded element. However, this separation is not really reliable. Embedded pages still have a lot of annoying or malicious behavior: autoplaying videos, redundant plug-ins, and pop-ups, and that’s just the tip of the iceberg.
The sandbox attribute of the iframe element reinforces the constraints on the framework’s content. We can instruct the browser to load the content of a particular framework in a low-privilege environment, allowing only a subset of the desired functionality to be done.
First remove, then verify
Twitter’s “Tweet” button is a good example of how it can be sandboxed into a site more securely. Twitter allows you to embed buttons via iframe with the following code:
<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
Copy the code
To determine what we can lock down, we need to examine what the button does. The loaded HTML executes a series of JavaScript code from Twitter’s servers and, when clicked, pops up a window with a tweet interface. At the same time, in order to bind the tweet to the correct account, the interface needs to access Twitter cookies and finally submit a form containing the tweet content.
Sandboxes work on a whitelist basis. We first remove all possible permissions, then re-enable the individual permissions by adding specific flags to the sandbox configuration. For the Twitter component, we decided to enable JavaScript, pop-ups, form submission, and access to twitter.com cookies. We can add a sandbox property to the iframe as follows:
<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
Copy the code
As mentioned above, we have provided the framework with the functionality it needs, and the browser will deny access to any permissions not granted by the Sandbox attribute.
Granularity control of permissions
You can see some possible sandbox flags in the example above, but now let’s dig into the inner workings of attributes in more detail.
Given an iframe with an empty sandbox attribute (), the framework file will be completely sandboxed with the following restrictions:
- JavaScript will not execute in the framework document. This includes not only JavaScript loaded via script tags, but also inline event handlers and JavaScript: URLs. This also means that the contents of the NoScript tag are displayed as if the user had disabled the script themselves.
- The framework document is loaded into a unique source, which means that all origin checks will fail; Unique sources do not match other sources, or even themselves. Among other effects, this means that documents cannot access data from other sources’ cookies or any other storage mechanism (DOM storage, indexed databases, etc.).
- Frame documents cannot create new Windows or dialogs (for example, through
window.open
或target="_blank"
). - The form cannot be submitted.
- Plug-ins will not be loaded.
- A framework document can navigate only itself, not its top-level parent. Set up the
window.top.location
Will throw an exception, click withtarget="_top"
The link will not work. - Auto-triggered features (auto-focus form elements, auto-play video, etc.) will be blocked.
- Unable to get Pointer Lock.
- Included in the framework documentation
iframes
Ignore theseamless
Properties.
The risk of files being loaded into a fully sandboxed IFrame is minimal, but it is demanding. Of course, there’s not much value in doing this: for some static content, you can use a full sandbox, but for the most part, you can go easy on it.
With the exception of plug-ins, these restrictions can be removed by adding a flag to the value of the Sandbox property. A sandboxed file cannot run a plugin because the plugin source code is not sandboxed, otherwise it is the same:
- Allow-forms Allows forms to be submitted.
allow-popups
Allow (window.open()
.showModalDialog()
.target="_blank"
Etc.) pop up.- Allow-pointer-lock Allows the mouse pointer to be locked.
allow-same-origin
Allows documents to maintain source; Loaded fromhttps://example.com/
Will retain access to the source data.- Allow-scripts allows JavaScript to execute and also allows features to trigger automatically (since implementing these features through JavaScript is trivial).
- Allow-top-navigation allows documents to jump out of frames through the navigation top-level window.
From this, we can see exactly why we used a specific set of sandbox flags in the Twitter example above:
- Allow-scripts is required because JavaScript needs to be executed to handle user interaction when the page loads into the framework.
- Allow-popups is required because the button needs to popup the form in a new window.
- Allow-forms is required because the form content needs to be submitted.
- Allow-same-origin is required otherwise Twitter.com cookies will not be accessible and users will not be able to log in to the publish form.
It is important to note that the sandbox tag that applies to frames also applies to any window or frame that is created in the sandbox. This means that we must add Allow-Forms to the framework’s sandbox, even though the form only exists in the frame’s pop-up window.
With the Sandbox property, the component only gets the permissions it needs, and features like plug-ins, top navigation, and Pointer Lock are still disabled. We reduce the risk of embedding components with no adverse impact.
The separation of permissions
In order to run untrusted code in a low-privilege environment, it is beneficial to sandbox third-party content. But what about your own code? You certainly believe in yourself, so why worry about sandboxed?
Ask yourself: if your code doesn’t need plug-ins, why give it plug-in permission? At best, you won’t use this permission; But at worst, for an attacker, it gives the attacker an opportunity. Everyone’s code is buggy, and almost every application is vulnerable to various attacks. Sandboxed code means that even if an attacker succeeds in breaking your application, they won’t be given full access to your application. They can only do what the application can do. That’s bad, but it’s not bad enough.
You can further reduce risk by splitting your application into logical blocks and sandboxing each block with as few privileges as possible. This approach is common in source code: Chrome, for example, decomposes itself into a high-privilege browser process that accesses local hard drives and network connections; And a number of low-privilege rendering processes responsible for parsing untrusted content. A low-privileged renderer does not need to touch the disk, and the browser provides all the information needed to render the page. Even if the hacker found a way to break the renderer, it wouldn’t get anywhere, because all high-access access would have to be routed through the browser’s process. Attackers have to find vulnerabilities in different parts of the system before they can wreak havoc, which greatly reduces the risk.
Safe sandbox:eval()
methods
This model can be successfully applied to the Web through sandbox and the postMessage API. Parts of an application can be placed in sandboxed IFrames, and parent documents can communicate between parts by publishing messages and listening for responses.
Evalbox can parse strings into JavaScript code. And this is what you’ve been waiting for. Of course, this is a fairly dangerous application, because allowing arbitrary JavaScript execution means that any data provided by the source is available. We reduce the risk by ensuring that the code executes in a sandbox, starting with the contents of the framework and finishing the code from the inside out:
<! -- frame.html -->
<html>
<head>
<title>Evalbox's Frame</title>
<script>
window.addEventListener('message'.function (e) {
var mainWindow = e.source;
var result = ' ';
try {
result = eval(e.data);
} catch (e) {
result = 'eval() threw an exception.';
}
mainWindow.postMessage(result, event.origin);
});
</script>
</head>
</html>
Copy the code
Inside the framework, we have a minimal document that listens for messages from its parent object via message events connected to the Window object. This event is emitted whenever the parent executes a postMessage on the contents of the iframe, and then executes the string that the parent wants us to execute.
In the handler, use the parent window to get the source property of the event. Once we finish the work, we use it to send the results. The data is then passed to eval() to do the heavy lifting. This call is included in the try block because operations prohibited in sandboxed iframe often generate DOM exceptions; We’ll catch them and report a friendly error message. Finally, we publish the results back to the parent window. The whole process is simple.
Superclasses are equally simple. We’ll create a small UI layer with a textarea and an executable button, and we’ll embed the iframe in frame.html with a sandbox that only allows script execution:
<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
id='sandboxed'
src='frame.html'></iframe>
Copy the code
Now let’s write a method to implement it. First, listen for responses from iframe and alert() using window.addeventListener. A real application should be concise and unambiguous:
window.addEventListener('message'.function (e) {
// If the sandbox value of iframe is not allow-same-origin,
// The source of the embedded content is treated as a null rather than a valid source.
// This means you have to be careful about the data you receive through the API.
// In this case, you need to check the source and validate the input.
var frame = document.getElementById('sandboxed');
if (e.origin === "null" && e.source === frame.contentWindow)
alert('Result: ' + e.data);
});
Copy the code
Next, we mount a click event on the button. When the user clicks, we grab the contents of the textarea and pass it to the iframe to execute:
function evaluate() {
var frame = document.getElementById('sandboxed');
var code = document.getElementById('code').value;
// Note that we are sending information to "*", not to a specific source.
// If the sandbox value of iframe is not allow-same-origin,
// The message sent has no target source, which can lead to some unusual attacks.
// So you must validate your output!
frame.contentWindow.postMessage(code, The '*');
}
document.getElementById('safe').addEventListener('click', evaluate);
Copy the code
Isn’t that easy? We wrote a simple evaluation API while ensuring that the evaluated code does not access sensitive information, such as cookies or DOM storage. Also, the code being evaluated can’t load plug-ins, pop up new Windows, or do some other bad thing.
Check out your own code here:
Evalbox Demo
index.html
frame.html
You can deconstruct your program into single-purpose components and then perform evaluation operations on your own code. As the code above shows, each component can be encapsulated in a simple messaging API. A high-privilege parent window can act as a controller and scheduler, sending messages to each module that has minimal privileges to get the job done. We just listen for the results and make sure that each module only gets the information it needs.
Note, however, that you need to be very careful with frame content from the same source as the parent element. If a page on https://example.com/ builds another page of the same origin (the sandbox values for this page include allow-same-Origin and Allow-scripts), then the permissions of the built page can be reached up to the parent page, And remove the sandbox property completely.
How do you use the sandbox
Additionally, the sandbox is very powerful and reduces the risk that an attacker will subtly exploit a vulnerability in your code. By separating individual applications into a set of sandbox services, each sandbox service is responsible for a small piece of its own functionality. In doing so, attackers destroy not only specific builds, but also their controllers. This is difficult, especially because the scope of the controller can be greatly reduced. If you ask the browser to do the rest of the work for you, you can review this code more often.
This is not to say that sandboxes are the complete solution to Internet security problems. It provides defense in depth, but don’t rely on browser support unless you can control the user’s client (ideally if you do control a user client like an enterprise environment). Now the sandbox is another layer of protection that can be used to reinforce the defense, but it’s not a defense that can be relied on completely. Still, I recommend using it.
Learn more
- “Separation of permissions in HTML5 Apps” is an interesting article about the design of a small framework and its application in three HTML5 apps.
- Sandboxes can be more flexible when they combine two new IFrame properties: SRcdoc and Seamless. The former allows you to populate the frame with content without the overhead of HTTP requests, and the latter allows styles to be applied to the frame content. Both are currently poorly supported in browsers (only Chrome and WebKit Nightlies). But it’s an interesting combination for the future. For example, you can sandbox an article with the following code:
<iframe sandbox seamless
srcdoc="This is a user's comment! It can't execute script! Hooray for safety!
"></iframe>
Copy the code
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.