1. 引言
网页上的站点和应用程序很少仅由单一来源的资源组成。例如,作者会从各种服务和内容分发网络加载脚本和样式,并且必须信任所交付的表示确实是他们期望加载的内容。如果攻击者能够骗用户从恶意服务器下载内容(通过 DNS [RFC1035] 投毒或其他手段),作者将无计可施。同样,如果攻击者能够在内容分发网络(CDN)服务器上替换文件,则可以注入任意内容。
通过安全通道传递资源可以降低部分风险:使用 TLS [TLS]、HSTS [RFC6797] 和固定公钥 [RFC7469],用户代理基本可以确定它正与它认为的服务器通信。然而,这些机制仅验证**服务器**,而**不验证内容**。拥有服务器访问权限的攻击者(或管理员)可以随意篡改内容。理想情况下,作者不仅能够固定服务器的密钥,还能固定**内容**,确保资源的准确表示,并且仅加载和执行该表示。
本文档规定了这样一种验证方案,通过为两个 HTML 元素扩展一个包含资源表示的加密哈希的 integrity 属性来实现。例如,作者可能希望从共享服务器加载某个框架而不是在自己的来源上托管。若指定期望的 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. 关键概念和术语
本节定义了文档中使用的若干术语。
术语 digest 指对任意数据块执行密码哈希函数后得到的 base64 编码结果。
术语 origin 与 same origin 在 HTML 中已定义。[HTML]
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 Priority 与 § 3.3.3 Get the strongest metadata from set。
如果字符串的 ASCII 小写形式被 包含 在 有效的 SRI 哈希算法标记集合 中,则该字符串是 有效的 SRI 哈希算法标记。
2.1. 语法概念
本文件使用的扩展巴科斯范式(ABNF)记法在 RFC5234 中定义。[ABNF]
附录 B.1([ABNF])定义了 VCHAR(可打印字符)和 WSP(空白)规则。
内容安全策略(CSP)定义了 base64-value 与 hash-algorithm 规则。[CSP]
3. 框架
此处规定的完整性验证机制归结为为资源生成足够强的密码摘要,并将该摘要传递给用户代理,以便用于验证响应。
3.1. 完整性元数据
要验证响应的完整性,用户代理需要在 请求 中包含 完整性元数据。该元数据包括以下信息
-
密码哈希函数("alg")
-
digest("val")
-
选项("opt")
必须提供哈希函数和摘要,才能验证响应的完整性。
注意: 目前没有定义选项。然而,规范的未来版本可能会定义选项,例如 MIME 类型 [MIME-TYPES]。
此元数据必须采用与 hash-source(不含单引号)相同的格式编码,参见 Content Security Policy Level 2 规范第 4.2 节。
例如,给定仅包含字符串 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 Hash collision attacks。
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 Parse metadata 与 § 3.3.3 Get the strongest metadata from set 所述算法)。
当哈希函数被判定为不安全时,用户代理应当弃用并最终移除使用该不安全哈希函数进行完整性验证的支持。用户代理可以在使用已弃用函数的摘要时检查响应的有效性。
为了让作者能够切换到更强的哈希函数而不受旧用户代理的限制,使用不受支持的哈希函数进行验证的行为相当于未提供完整性值(参见下面的 § 3.3.4 Do bytes match 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 哈希算法标记,则 continue。
-
如果 algorithm-and-value[1] exists,将 base64-value 设为 algorithm-and-value[1]。
-
设 metadata 为有序映射 «["alg" → algorithm, "val" → base64-value]»。
注意: 由于尚未定义
options(参见 § 3.1 完整性元数据),metadata 中不设置对应条目。如果未来版本定义了options,则可以将 expression-and-options[1] 用作options。 -
Append 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,continue。
-
否则,如果 newAlgorithmIndex 大于 currentAlgorithmIndex
-
将 strongest 设为 item。
-
将 result 设为 « item »。
-
-
否则,newAlgorithmIndex 与 currentAlgorithmIndex 相同。Append 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 >
...(示例代码保持不变)...
注意: 用户代理可能允许用户通过用户偏好、书签脚本、第三方插件等机制修改此算法的结果。例如,像 HTTPS Everywhere 这样的扩展生成的重定向,即使资源的 HTTPS 版本与 HTTP 版本不同,也可以正确加载和执行。
注意: 子资源完整性(Subresource Integrity)需要 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-expressions 是在每个 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";
3.7. 处理完整性违规
用户代理将拒绝渲染或执行未通过完整性检查的响应,而是返回 Fetch [Fetch] 中定义的网络错误。
注意: 在完整性检查失败时,会触发 error 事件。希望提供备用资源的开发者(例如,从非 CDN 的次要、可信但速度较慢的来源获取资源)可以捕获此 error 事件并提供相应的处理程序,以用不同的资源替换失败的资源。
3.8. 完整性策略
Integrity-Policy 与 Integrity-Policy-Report-Only HTTP 头使文档能够对所有加载的子资源的完整性元数据要求进行策略性强制,针对特定的 destinations。这些头的值是 Dictionary [RFC9651],每个成员值都是 inner list,其中包含 tokens。
source 是一个字符串。唯一可能的取值是 "inline"。
destination 是 destination type。其可能的取值为 "script" 与 "style"。
integrity policy 是一个 struct,包含以下内容
-
sources,一个 source 列表,初始为空。
-
blocked destinations,一个 destination 列表,初始为空。
-
endpoints,一个 list,其中为字符串,初始为空。
在 处理完整性策略 时,使用 header list headers 与 header name headerName,执行以下操作
-
设 integrityPolicy 为新的 integrity policy。
-
设 dictionary 为从 headers 中使用 headerName 与 "
dictionary" 获取的结构化字段值的结果。 -
如果 dictionary["
sources"] 不 exist 或其值 contains "inline",append "inline" 到 integrityPolicy 的 sources。 -
如果 dictionary["
blocked-destinations"] exists-
如果其值 contains "
script",append "script" 到 integrityPolicy 的 blocked destinations。 -
如果其值 contains "
style",append "style" 到 integrityPolicy 的 blocked destinations。
-
-
如果 dictionary["
endpoints"] exists-
将 integrityPolicy 的 endpoints 设为 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 与 policy container container,执行以下操作-
设 headers 为 response 的 header list。
-
如果 headers contains
integrity-policy,则将 container 的 integrity policy 设为运行 处理完整性策略 并使用相应的 header value 的结果。 -
如果 headers contains
integrity-policy-report-only,则将 container 的 report only integrity policy 设为运行 处理完整性策略 并使用相应的 header value 的结果。
3.8.2. 请求是否应被完整性策略阻止
要确定 请求是否应被完整性策略阻止,给定 request request,执行以下操作-
设 policyContainer 为 request 的 policy container。
-
设 parsedMetadata 为调用 parse metadata,传入 request 的 integrity metadata 所得到的结果。
-
如果 parsedMetadata 不是空集,并且 request 的 mode 是 "
cors" 或 "same-origin",则返回 "Allowed". -
令 policy 为 policyContainer 的 integrity policy。
-
令 reportPolicy 为 policyContainer 的 report only integrity policy。
-
如果 policy 与 reportPolicy 均为空的 integrity policy,则返回 "Allowed".
-
令 global 为 request 的 client 的 global object。
-
如果 global 不是
Window且也不是WorkerGlobalScope,则返回 "Allowed". -
令 block 为布尔值,初始为 false。
-
令 reportBlock 为布尔值,初始为 false。
-
如果 policy 的 sources contains "
inline" 且 policy 的 blocked destinations contains request 的 destination,则将 block 设为 true。 -
如果 reportPolicy 的 sources contains "
inline" 且 reportPolicy 的 blocked destinations contains request 的 destination,则将 reportBlock 设为 true。 -
如果 block 为 true 或 reportBlock 为 true,则 report violation,并传入 request、block、reportBlock、policy 与 reportPolicy。
-
如果 block 为 true,则返回 "
Blocked";否则返回 "Allowed"。
3.8.3. 报告违规
dictionary :IntegrityViolationReportBody ReportBody {USVString ;documentURL USVString ;blockedURL USVString ;destination boolean ; };reportOnly
要 report violation(报告违规),需提供 Request request、布尔值 block、布尔值 reportBlock、integrity policy policy 与 integrity policy reportPolicy,执行如下步骤:
-
令 settingsObject 为 request 的 client。
-
令 global 为 settingsObject 的 global object。
-
Assert:global 是
Window或WorkerGlobalScope。 -
令 url 为 null。
-
如果 global 是
Window,则把 url 设为 global 的 associated Document 的URL。 -
如果 global 是
WorkerGlobalScope,则把 url 设为 global 的 URL。 -
令 documentURL 为对 url 调用 strip URL for use in reports(为报告去除 URL)后的结果。
-
令 blockedURL 为对 request 的 URL 调用 strip URL for use in reports(为报告去除 URL)后的结果。
-
如果 block 为 true,则 for each endpoint 在 policy 的 endpoints 中
-
令 body 为一个新的
IntegrityViolationReportBody,并按如下方式初始化:documentURL-
documentURL
blockedURL-
blockedURL
destination-
request 的 destination
reportOnly-
false
-
Generate and queue a report(生成并入队报告),使用以下参数:
- context
-
settingsObject
- type
-
"
integrity-violation" - destination
-
endpoint
- data
-
body
-
-
如果 reportBlock 为 true,则 for each endpoint 在 reportPolicy 的 endpoints 中
-
令 reportBody 为一个新的
IntegrityViolationReportBody,并按如下方式初始化:documentURL-
documentURL
blockedURL-
blockedURL
destination-
request 的 destination
reportOnly-
true
-
Generate and queue a report(生成并入队报告),使用以下参数:
- context
-
settingsObject
- type
-
"
integrity-violation" - destination
-
endpoint
- data
-
reportBody
-
4. 代理
对优化代理及其他修改响应的中间服务器的要求是:必须确保这些响应的摘要与新内容保持同步。一个办法是确保与资源关联的完整性元数据得到更新。另一种做法是只交付页面作者已请求完整性校验的资源的规范(canonical)版本。
为帮助中间服务器获取信息,提供资源的服务器应该在资源响应中附带 Cache-Control 头部,且其值为 no-transform。
5. 安全性和隐私考虑
本节不具有规范性。
5.1. 非安全上下文仍然不安全
完整性元数据若由非安全上下文(例如 HTTP 页面)提供,只能防止因服务器被攻破而导致的外部资源完整性受损。网络攻击者仍然可以在传输途中篡改摘要(或直接删除摘要,或对文档进行任何其他修改),正如他们可以篡改要验证的响应一样。因此,**建议**作者仅在安全上下文中交付完整性元数据。另请参阅Securing the Web。
5.2. 哈希碰撞攻击
摘要的强度取决于生成它的哈希函数。**建议**用户代理拒绝支持已知弱哈希函数,并将支持的算法限制在已知抗冲突的算法上。通常不推荐使用的哈希函数包括 MD5 和 SHA-1。撰写本文时,SHA-384 是一个不错的基线。
此外,**建议**用户代理定期重新评估其支持的哈希函数,并在发现不安全时弃用对应支持。随着时间推移,一些哈希函数可能被证明远比预期更弱,甚至被破解,因此用户代理保持对这些进展的感知非常重要。
5.3. 跨源数据泄漏
本规范要求完整性受保护的跨源请求必须使用CORS 协议,以确保资源内容已明确共享给请求方。如果省略此要求,攻击者即可违反同源策略,判断跨源资源是否包含特定内容。
攻击者会尝试使用已知摘要加载资源,并观察加载是否失败。如果加载失败,攻击者可以推断响应未匹配摘要,从而获得部分内容信息。例如,这可能会泄露用户是否已登录某项服务。
此外,攻击者还可以对本应静态的资源进行暴力破解。设想有如下 JSON 响应:
攻击者可以预先为常见用户名计算 hash,并在多次尝试加载文档时提交对应的摘要。一次成功的加载即证实攻击者正确猜出了用户名。
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 提供的深入反馈。