blob: 7a6d4b475606c505cd484ed6513162b8a9d6709c (
plain)
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
|
---
title: Subresource Integrity
slug: Web/Security/Subresource_Integrity
tags:
- CORS
- SRI
- Security
- Subresource Integrity
- 子资源完整性
translation_of: Web/Security/Subresource_Integrity
original_slug: Web/Security/子资源完整性
---
<p><strong>子资源完整性</strong>(SRI)是允许浏览器检查其获得的资源(例如从 <a href="/en-US/docs/Glossary/CDN">CDN</a> 获得的)是否被篡改的一项安全特性。它通过验证获取文件的哈希值是否和你提供的哈希值一样来判断资源是否被篡改。</p>
<h2 id="SRI_如何工作">SRI 如何工作</h2>
<p>使用 {{Glossary("CDN", "内容分发网络 (CDNs)")}} 在多个站点之间共享脚本和样式表等文件可以提高站点性能并节省带宽。然而,使用CDN也存在风险,如果攻击者获得对 CDN 的控制权,则可以将任意恶意内容注入到 CDN 上的文件中 (或完全替换掉文件)<br>
),因此可能潜在地攻击所有从该 CDN 获取文件的站点。</p>
<p>子资源完整性通过确保 Web 应用程序获得的文件未经第三方注入或其他任何形式的修改来降低这种攻击的风险。</p>
<div class="note">
<p><strong>Note</strong>: SRI并不能规避所有的风险。第三方库经常会自己请求额外的信息,这就有可能会携带用户的账号密码等关键信息。这些经常需要js功能的支持,比如一个地图库会需要取<svg>数据来渲染,但是包含点击事件。</p>
</div>
<h2 id="如何使用_SRI">如何使用 SRI</h2>
<p>将使用 base64 编码过后的文件哈希值写入你所引用的 {{HTMLElement("script")}} 或 {{HTMLElement("link")}} 标签的 <strong>integrity</strong> 属性值中即可启用子资源完整性功能。</p>
<p>integrity 值分成两个部分,第一部分指定哈希值的生成算法(目前支持 sha256、sha384 及 sha512),第二部分是经过 base64 编码的实际哈希值,两者之间通过一个短横(-)分割。</p>
<div class="note">
<p><strong>integrity</strong> 值可以包含多个由空格分隔的哈希值,只要文件匹配其中任意一个哈希值,就可以通过校验并加载该资源。</p>
</div>
<p>使用 base64 编码 sha384 算法计算出摘要后的 <strong>integrity</strong> 值的例子:</p>
<pre>sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC
</pre>
<p><code>oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC</code> 即哈希值部分,<code>sha384</code> 前缀说明使用的是 sha384 哈希方法。</p>
<div class="note">
<p><strong>integrity</strong> 中的“hash”部分,严格来说,是一种经过特定的哈希函数转换之后的密码学摘要。但是更一般的叫法就是<strong>哈希</strong>,本文用的也是这种叫法。</p>
</div>
<h3 id="生成_SRI_哈希的工具">生成 SRI 哈希的工具</h3>
<p>你可以用 <strong>openssl</strong> 在命令行中执行如下命令来生成 SRI 哈希值:</p>
<pre>cat <strong>FILENAME.js</strong> | openssl dgst -sha384 -binary | openssl enc -base64 -A </pre>
<p>或者用 <strong>shasum</strong> 在命令行中执行:</p>
<pre><code>shasum -b -a 384 FILENAME.js | xxd -r -p | base64</code></pre>
<p><code>另外,</code><a href="https://srihash.org/">SRI Hash Generator</a> 是一个在线生成 SRI 哈希值的工具。</p>
<h2 id="内容安全策略及子资源完整性">内容安全策略及子资源完整性</h2>
<p>你可以根据<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">内容安全策略</a>来配置你的服务器使得指定类型的文件遵守 SRI。这是通过在 CSP 头部添加 {{CSP("require-sri-for")}} 指令实现的:</p>
<pre><code>Content-Security-Policy: require-sri-for script;</code></pre>
<p><code>这条指令规定了所有 JavaScript 都要有 <strong>integrity</strong> 属性,且通过验证才能被加载。</code></p>
<p>你也可以指定所有样式表也要通过 SRI 验证:</p>
<pre><code>Content-Security-Policy: require-sri-for style;</code></pre>
<p><code>你也可以对两者都加上验证。</code></p>
<h2 id="范例">范例</h2>
<p>在这个例子中,我们已知 <code id="sriSnippet">oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC 是一个指定文件,比如 </code><code>example-framework.js,经过 SHA-384</code> 算法得出的摘要,同时在 <code>https://example.com/example-framework.js 上有其一份拷贝。</code></p>
<h3 id="在_script_标签中增加_SRI">在 script 标签中增加 SRI</h3>
<p>你可以使用以下的 {{HTMLElement("script")}} 元素告诉浏览器在执行 https://example.com/example-framework.js 中的内容之前,必须先比较该文件的哈希值是否和预期的一致,只有一致才能执行。</p>
<pre class="brush: html"><script src="https://example.com/example-framework.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script></pre>
<div class="note">
<p>有关 <strong>crossorigin </strong>属性的更多信息,见 <a href="/en-US/docs/Web/HTML/CORS_settings_attributes">CORS settings attributes</a>.</p>
</div>
<h2 id="浏览器如何处理_SRI">浏览器如何处理 SRI</h2>
<p>浏览器根据以下步骤处理 SRI:</p>
<ol>
<li>当浏览器在 {{HTMLElement("script")}} 或者 {{HTMLElement("link")}} 标签中遇到 <strong>integrity</strong> 属性之后,会在执行脚本或者应用样式表之前对比所加载文件的哈希值和期望的哈希值。</li>
<li>当脚本或者样式表的哈希值和期望的不一致时,浏览器必须拒绝执行脚本或者应用样式表,并且必须返回一个网络错误说明获得脚本或样式表失败。</li>
</ol>
<h2 id="规范">规范</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">规范</th>
<th scope="col">状态</th>
<th scope="col">注释</th>
</tr>
<tr>
<td>{{SpecName('Subresource Integrity')}}</td>
<td>{{Spec2('Subresource Integrity')}}</td>
<td></td>
</tr>
<tr>
<td>{{SpecName('Fetch')}}</td>
<td>{{Spec2('Fetch')}}</td>
<td></td>
</tr>
</tbody>
</table>
<h2 id="浏览器兼容性">浏览器兼容性</h2>
<p>{{CompatibilityTable}}</p>
<div id="compat-desktop">
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Chrome</th>
<th>Firefox (Gecko)</th>
<th>Internet Explorer</th>
<th>Opera</th>
<th>Safari</th>
</tr>
<tr>
<td>The integrity attribute for <code><script></code> and <code><link></code></td>
<td>{{ CompatChrome("45.0") }}</td>
<td>{{ CompatGeckoDesktop("43") }}</td>
<td>{{CompatNo}}</td>
<td>{{CompatOpera("32")}}</td>
<td>{{CompatNo}} [1]</td>
</tr>
<tr>
<td>The CSP {{CSP("require-sri-for")}} directive</td>
<td>{{CompatUnknown}}</td>
<td>{{ CompatGeckoDesktop("49") }}</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatUnknown}}</td>
</tr>
</tbody>
</table>
</div>
<div id="compat-mobile">
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Chrome for Android</th>
<th>Firefox Mobile (Gecko)</th>
<th>IE Mobile</th>
<th>Opera Mobile</th>
<th>Safari Mobile</th>
</tr>
<tr>
<td>The integrity attribute for <code><script></code> and <code><link></code></td>
<td>{{ CompatChrome("45.0") }}</td>
<td>{{CompatGeckoMobile("43")}}</td>
<td>{{CompatNo}}</td>
<td>{{CompatNo}}</td>
<td>{{CompatNo}} [1]</td>
</tr>
<tr>
<td>The CSP {{CSP("require-sri-for")}} directive</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatGeckoMobile("49")}}</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatUnknown}}</td>
</tr>
</tbody>
</table>
</div>
<p>[1] {{WebKitBug(148363)}}</p>
<p>[2] 位于 <code>security.csp.experimentalEnabled</code> 之后的是: config preference。</p>
<h2 id="相关资料">相关资料</h2>
<ul>
<li>Content Security Policy 内容安全策略</li>
<li>{{httpheader("Content-Security-Policy")}}</li>
<li><a href="https://frederik-braun.com/using-subresource-integrity.html" id="page-title">A CDN that can not XSS you: Using Subresource Integrity</a></li>
</ul>
<p>{{QuickLinksWithSubpages("/zh-CN/docs/Web/Security")}}</p>
|