This repository was archived by the owner on Jan 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 755
Expand file tree
/
Copy pathindex.html
More file actions
391 lines (332 loc) · 22.4 KB
/
index.html
File metadata and controls
391 lines (332 loc) · 22.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
{% extends "tutorial.html" %}
{% block head %}
{% endblock %}
{% block iscompatible %}
return !!('sandbox' in document.createElement('iframe'));
{% endblock %}
{% block html5badge %}
{% endblock %}
{% block content %}
<p>Constructing a rich experience on today’s web almost unavoidably involves
embedding components and content over which you have no real control.
Third-party widgets can drive engagement and play a critical role in the overall
user experience, and user-generated content is sometimes even more important
than a site’s native content. Abstaining from either isn’t really an option, but
both increase the risk that Something Bad™ could happen on your site. Each
widget that you embed – every ad, every social media widget – is a potential
attack vector for those with malicious intent:</p>
<blockquote class="twitter-tweet tw-align-center"><p>Attn: NYTimes.com readers: Do not click pop-up box warning about a virus -- it's an unauthorized ad we are working to eliminate.</p>— The New York Times (@nytimes) <a href="https://twitter.com/nytimes/status/3958547840" data-datetime="2009-09-13T17:54:13+00:00">September 13, 2009</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/">Content Security Policy
(CSP)</a>
can mitigate the risks associated with both of these types of content by giving
you the ability to whitelist specifically trusted sources of script and other
content. This is a major step in the right direction, but it’s worth noting that
the protection that most CSP directives offer is binary: the resource is
allowed, or it isn’t. There are times when it would be useful to say “I’m not
sure I actually <em>trust</em> this source of content, but it’s soooo pretty! Embed it
please, Browser, but don’t let it break my site.”</p>
<h2 id="least-privilege">Least Privilege</h2>
<p>In essence, we’re looking for a mechanism that will allow us to grant content we
embed only the minimum level of capability necessary to do its job. If a widget
doesn’t <em>need</em> to pop up a new window, taking away access to window.open can’t
hurt. If it doesn’t require Flash, turning off plugin support shouldn’t be a
problem. We’re as secure as we can be if we follow the <a href="http://en.wikipedia.org/wiki/Principle_of_least_privilege">principle of least
privilege</a>, and block
each and every feature that isn’t directly relevant to functionality we’d like
to use. The result is that we no longer have to blindly trust that some piece
of embedded content won’t take advantage of privileges it shouldn’t be using. It
simply won’t have access to the functionality in the first place.</p>
<p><code>iframe</code> elements are the first step toward a good framework for such a solution.
Loading some untrusted component in an <code>iframe</code> provides a measure of separation
between your application and the content you’d like to load. The framed content
won’t have access to your page’s DOM, or data you’ve stored locally, nor will it
be able to draw to arbitrary positions on the page; it’s limited in scope to the
frame’s outline. The separation isn’t truly robust, however. The contained page
still has a number of options for annoying or malicious behavior: autoplaying
video, plugins, and popups are the tip of the iceberg.</p>
<p>The <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox"><strong><code>sandbox</code></strong> attribute of the <code>iframe</code> element</a>
gives us just what we need to tighten the restrictions on framed content. We can
instruct the browser to load a specific frame’s content in a low-privilege
environment, allowing only the subset of capabilities necessary to do whatever
work needs doing.</p>
<h3 id="twust-but-verify">Twust, but verify.</h3>
<p>Twitter’s “Tweet” button is a great example of functionality that can be more
safely embedded on your site via a sandbox. Twitter allows you to <a href="https://dev.twitter.com/docs/tweet-button#using-an-iframe">embed the
button via an iframe</a>
with the following code:</p>
<pre class="prettyprint"><code><iframe src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
</code></pre>
<p>To figure out what we can lock down, let’s carefully examine what capabilities
the button requires. The HTML that’s loaded into the frame executes a bit of
JavaScript from Twitter’s servers, and generates a popup populated with a
tweeting interface when clicked. That interface needs access to Twitter’s
cookies in order to tie the tweet to the correct account, and needs the ability
to submit the tweeting form. That’s pretty much it; the frame doesn’t need to
load any plugins, it doesn’t need to navigate the top-level window, or any of a
number of other bits of functionality. Since it doesn’t need those privileges,
let’s remove them by sandboxing the frame’s content.</p>
<p>Sandboxing works on the basis of a whitelist. We begin by removing all
permissions possible, and then turn individual capabilities back on by adding
specific flags to the sandbox’s configuration. For the Twitter widget, we’ve
decided to enable JavaScript, popups, form submission, and twitter.com’s
cookies. We can do so by adding a <code>sandbox</code> attribute to the <code>iframe</code> with the
following value:</p>
<pre class="prettyprint"><code><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>
</code></pre>
<p>That’s it. We’ve given the frame all the capabilities it requires, and the
browser will helpfully deny it access to any of the privileges that we didn’t
explicitly grant it via the <code>sandbox</code> attribute’s value.</p>
<h3 id="granular-control-over-capabilities">Granular Control over Capabilities</h3>
<p>We saw a few of the possible sandboxing flags in the example above, let’s now
dig through the inner workings of the attribute in a little more detail.</p>
<p>Given an iframe with an empty sandbox attribute (<code><iframe sandbox src="...">
</iframe></code>), the framed document will be fully sandboxed, subjecting it
to the following restrictions:</p>
<ul>
<li>JavaScript will not execute in the framed document. This not only includes
JavaScript explicitly loaded via script tags, but also inline event handlers
and javascript: URLs. This also means that content contained in noscript tags
will be displayed, exactly as though the user had disabled script herself.</li>
<li>The framed document is loaded into a unique origin, which means that all
same-origin checks will fail; unique origins match no other origins ever, not
even themselves. Among other impacts, this means that the document has no
access to data stored in any origin’s cookies or any other storage mechanisms
(DOM storage, Indexed DB, etc.).</li>
<li>The framed document cannot create new windows or dialogs (via <code>window.open</code> or
<code>target="_blank"</code>, for instance).</li>
<li>Forms cannot be submitted.</li>
<li>Plugins will not load.</li>
<li>The framed document can only navigate itself, not its top-level parent.
Setting <code>window.top.location</code> will throw an exception, and clicking on link with
<code>target="_top"</code> will have no effect.</li>
<li>Features that trigger automatically (autofocused form elements, autoplaying
videos, etc.) are blocked.</li>
<li>Pointer lock cannot be obtained.</li>
<li>The <code>seamless</code> attribute is ignored on <code>iframes</code> the framed document contains.</li>
</ul>
<p>This is nicely draconian, and a document loaded into a fully sandboxed <code>iframe</code>
poses very little risk indeed. Of course, it also can’t do much of value: you
might be able to get away with a full sandbox for some static content, but most
of the time you’ll want to loosen things up a bit.</p>
<p>With the exception of plugins, each of these restrictions can be lifted by
adding a flag to the sandbox attribute’s value. Sandboxed documents can never
run plugins, as plugins are unsandboxed native code, but everything else is fair
game:</p>
<ul>
<li><strong><code>allow-forms</code></strong> allows form submission.</li>
<li><strong><code>allow-popups</code></strong> allows popups (<code>window.open()</code>, <code>showModalDialog()</code>, <code>target=”_blank”</code>, etc.).</li>
<li><strong><code>allow-pointer-lock</code></strong> allows (surprise!) pointer lock.</li>
<li><strong><code>allow-same-origin</code></strong> allows the document to maintain its origin; pages loaded
from <code>https://example.com/</code> will retain access to that origin’s data.</li>
<li><strong><code>allow-scripts</code></strong> allows JavaScript execution, and also allows features to
trigger automatically (as they’d be trivial to implement via JavaScript).</li>
<li><strong><code>allow-top-navigation</code></strong> allows the document to break out of the frame by
navigating the top-level window.</li>
</ul>
<p>With these in mind, we can evaluate exactly why we ended up with the specific
set of sandboxing flags in the Twitter example above:</p>
<ul>
<li><strong><code>allow-scripts</code></strong> is required, as the page loaded into the frame runs some
JavaScript to deal with user interaction.</li>
<li><strong><code>allow-popups</code></strong> is required, as the button pops up a tweeting form in a new
window.</li>
<li><strong><code>allow-forms</code></strong> is required, as the tweeting form should be submittable.</li>
<li><strong><code>allow-same-origin</code></strong> is necessary, as twitter.com’s cookies would otherwise
be inaccessible, and the user couldn’t log in to post the form.</li>
</ul>
<p>One important thing to note is that the sandboxing flags applied to a frame also
apply to any windows or frames created in the sandbox. This means that we have
to add <strong><code>allow-forms</code></strong> to the frame’s sandbox, even though the form only exists
in the window that the frame pops up.</p>
<p>With the <code>sandbox</code> attribute in place, the widget gets only the permissions it
requires, and capabilities like plugins, top navigation, and pointer lock remain
blocked. We’ve reduced the risk of embedding the widget, with no ill-effects.
It’s a win for everyone concerned.</p>
<h2 id="privilege-separation">Privilege Separation</h2>
<p>Sandboxing third-party content in order to run their untrusted code in a
low-privilege environment is fairly obviously beneficial. But what about your
own code? You trust yourself, right? So why worry about sandboxing?</p>
<p>I’d turn that question around: if your code doesn’t need plugins, why give it
access to plugins? At best, it’s a privilege you never use, at worst it’s a
potential vector for attackers to get a foot in the door. Everyone’s code has
bugs, and practically every application is vulnerable to exploitation in one way
or another. Sandboxing your own code means that even if an attacker successfully
subverts your application, they won’t be given <em>full</em> access to the
application’s origin; they’ll only be able to do things the application could
do. Still bad, but not as bad as it could be.</p>
<p>You can reduce the risk even further by breaking your application up into
logical pieces and sandboxing each piece with the minimal privilege possible.
This technique is very common in native code: Chrome, for example, breaks itself
into a high-privilege browser process that has access to the local hard-drive
and can make network connections, and many low-privilege renderer processes that
do the heavy lifting of parsing untrusted content. Renderers don’t need to touch
the disk, the browser takes care of giving them all the information they need to
render a page. Even if a clever hacker finds a way to corrupt a renderer, she
hasn’t gotten very far, as the renderer can’t do much of interest on its own:
all high-privilege access must be routed through the browser’s process.
Attackers will need to find several holes in different pieces of the system
order to do any damage, which hugely reduces the risk of successful pwnage.</p>
<h3 id="safely-sandboxing-eval">Safely sandboxing <code>eval()</code></h3>
<p>With sandboxing and the
<a href="https://developer.mozilla.org/en-US/docs/DOM/window.postMessage"><code>postMessage</code> API</a>, the
success of this model is fairly straightforward to apply to the web. Pieces of
your application can live in sandboxed <code>iframe</code>s, and the parent document can
broker communication between them by posting messages and listening for
responses. This sort of structure ensures that exploits in any one piece of the
app do the minimum damage possible. It also has the advantage of forcing you to
create clear integration points, so you know exactly where you need to be
careful about validating input and output. Let’s walk through a toy example,
just to see how that might work.</p>
<p><a href="/static/demos/evalbox/index.html">Evalbox</a> is an exciting application
that takes a string, and evaluates it as JavaScript. Wow, right? Just what
you’ve been waiting for all these long years. It’s a fairly dangerous
application, of course, as allowing arbitrary JavaScript to execute means that any
and all data an origin has to offer is up for grabs. We’ll mitigate the risk of
Bad Things™ happening by ensuring that the code is executed inside of a sandbox,
which makes it quite a bit safer. We’ll work our way through the code from the
inside out, starting with the frame’s contents:</p>
<pre class="prettyprint"><code><!-- frame.html -->
<!DOCTYPE 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, e.origin);
});
</script>
</head>
</html>
</code></pre>
<p>Inside the frame, we have a minimal document that simply listens for messages
from its parent by hooking into the <code>message</code> event of the <code>window</code> object.
Whenever the parent executes postMessage on the iframe’s contents, this event
will trigger, giving us access to the string our parent would like us to
execute.</p>
<p>In the handler, we grab the <code>source</code> attribute of the event, which is the parent
window. We’ll use this to send the result of our hard work back up once we’re
done. Then we’ll do the heavy lifting, by passing the data we’ve been given into
<code>eval()</code>. This call has been wrapped up in a try block, as banned operations
inside a sandboxed <code>iframe</code> will frequently generate DOM exceptions; we’ll catch
those and report a friendly error message instead. Finally, we post the result
back to the parent window. This is pretty straightforward stuff.</p>
<p>The parent is similarly uncomplicated. We’ll create a tiny UI with a <code>textarea</code>
for code, and a <code>button</code> for execution, and we’ll pull in <code>frame.html</code> via a
sandboxed <code>iframe</code>, allowing only script execution:</p>
<pre class="prettyprint"><code><textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
id='sandboxed'
src='frame.html'></iframe>
</code></pre>
<p>Now we’ll wire things up for execution. First, we’ll listen for responses from
the <code>iframe</code> and <code>alert()</code> them to our users. Presumably a real application
would do something less annoying:</p>
<pre class="prettyprint"><code>window.addEventListener('message',
function (e) {
// Sandboxed iframes which lack the 'allow-same-origin'
// header have "null" rather than a valid origin. This means you still
// have to be careful about accepting data via the messaging API you
// create. Check that source, and validate those inputs!
var frame = document.getElementById('sandboxed');
if (e.origin === "null" && e.source === frame.contentWindow)
alert('Result: ' + e.data);
});
</code></pre>
<p>Next, we’ll hook up an event handler to clicks on the <code>button</code>. When the user
clicks, we’ll grab the current contents of the <code>textarea</code>, and pass them into the
frame for execution:</p>
<pre class="prettyprint"><code>function evaluate() {
var frame = document.getElementById('sandboxed');
var code = document.getElementById('code').value;
// Note that we're sending the message to "*", rather than some specific
// origin. Sandboxed iframes which lack the 'allow-same-origin' header
// don't have an origin which you can target: you'll have to send to any
// origin, which might alow some esoteric attacks. Validate your output!
frame.contentWindow.postMessage(code, '*');
}
document.getElementById('safe').addEventListener('click', evaluate);
</code></pre>
<p>Easy, right? We’ve created a very simple evaluation API, and we can be sure that
code that’s evaluated doesn’t have access to sensitive information like cookies
or DOM storage. Likewise, evaluated code can’t load plugins, pop up new windows,
or any of a number of other annoying or malicious activities.</p>
<p>Take a look at the code for yourself:</p>
<ul>
<li><a href="/static/demos/evalbox/index.html">Evalbox Demo</a></li>
<li><a href="/static/demos/evalbox/index.html" download="index.html"><code>index.html</code></a></li>
<li><a href="/static/demos/evalbox/frame.html" download="frame.html"><code>frame.html</code></a></li>
</ul>
<p>You can do the same for your own code by breaking monolithic applications into
single-purpose components. Each can be wrapped in a simple messaging API, just
like what we’ve written above. The high-privilege parent window can act as a
controller and dispatcher, sending messages into specific modules that each have
the fewest privileges possible to do their jobs, listening for results, and
ensuring that each module is well-fed with only the information it requires.</p>
<p>Note, however, that you need to be very careful when dealing with framed content
that comes from the same origin as the parent. If a page on
<code>https://example.com/</code> frames another page on the same origin with a sandbox
that includes both the <strong><code>allow-same-origin</code></strong> and <strong><code>allow-scripts</code></strong> flags, then
the framed page can reach up into the parent, and remove the sandbox attribute
entirely.</p>
<h2 id="play-in-your-sandbox">Play in your sandbox.</h2>
<p>Sandboxing is available for you now in a variety of browsers: Firefox 17+,
IE10+, and Chrome at the time of writing (<a href="http://caniuse.com/#feat=iframe-sandbox">caniuse, of course, has an up-to-date
support table</a>). Applying the <code>sandbox</code>
attribute to <code>iframes</code> you include allows you to grant certain privileges to the
content they display, <em>only</em> those privileges which are necessary for the
content to function correctly. This gives you the opportunity to reduce the risk
associated with the inclusion of third-party content, above and beyond what is
already possible with <a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/">Content Security
Policy</a>.</p>
<p>Moreover, sandboxing is a powerful technique for reducing the risk that a clever
attacker will be able to exploit holes in your own code. By separating a
monolithic application into a set of sandboxed services, each responsible for a
small chunk of self-contained functionality, attackers will be forced to not
only compromise specific frames’ content, but also their controller. That’s a
much more difficult task, especially since the controller can be greatly reduced
in scope. You can spend your security-related effort auditing <em>that</em> code if you
ask the browser for help with the rest.</p>
<p>That’s not to say that sandboxing is a complete solution to the problem of
security on the internet. It offers defense in depth, and unless you have
control over your users’ clients, you can’t yet rely on browser support for all
your users (if you do control your users clients – an enterprise environment,
for example – hooray!). Someday… but for now sandboxing is another layer of
protection to strengthen your defenses, it’s not a complete defense upon which
you can soley rely. Still, layers are excellent. I suggest making use of this
one.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li>
<p>“<a href="http://www.cs.berkeley.edu/~devdatta/LeastPrivileges.pdf">Privilege Separation in HTML5 Applications</a>”
is an interesting paper that works through the design of a small framework,
and its application to three existing HTML5 apps.</p>
</li>
<li>
<p>Sandboxing can be even more flexible when combined with two other new iframe
attributes: <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-srcdoc"><code>srcdoc</code></a>,
and <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-seamless"><code>seamless</code></a>.
The former allows you to populate a frame with content without the overhead of
an HTTP request, and the latter allows style to flow into the framed content.
Both have fairly miserable browser support at the moment (Chrome and WebKit
nightlies). but will be an interesting combination in the future. You could,
for example, sandbox comments on an article via the following code:</p>
<pre class="prettyprint"><code><iframe sandbox seamless
srcdoc="<p>This is a user's comment!
It can't execute script!
Hooray for safety!</p>"></iframe>
</code></pre>
</li>
</ul>
{% endblock %}