1. 介绍
Web 上的站点和应用很少仅由来自单一源的资源组成。 例如,作者会从各种服务和内容分发网络拉取脚本和样式, 并且必须相信所交付的表示确实就是他们预期加载的内容。 如果攻击者能诱使用户从恶意服务器下载内容(通过 DNS [RFC1035] 投毒,或其他此类手段), 作者将 无计可施。同样,能够替换内容分发网络(CDN)服务器上文件的攻击者, 也有能力注入任意内容。
通过安全通道交付资源可缓解部分风险:借助 TLS [TLS]、HSTS [RFC6797] 以及固定的公钥 [RFC7469],用户代理可以相当确信 它确实在与其认为正在通信的服务器通信。然而,这些 机制只认证服务器,而不认证内容。 能够访问服务器的攻击者(或管理员)可以肆意篡改内容。 理想情况下,作者不仅应能够固定服务器的密钥, 还应能够固定内容,确保加载和执行的是 资源的精确表示,且仅该表示。
本文档规定了这样一种验证方案,它通过向两个 HTML 元素扩展
一个 integrity 属性,该属性包含作者预期加载的资源表示的
密码学哈希。例如,作者可能希望从共享服务器加载某个框架,
而不是将其托管在自己的源上。指定
https://example.com/example-framework.js 的预期 SHA-384 哈希为
Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7,意味着
用户代理可以在执行其包含的 JavaScript 之前,验证它从该 URL 加载的数据是否匹配
该预期哈希。这种
完整性验证显著降低了攻击者替换恶意内容的风险。
可以通过将该哈希添加到
script 元素来把此示例传达给用户代理,如下所示:
< script src = "https://example.com/example-framework.js" integrity = "sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7" crossorigin = "anonymous" ></ script >
当然,脚本并不是唯一会受益于完整性验证的响应类型。
此处规定的方案也适用于 link,
并且本规范的未来版本很可能会扩展这一覆盖范围。
1.1. 目标
-
第三方服务被攻陷不应自动意味着包含其脚本的每个站点也被攻陷。 内容作者将拥有一种机制,借此可指定他们对所加载内容的预期, 例如,这意味着他们可以加载一个特定脚本, 而不是碰巧具有某个特定 URL 的任意脚本。
-
验证机制应具有错误报告功能, 用以通知作者收到的是无效响应。
1.2. 用例/示例
1.2.1. 资源完整性
-
作者希望使用内容分发网络来提高全球分布用户的性能。 然而,重要的是要确保 CDN 的服务器只交付作者预期它们交付的代码。 为了缓解 CDN 被攻陷(或出现意外恶意行为)会以不幸方式改变该站点的风险, 将以下 完整性 元数据添加到页面中包含的
link元素: -
作者希望包含由第三方分析服务提供的 JavaScript。 为了确保只执行经过仔细审查的代码,作者为该脚本生成 完整性元数据, 并将其添加到
script元素: -
用户代理希望确保在高权限 HTML 上下文(例如浏览器的新标签页)中运行的 JavaScript 代码 在显示前不会被篡改。 完整性 元数据可缓解被更改的 JavaScript 在这些页面的高权限上下文中运行的风险。
2. 关键 概念和术语
本节定义本文档中使用的若干术语。
术语 摘要 指的是 对任意数据块执行密码学哈希函数后得到的 base64 编码结果。
base64 编码 在 RFC 4648 第 4 节中定义。 [RFC4648]
SHA-256、SHA-384 和 SHA-512 是 由 NIST 定义的 SHA-2 密码学哈希函数集合的一部分。[SHA2]
有效
SRI 哈希算法令牌集是有序集
« "sha256", "sha384", "sha512" »(分别对应于 SHA-256、
SHA-384 和 SHA-512)。该集合的顺序
具有含义,更强的算法出现在集合中更靠后的位置。更多信息见
§ 3.2.2 优先级和 § 3.3.3 从 set 获取最强
元数据。
如果某字符串的 ASCII 小写形式包含在 有效 SRI 哈希算法令牌集中, 则该字符串是一个有效 SRI 哈希算法令牌。
2.1. 语法概念
本文档中使用的扩展巴科斯-诺尔范式(ABNF)记法 在 RFC5234 中规定。[ABNF]
附录 B.1 [ABNF] 定义了 VCHAR(打印 字符)和 WSP (空白)规则。
Content Security Policy 定义了 base64-value 和
hash-algorithm 规则。[CSP]
3. 框架
此处规定的完整性验证机制归结为: 为资源生成足够强的密码学摘要, 并将该摘要传输给用户代理,使其可用于 验证响应。
3.1. 完整性元数据
为了验证响应的完整性,用户代理需要将完整性 元数据作为请求的一部分。此 元数据由以下信息组成:
-
密码学哈希函数("alg")
-
摘要("val")
-
选项("opt")
必须提供哈希函数和摘要,才能验证 响应的完整性。
注:目前未定义任何选项。不过, 该规范的未来版本可能会定义选项,例如 MIME 类型 [MIME-TYPES]。
此元数据必须使用与 Content
Security Policy Level 2 规范第 4.2 节中 hash-source 相同的格式进行编码
(不含单引号)。
例如,给定一个仅包含字符串 alert('Hello, world.'); 的脚本资源,
作者可能选择 SHA-384 作为哈希函数。
H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO 是由此产生的
base64 编码摘要。它可以
编码如下:
echo -n"alert('Hello, world.');" | openssl dgst -sha384 -binary| openssl base64 -A
3.2. 密码学哈希函数
一致性用户代理必须支持 SHA-256、SHA-384 和 SHA-512 密码学哈希函数,供请求的 完整性 元数据使用,并且可以支持 本文档未来迭代中定义的额外哈希函数。
注:本文档中支持的算法 (目前!)被认为能够抵抗第二原像攻击和碰撞攻击。 对支持算法集合的未来添加/移除,应建议采用类似的 标准。见 § 5.2 哈希碰撞攻击。
3.2.1. 敏捷性
多个完整性元数据集合可以与单个资源关联, 以便面对未来的密码学发现时提供敏捷性。 例如,上一节所述的资源可以由以下任一哈希表达式描述:
sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO sha512-Q2bFTOhEALkN8hOms2FKTDLy7eugP2zFZ1T8LCvX42Fp3WoNr3bjZSAHeOsHrbV1Fu9/A0EzCinRE7Af1ofPrw==
作者可以选择同时指定两者,例如:
< script src = "hello_world.js" integrity = "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO sha512-Q2bFTOhEALkN8hOms2FKTDLy7eugP2zFZ1T8LCvX42Fp3WoNr3bjZSAHeOsHrbV1Fu9/A0EzCinRE7Af1ofPrw==" crossorigin = "anonymous" ></ script >
在这种情况下,用户代理将选择列表中最强的哈希函数, 并使用该元数据验证响应(如下文 § 3.3.2 解析元数据和 § 3.3.3 从 set 获取最强元数据算法所述)。
当确定某个哈希函数不安全时,用户代理应弃用 并最终移除使用该不安全哈希函数进行完整性验证的支持。 用户代理可以使用基于已弃用函数的摘要来检查响应的有效性。
为了允许作者切换到更强的哈希函数而不受旧版 用户代理的阻碍,使用不支持的哈希函数进行验证的行为就像未提供完整性值一样 (见下文 § 3.3.4 bytes 是否匹配 metadataList?算法)。 鼓励作者使用强哈希函数,并在更强的哈希函数可用时开始迁移到 更强的哈希函数。
3.2.2. 优先级
哈希算法的优先级通过其各自令牌在 有效 SRI 哈希算法令牌集中的顺序指定。 在该集合中更早出现的算法弱于在该集合中更晚出现的算法。
按当前规定,SHA-256 弱于 SHA-384,后者又 弱于 SHA-512。本规范目前不支持其他哈希算法。
3.3. 响应验证算法
3.3.1. 将 algorithm 应用于 bytes
-
令 result 为将 algorithm 应用于 bytes 的结果。
-
返回对 result 进行 base64 编码的结果。
3.3.2. 解析元数据
当被要求在给定字符串 metadata 的情况下解析 元数据时,运行以下步骤:
注:该算法返回一组其哈希函数 可被用户代理理解的哈希表达式。
-
令 result 为空集。
-
对于在空格处拆分 metadata 所返回的每个 item:
-
令 expression-and-options 为 在 U+003F (?) 处拆分 item 的结果。
-
令 algorithm-expression 为 expression-and-options[0]。
-
令 base64-value 为空字符串。
-
令 algorithm-and-value 为 在 U+002D (-) 处拆分 algorithm-expression 的结果。
-
令 algorithm 为 algorithm-and-value[0]。
-
如果 algorithm 不是有效 SRI 哈希算法令牌,则 继续。
-
如果 algorithm-and-value[1] 存在,则将 base64-value 设为 algorithm-and-value[1]。
-
令 metadata 为有序映射 «["alg" → algorithm, "val" → base64-value]»。
注:由于没有定义
options(见 § 3.1 完整性元数据),因此未在 metadata 中设置对应条目。如果在未来版本中定义了options, 则可将 expression-and-options[1] 用作options。 -
将 metadata 追加到 result。
-
-
返回 result。
3.3.3. 从 set 获取最强元数据
-
令 result 为空集,并令 strongest 为 null。
-
对于 set 中的每个 item:
-
断言:item["
alg"] 是一个有效 SRI 哈希算法令牌。 -
如果 result 是空集,则:
-
令 currentAlgorithm 为 strongest["
alg"],并令 currentAlgorithmIndex 为 currentAlgorithm 在有效 SRI 哈希算法令牌集中的索引。 -
令 newAlgorithm 为 item["
alg"],并令 newAlgorithmIndex 为 newAlgorithm 在有效 SRI 哈希算法令牌集中的索引。 -
如果 newAlgorithmIndex 小于 currentAlgorithmIndex,则继续。
-
否则,如果 newAlgorithmIndex 大于 currentAlgorithmIndex:
-
将 strongest 设为 item。
-
将 result 设为 « item »。
-
-
否则,newAlgorithmIndex 和 currentAlgorithmIndex 是 相同值。将 item 追加到 result。
-
-
返回 result。
3.3.4. bytes 是否匹配 metadataList?
-
令 parsedMetadata 为 解析 metadataList的结果。
-
如果 parsedMetadata 为空集, 则返回
true。 -
令 metadata 为 从 parsedMetadata 获取最强元数据的结果。
-
对于 metadata 中的每个 item:
-
令 algorithm 为 item["alg"]。
-
令 expectedValue 为 item["val"]。
-
令 actualValue 为将 algorithm 应用于 bytes 的结果。
-
如果 actualValue 与 expectedValue 区分大小写匹配,则返回
true。
-
-
返回
false。
该算法允许用户代理接受多个有效的强哈希
函数。例如,开发者可能编写如下 script 元素:
< script src = "https://example.com/example-framework.js" integrity = "sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7 sha384-+/M6kredJcxdsqkczBUjMLvqyHb1K/JThDXWsBVxMEeZHEaMKEOEct339VItX1zB" crossorigin = "anonymous" ></ script >
这将允许用户代理接受两种不同的内容载荷, 其中一种匹配第一个 SHA-384 哈希值,另一种匹配第二个 SHA-384 哈希值。
注:用户代理可以允许用户通过 用户偏好、书签脚本、用户代理的第三方附加组件以及 其他此类机制修改该算法的结果。例如,由 HTTPS Everywhere 这类扩展生成的重定向 可以正确加载并执行,即使某个资源的 HTTPS 版本与 HTTP 版本不同。
注:子资源完整性需要 CORS, 在没有 CORS 的情况下尝试使用它是一种逻辑错误。 鼓励用户代理向开发者控制台报告警告消息,以解释此失败。[Fetch]
3.4. HTML 文档子资源的验证
各种 HTML 元素会导致对资源发起请求,这些资源将被
嵌入到文档中,或在其上下文中执行。为了支持其中某些元素的完整性
元数据,向 link 和 script 元素的内容属性列表添加了
一个新的 integrity 属性。[HTML]
注:本规范的未来修订版很可能会
包含对所有可能子资源的完整性支持,即 a、audio、embed、
iframe、img、
link、object、script、source、track 和
video 元素。
3.5. integrity 属性
integrity 属性表示元素的完整性元数据。
该属性的值必须为空字符串,或至少一个
由以下 ABNF 语法描述的有效元数据:
integrity-metadata = *WSP hash-with-options *(1*WSP hash-with-options ) *WSP / *WSP
hash-with-options = hash-expression *("?" option-expression)
option-expression = *VCHAR
hash-expression = hash-algorithm "-" base64-value
option-expression 按每个 hash-expression 关联,
并且仅应用于其紧前面的那个 hash-expression。
为了使用户代理与未来的选项保持完全向前兼容,
用户代理必须忽略所有无法识别的 option-expression。
注:注意,虽然 option-expression
已在语法中保留,
但尚未定义任何选项。规范的未来版本很可能会
为选项定义更具体的语法,因此这里尽可能宽泛地定义它。
3.6.
integrity 链接处理选项
完整性元数据
也可以为
`link`
HTTP 响应标头指定,
作为 integrity 链接参数;该参数必须使用与元素上的
integrity 属性相同的
integrity-metadata 语法
来指定。例如:
Link: </style.css>; rel=preload; as=style; crossorigin="anonymous"; integrity="sha256-[digest goes here]"
3.7. 处理完整性违规
用户代理将拒绝渲染或执行未通过完整性检查的响应, 而是返回 Fetch 中定义的网络错误 [Fetch]。
注:在完整性检查失败时,将触发 error
事件。希望提供规范回退资源(例如,不从 CDN 提供的资源,
也许来自次要、可信但较慢的源)的开发者,可以捕获此
error 事件,并提供适当的处理程序,以将失败的
资源替换为另一个资源。
3.8. Integrity-Policy
Integrity-Policy 和 Integrity-Policy-Report-Only HTTP 标头使文档能够
针对其加载的某些目标的所有子资源,强制实施关于完整性元数据要求的策略。
这些标头的值是一个字典 [RFC9651],其中每个 member-value 都是由 内列表组成的令牌。
来源是一个字符串。其唯一
可能值是 "inline"。
目标是一个目标类型。其可能值为
"script" 和 "style"。
完整性策略是 一个包含以下内容的结构体:
当在给定标头 列表 headers 和标头名称 headerName 的情况下处理完整性策略时,执行以下步骤:
-
令 integrityPolicy 为一个新的完整性策略。
-
令 dictionary 为从 headers 给定 headerName 和 "
dictionary" 获取结构化字段值的结果。 -
如果 dictionary["
sources"] 不存在,或者 其值 包含 "inline",则将 "inline" 追加到 integrityPolicy 的来源。 -
如果 dictionary["
blocked-destinations"] 存在: -
如果 dictionary["
endpoints"] 存在:-
将 integrityPolicy 的端点设为 dictionary['endpoints']。
-
-
返回 integrityPolicy。
Integrity-Policy: blocked-destinations=(script), endpoints=(integrity-endpoint)该阻止还会向 "
integrity-endpoint"
报告端点(由相关 Reporting-Endpoints 标头定义)触发报告。
开发者还可以为 "integrity-violation" 注册 ReportingObserver,以获取
基于 JavaScript 的
报告。
3.8.1. 解析 Integrity-Policy 标头
要解析 Integrity-Policy 标头,给定一个Response response 和一个策略容器 container,执行以下步骤:-
令 headers 为 response 的标头列表。
-
如果 headers 包含
integrity-policy, 则将 container 的完整性策略设为运行 处理完整性策略并使用 对应标头值的结果。 -
如果 headers 包含
integrity-policy-report-only, 则将 container 的仅报告完整性策略设为 运行 处理完整性策略并使用 对应标头值的结果。
3.8.2. 请求是否应被 Integrity Policy 阻止
要确定在给定一个请求 request 的情况下,请求是否应被完整性策略阻止, 执行以下步骤:-
令 policyContainer 为 request 的策略容器。
-
如果 parsedMetadata 不是空集,并且 request 的模式为 "
cors" 或 "same-origin", 则返回 "Allowed"。 -
令 policy 为 policyContainer 的完整性策略。
-
令 reportPolicy 为 policyContainer 的仅报告完整性策略。
-
如果 policy 和 reportPolicy 都是空完整性策略,则返回 "Allowed"。
-
如果 global 既不是
Window也不是WorkerGlobalScope, 则返回 "Allowed"。 -
令 block 为布尔值,初始为 false。
-
令 reportBlock 为布尔值,初始为 false。
-
如果 policy 的来源包含 "
inline", 并且 policy 的被阻止的目标包含 request 的目标, 则将 block 设为 true。 -
如果 reportPolicy 的来源包含 "
inline", 并且 reportPolicy 的被阻止的目标包含 request 的目标, 则将 reportBlock 设为 true。 -
如果 block 为 true 或 reportBlock 为 true,则使用 request、block、reportBlock、policy 和 reportPolicy 报告违规。
-
如果 block 为 true,则返回 "
Blocked";否则返回 "Allowed"。
3.8.3. 报告违规
dictionary :IntegrityViolationReportBody ReportBody {USVString ;documentURL USVString ;blockedURL USVString ;destination boolean ; };reportOnly
要在给定一个Request request、一个布尔值 block、 一个布尔值 reportBlock、一个完整性策略 policy 以及一个完整性策略 reportPolicy 的情况下报告违规, 执行以下步骤:
-
令 settingsObject 为 request 的client。
-
令 global 为 settingsObject 的全局对象。
-
断言: global 是
Window或WorkerGlobalScope。 -
令 url 为 null。
-
如果 global 是
Window, 则将 url 设为 global 的关联 Document 的URL。 -
如果 global 是
WorkerGlobalScope, 则将 url 设为 global 的URL。 -
令 documentURL 为对 url 执行剥离用于报告的 URL的结果。
-
令 blockedURL 为对 request 的URL执行剥离用于报告的 URL的结果。
-
如果 block 为 true,则对 policy 的端点中的每个 endpoint 执行:
-
令 body 为一个新的
IntegrityViolationReportBody, 并按如下方式初始化:documentURL-
documentURL
blockedURL-
blockedURL
destination-
request 的目标
reportOnly-
false
-
使用以下参数生成并排队一个报告:
- context
-
settingsObject
- type
-
"
integrity-violation" - destination
-
endpoint
- data
-
body
-
-
如果 reportBlock 为 true,则对 reportPolicy 的端点中的每个 endpoint 执行:
-
令 reportBody 为一个新的
IntegrityViolationReportBody, 并按如下方式初始化:documentURL-
documentURL
blockedURL-
blockedURL
destination-
request 的目标
reportOnly-
true
-
使用以下参数生成并排队一个报告:
- context
-
settingsObject
- type
-
"
integrity-violation" - destination
-
endpoint
- data
-
reportBody
-
4. 代理
会修改响应的优化代理和其他中间服务器必须确保 与这些响应关联的摘要 与新内容保持同步。一种选择是确保与 资源关联的完整性元数据得到更新。另一种 选择则是仅交付页面作者已请求完整性验证的资源的 规范版本。
为帮助通知中间服务器,提供这些资源的服务器应
随资源一起发送一个 Cache-Control 标头,
其值为 no-transform。
5. 安全和隐私注意事项
本节不是规范性的。
5.1. 非安全上下文仍然是非安全的
由并非安全 上下文的上下文(例如 HTTP 页面)交付的完整性元数据, 只能保护源免受托管外部资源的服务器被攻陷的影响。网络攻击者可以在传输过程中更改 摘要(或完全移除它,或对文档做任何其他事情),正如他们可以更改该哈希意图 验证的响应一样。因此,建议作者仅向安全上下文交付完整性元数据。另见 Securing the Web。
5.2. 哈希碰撞攻击
摘要的强度只取决于用于生成它们的哈希函数的强度。 建议用户代理拒绝支持已知较弱的哈希函数, 并将支持的算法限制为已知抗碰撞的算法。 不推荐的哈希函数示例包括 MD5 和 SHA-1。在撰写本文时, SHA-384 是一个良好的基线。
此外,建议用户代理定期重新评估其支持的哈希 函数,并弃用那些被证明不安全的函数。随着时间推移, 哈希函数可能被证明远比预期弱得多,并且在某些情况下会被破解, 因此用户代理必须了解这些发展。
5.3. 跨源数据泄漏
本规范要求受完整性保护的跨源请求使用 CORS 协议,以确保资源内容被显式共享给 请求者。如果省略该要求, 攻击者就可能违反同源 策略, 并确定某个跨源资源是否具有特定内容。
攻击者会尝试使用已知摘要加载资源,并 观察加载失败。如果加载失败,攻击者可以推测 响应不匹配该哈希,从而获得关于其内容的一些信息。 例如,这可能会揭示用户是否已登录某个特定服务。
此外,攻击者可以对原本静态资源中的特定值进行暴力破解。 考虑如下 JSON 响应:
攻击者可以为包含各种常见用户名的响应预先计算哈希, 并在反复尝试加载文档时指定这些哈希。 成功加载将确认攻击者已正确猜出了用户名。
6. 致谢
此处的大部分内容深受 Gervase Markham 的 Link Fingerprints 概念以及 WHATWG 的 Link Hashes 启发。
特别感谢 Mike West 对本规范初始 版本所作出的宝贵贡献。感谢 Brad Hill、Anne van Kesteren、Jonathan Kingston、Fatih Kilic、Mark Nottingham、Sergey Shekyan、Dan Veditz、Eduardo Vela、 Tanvi Vyas、Yoav Weiss 和 Michal Zalewski 提供宝贵反馈。