1. 引言
本节不具有规范性。
虽然 [RFC1918] 二十多年来一直规定了 “私有”和 “公共”互联网地址之间的区别,但用户代理在将二者相互隔离方面 进展甚微。公共 互联网中的网站可以向内部设备和服务器发起请求,这会促成 多种恶意行为,包括对用户路由器的攻击,例如 [DRIVE-BY-PHARMING]、[SOHO-PHARMING] 和 [CSRF-EXPLOIT-KIT] 中所记录的那些攻击。
这里,我们提出一种针对这类攻击的缓解措施,该措施将要求 内部设备显式选择加入来自公共 互联网的请求。
1.1. 目标
总体目标是防止用户代理无意中促成 对运行在用户本地内联网中的设备,或直接运行在 用户机器上的服务的攻击。例如,我们希望缓解针对以下目标的攻击:
-
用户的路由器,如 [SOHO-PHARMING] 中所概述。注意,现状下的 CORS 保护无法防御这里讨论的这类攻击, 因为它们只依赖 CORS 安全列表方法和 CORS 安全列表请求标头。不会触发 预检,而且 攻击者实际上并不关心读取响应,因为请求 本身就是 CSRF 攻击。
-
在用户回环地址上运行 Web 接口的软件。不管好坏, 这正成为各种应用程序的一种常见部署机制, 并且常常假设存在实际上并不存在的保护(近期示例见 [AVASTIUM] 和 [TREND-MICRO])。
1.2. 示例
1.2.1. 默认安全
http://admin:admin@router.local/set_dns
并传入各种 GET
参数来更改其 DNS 设置。糟糕!
幸运的是,MegaCorp Inc 的路由器对来自 公共互联网的请求没有任何兴趣,也没有采取任何特殊措施来启用它们。这 大大缓解了该漏洞的影响范围,因为恶意请求将 生成一个 CORS 预检请求,而路由器会忽略它。让我们 更仔细地看一下:
给定包含以下 HTML 的 https://csrf.attack/:
<iframe href="https://admin:admin@router.local/set_dns?server1=123.123.123.123"> </iframe>
router.local 将通过组播 DNS 的魔力解析为路由器地址
[RFC6762],
而用户代理会将其标记为 private。由于 csrf.attack 解析为一个
公共地址,该
请求将触发一个 CORS 预检请求:
OPTIONS /set_dns?... HTTP/1.1 Host: router.local Access-Control-Request-Method: GET Access-Control-Request-Private-Network: true ... Origin: https://csrf.attack
路由器将收到此 OPTIONS 请求,并且有多种可能的
安全响应:
-
如果它完全不理解
OPTIONS,它可以返回50X错误。 这会导致预检失败,而实际的GET将永远 不会发出。 -
如果它理解
OPTIONS,它可以在其 响应中不包含Access-Control-Allow-Private-Network标头。 这会导致预检失败,而实际的GET将永远 不会发出。 -
它可以崩溃。崩溃相当安全,尽管不够优雅。
1.2.2. 选择加入
当公共互联网中的网站向该设备发出请求时, 用户代理会判定请求方是 public,而 路由器是 private。这意味着请求将 触发一个 CORS 预检请求,与上文一样。
设备可以通过在其对预检请求的 响应中发送正确的标头来显式授予访问权限。对于上述请求,这可能看起来 像这样:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://public.example.com Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Content-Length: 0 ...
1.2.3. 导航
https://go/ 运行一个内部链接缩短
服务,并且
其员工经常通过电子邮件相互发送此类链接。电子邮件服务器
托管在一个 公共
地址上,以确保员工即使不在办公室也可以工作。多体贴啊!
从 https://mail.mega.corp/ 点击 https://go/* 链接将触发一个 CORS 预检请求,因为它是一个从 公共
地址到 私有地址的请求:
OPTIONS /short-links-are-short-after-shortening HTTP/1.1 Host: go Access-Control-Request-Method: GET Access-Control-Request-Private-Network: true ... Origin: https://mail.mega.corp
为了确保员工能够继续按预期导航这些链接, MegaCorp 选择允许私有网络请求:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://mail.mega.corp Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Content-Length: 0 ...
不过,MegaCorp 的泄露防护部门担心,这种访问
将允许外部人员读取短链器返回的任何重定向位置。
他们或多或少已经接受
https://go/shortlink 会泄露这一事实,但如果目标
(https://sekrits/super-sekrit-project-with-super-sekrit-partner)也泄露,
他们会非常难过。
MegaCorp 的短链工程师小心地通过只为预检返回 CORS 标头 来避免这种潜在失败。“真正的” 导航不需要 CORS 标头,而且他们实际上也不想 支持跨源请求被视为 CORS 同源:
// Request: GET /short-links-are-short-after-shortening HTTP/1.1 Host: go ... // Response: HTTP/1.1 301 Moved Permanently ... Location: https://sekrits/super-sekrit-project-with-super-sekrit-partner
导航将正常继续,但 mail.mega.corp 不会被
视为与该响应 CORS 同源。
1.2.4. 混合内容
当公共互联网中具有 潜在可信源的网站 从该设备请求数据时,用户代理会识别出 请求方为 public,而该设备为 private(不是 潜在可信源)。 这会触发一个 CORS 预检请求,并在 收到正确的预检响应后向用户显示权限提示。
网站需要将 IPAddressSpace
作为 fetch() API
选项显式声明:
fetch( "http://router.local/ping" , { targetAddressSpace: "private" , });
设备可以通过在预检响应 标头中显式指示权限,并提供 唯一设备 ID 和对用户友好的设备名称来授予访问权限。 对上述请求的示例响应:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://mail.mega.corp Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Private-Network-Access-ID: 01:23:45:67:89:0A Private-Network-Access-Name: userA’s MegaCorp device Content-Length: 0 ...
权限提示将出现,显示来自设备 标头的 ID 和名称。如果用户授予权限,请求将继续进行。
2. 框架
2.1. IP 地址空间
如下定义 IPAddressSpace
:
enum {IPAddressSpace ,"public" ,"private" };"local"
每个 IP 地址都属于一个 IP 地址空间,它可以是 三个不同值之一:
-
local:仅包含本地 主机。换言之,其目标对每个 设备都不同的地址。
-
private:包含 仅在当前网络内有意义的地址。换言之, 其目标会根据网络位置而不同的地址。
-
public:包含所有 其他地址。换言之,其目标对于 IP 网络上的全球所有设备都相同的地址。
为方便起见,我们还定义以下术语:
如果满足以下任一条件,则 IP 地址空间 lhs 比 IP 地址空间 rhs 较不公共:
要确定 IP 地址空间,给定一个 IP 地址 address,运行以下步骤:
-
如果 address 属于
::ffff:0:0/96“IPv4 映射地址” 地址块,则将 address 替换为其嵌入的 IPv4 地址。 -
对于 非公共 IP 地址 块”表中的每个 row:
-
如果 address 属于 row 的地址块,则返回 row 的 地址空间。
-
-
返回 public。
| 地址块 | 名称 | 引用 | 地址空间 |
|---|---|---|---|
127.0.0.0/8
| IPv4 回环 | [RFC1122] | local |
10.0.0.0/8
| 私有使用 | [RFC1918] | private |
100.64.0.0/10
| 运营商级 NAT | [RFC6598] | private |
172.16.0.0/12
| 私有使用 | [RFC1918] | private |
192.168.0.0/16
| 私有使用 | [RFC1918] | private |
198.18.0.0/15
| 基准测试 | [RFC2544] | local |
169.254.0.0/16
| 链路本地 | [RFC3927] | private |
::1/128
| IPv6 回环 | [RFC4291] | local |
fc00::/7
| 唯一本地 | [RFC4193] | private |
fe80::/10
| 链路本地单播 | [RFC4291] | private |
::ffff:0:0/96
| IPv4 映射 | [RFC4291] | 见映射的 IPv4 地址 |
用户代理可以允许通过管理员或用户配置 覆盖某些 IP 地址块的地址空间。这可能有助于保护例如 IPv6 内联网, 在这些内联网中,按照上述算法,大多数 IP 地址都被视为 public, 而可改为配置 用户代理将该内联网视为 private。
注: 类似
169.254.0.0/16 的链路本地 IP 地址被视为 private,因为此类地址可以为网络链路上的所有设备
标识同一目标。此
规范的先前版本曾改为将它们视为 local。
注: 如果链路本地 IP 地址跨链路共享, 它们就会失去意义。这 与非公共 IP 地址并没有根本不同,后者也都有 某种程度的局部性,超出该范围后就会变得含糊, 但它确实带来了代理混淆问题的特定风险。[LINK-LOCAL-URI] 试图通过为 URI 中的链路本地 IP 地址定义语法来解决这个问题。
注: 每个 IP 地址空间的内容曾一度
根据 IANA 特殊用途地址注册表
([IPV4-REGISTRY]
和 [IPV6-REGISTRY])以及其中定义的
Globally Reachable 位
来确定。事实证明,这对我们的用途而言是一个不准确的信号,
如 规范 issue #50 中所述。
一旦对 IPv4 映射 IPv6 地址的访问 被完全阻止,就移除此特殊情况。[Issue #36]
2.2. 私有网络请求
一个 请求(request)是一个 私有网络请求,如果
request 的 当前 URL 的 host
映射到一个 IP 地址,
且该 IP 地址的 IP 地址
空间比 request 的 更不公开,相较于其 策略容器的 IP 地址
空间。
将 IP 地址分类到三个宽泛的地址空间是一种 不完善且理论上并不严谨的方法。它是一个代理指标,用来确定 两个网络端点是否应被允许自由通信, 换言之,端点 A 是否可以在不经由 端点 C 上的用户代理中转的情况下从端点 B 到达。
这种方法存在一些缺陷:
-
漏报:连接到两个不同 private 网络(例如家庭网络和 VPN)的客户端,可能 允许从 VPN 提供服务的网站访问家庭网络上的设备。 另见下方 issue。
即便如此,此规范旨在为一个 广泛影响大多数 Web 用户的安全问题提供务实的解决方案, 这些用户的网络配置并不那么复杂。
私有
网络请求的定义可以扩展为
覆盖所有跨源请求,其中 当前 URL的 host
映射到的 IP 地址所属的 IP
地址空间不是 public。这将防止
私有网络中的恶意服务器攻击其他服务器。当前认为发布这种
更改所需的工作量不值得其收益。以后可以将其作为
增量改进发布。[Issue #39]
注: 某些 私有网络请求 比其他请求更难保护。详见 § 4.4 推出困难。
2.3. 额外的 CORS 标头
Access-Control-Request-Private-Network
指示该 request 是一个 私有网络请求。
Access-Control-Allow-Private-Network
指示资源可以安全地与外部网络共享。
注: 这些标头曾短暂地被指定为
Access-Control-Request-Local-Network 和 Access-Control-Allow-Local-Network,
但由于其兼容性影响,
该决定被撤销。详见 issue
#91。
Private-Network-Access-Name
试图在私有网络访问
权限提示中向用户提供一个人类友好的名称。
Private-Network-Access-ID 标头用于
PrivateNetworkAccessPermissionDescriptor
中,以跨 IP 地址识别相同
设备。
2.4. treat-as-public-address 内容安全策略指令
treat-as-public-address 指令指示用户代理将 文档视为仿佛它是从 公共地址提供的,即使 它实际上是从 私有地址或 本地地址提供的。也 就是说,它是一种机制,使非公共文档可以放弃 在没有预检的情况下联系其他非公共文档的特权。
该指令的语法由以下 ABNF 语法描述:
directive-name = "treat-as-public-address" directive-value = ""
此指令没有报告要求;当它通过
Content-Security-Policy-Report-Only 标头交付,或位于
a
meta
元素中时,它将被完全忽略。
此指令的初始化算法如下。给定
一个 environment settings object(context)、一个
Response(response)以及
一个 policy(policy):
-
如果 policy 的 disposition 为 “
enforce”,则将 context 的 policy container 的 IP 地址空间设置为 public。
2.5. 特性检测
此规范的先前版本曾提议向
Document
和 WorkerGlobalScope
添加一个 addressSpace 枚举属性,
但由于指纹识别方面的担忧(见 issue #21),它已被移除。
无论文档是否基于 UA 实现此规范而表现不同,所有文档都应假设它已实现。
2.6. 权限提示
根据 [Issue#23] 中的讨论, 引入私有网络访问权限提示,以放宽混合内容 检查。
该权限的目标是允许从公共网站到 通过 HTTP 运行的本地网络服务器进行通信,否则这会被 安全上下文限制和混合 内容检查阻止。事实证明,将私有网络服务器迁移到 HTTPS 通常很困难,有时甚至不可能。
向 fetch() 选项包添加一个新参数:
fetch( "http://router.local/ping" , { targetAddressSpace: "private" , });
这会指示浏览器允许该 fetch,即使其方案是
非安全的,并获取到目标服务器的连接。新的 fetch() API
是向后兼容的。
注意,此特性不能被滥用来一般性地绕过混合内容。
如果远程 IP 地址不属于
targetAddressSpace 选项值所指定的 IP 地址空间,则请求失败。如果属于,
则发送 CORS 预检请求。目标服务器随后以 CORS 预检响应作出回应,
并附加以下两个标头:
Private-Network-Access-Name: <some human-readable device self-identification> Private-Network-Access-ID: <some unique and stable machine-readable ID, such as a MAC address>
例如:
Private-Network-Access-Name: "My Smart Toothbrush" Private-Network-Access-ID: "01:23:45:67:89:0A"
Private-Network-Access-ID
应为一个 48 位值,呈现为 6 个
用冒号分隔的十六进制字节。Private-Network-Access-Name
应为一个有效名称,即匹配
[ECMAScript] 正则表达式 /^[a-z0-9_-.]+$/ 的字符串。名称最多为 248 个 UTF-8
代码单元。
随后会向用户显示一个提示,请求访问目标
设备的权限。-Name 标头用于向用户
呈现一个友好字符串,以代替或补充源(通常是原始 IP 字面量)。-ID 标头用于
为权限建立键,并跨 IP
地址识别设备。确实,DHCP 的广泛使用意味着设备很可能
定期更改 IP 地址,而我们希望避免跨设备
混淆和权限疲劳。
如果用户决定授予权限,则 fetch 继续。如果不授予, 则失败。该权限随后会被持久化。属于同一 发起方源的下一个文档在声明其访问同一服务器的意图时 (如果使用原始 IP 地址,可能位于不同的源),不会触发 权限提示。初始 CORS 预检响应携带相同的 ID, 浏览器会识别出该文档已经具有访问 该服务器的权限。
如果没有现有的 -Name 或 -ID,提示只显示 IP
地址。如果用户决定授予权限,则 fetch 继续。
该权限会作为临时权限存储,并且只在当前
窗口进程中持久存在。
3. 集成
本节不具有规范性。
本文档提出对其他规范的一系列修改, 以实现上文示例中概述的缓解措施。这些 集成在此处列出以便清晰说明,但外部文档才是 规范性引用。
3.1. 安全上下文限制
UA 不得允许非安全的 public 上下文请求来自私有地址的资源, 即使私有服务器会通过预检选择加入这样的 请求。向 private 资源发出请求会带来风险,这些风险通过 确保发起请求的 client 的完整性来缓解。特别是,网络 攻击者不应能够轻易利用端点对 非安全源的同意。
混合内容检查 [MIXED-CONTENT-2] 会阻止安全上下文通过 HTTP 发出 请求,因此这一限制似乎要求私有 网络服务器迁移到 HTTPS。这通常困难到近乎不可能。引入 一个新的权限提示,以便在用户同意的情况下允许安全上下文 仍然通过 HTTP 向私有网络发出 请求。
3.2. 与 Permissions 集成
本文档定义了一个由 name "private-network-access"
标识的
强大特性。它
覆盖以下类型:
- 权限描述符类型
-
权限描述符类型,对于
"private-network-access"特性,由以下 WebIDL 接口定义,该接口 继承自默认的 权限描述符类型:dictionary :PrivateNetworkAccessPermissionDescriptor PermissionDescriptor {DOMString ; };id
3.3. 与 Mixed Content 集成
对 获取 request 是否应作为混合内容被阻止?进行修订,以向 其中一个允许条件添加 以下条件:-
request 的 origin 不是一个潜在可信源, 且 request 的目标 IP 地址空间是 private 或 local。
3.4. 与 Fetch 集成
本文档提出对 Fetch 的一些更改,其含义如下:私有网络请求 只有在其 client 是 一个安全上下文并且对目标源的 CORS 预检请求 成功时才被允许。如果请求原本会被作为混合内容阻止, 只要网站声明其访问私有 网络的意图,并且用户授予权限,就可以允许它。
注: 这包括导航。它们确实可以 用来触发 CSRF 攻击,尽管其隐蔽性不如子资源请求。
注: [FETCH] 尚未将 DNS 解析的细节集成到 Fetch 算法中,不过它确实定义了一个获取连接算法,这对本 规范而言已经足够。私有网络访问 检查会应用到新获取的连接上。考虑到 Happy Eyeballs([RFC6555]、[RFC8305])等复杂性,这些检查 对于具有多个跨越 IP 地址 空间边界的 IP 地址的主机,可能会 非确定性地通过或失败。
3.4.1. CORS 预检
HTTP fetch 算法应进行调整,以确保 从安全上下文发起的所有私有网络请求都会触发 预检。
这里的主要问题同样是,直到在 HTTP-network fetch 中获取连接之前,响应的 IP 地址空间是未知的,而该算法位于 CORS-preflight fetch 之下。
3.4.2. 获取
下面是一种潜在解决方案的草图:
-
Connection 对象被赋予一个新的 IP 地址空间属性,初始为 null。这同样适用于 WebSocket 连接。
-
在 获取连接算法中, 紧接在将 connection 追加到用户代理的连接池之前添加一个新步骤:
-
将 connection 的IP 地址空间设置为 在 connection 的远程端点 IP 地址上运行确定 IP 地址空间 算法 的结果。
远程端点概念尚未在 [FETCH] 中指定,因此这在某种程度上 仍然是在粗略描述。[Issue #33]
-
-
Request 对象被赋予一个新的 目标 IP 地址空间属性,初始为 null。
-
定义一个新的 私有网络访问检查算法。 给定一个 request request 和一个 connection connection:
-
如果 request 的 origin 是一个潜在可信 源,且 request 的当前 URL的 origin 与 request 的 origin 同源,则返回 null。
-
如果 request 的 policy container 为 null,则 返回 null。
注: 如果 request 的 policy container 为 null,则 PNA 检查不适用于 request。fetch 算法的 使用者应注意,要么将 request 的 client 设置为一个具有 非 null policy container 的 environment settings object,并让 fetch 相应地初始化 request 的 policy container,或者 直接将 request 的 policy container 设置为非 null 值。
-
如果 request 的目标 IP 地址空间不是 null,则:
-
断言:request 的目标 IP 地址空间 不是 public。
-
如果 connection 的IP 地址空间不等于 then request 的目标 IP 地址空间, 则返回 一个网络错误。
-
返回 null。
-
-
如果 connection 的IP 地址空间比 request 的 policy container 的IP 地址空间较不公共,则:
-
返回 null。
-
-
对 fetch 算法进行修订,在 request 的 policy container 被设置之后立即 添加以下步骤:
-
如果 request 的目标 IP 地址空间是 public,则返回一个网络错误。
-
-
对 HTTP-network fetch 算法进行修订,在 检查新获取的 connection 不是 failure 之后立即添加 3 个新步骤:
-
定义一个新的算法来确定预检模式,给定一个 request request 和一个布尔值 makeCORSPreflight:
-
如果 makeCORSPreflight 为 true,且以下条件之一为 true:
-
使用 request 对 request 的 method 没有方法缓存条目匹配,并且 request 的 method 不是 CORS 安全列表方法,或者 request 的 use-CORS-preflight flag 已设置。
-
在使用 request 的 header list 的 CORS-unsafe request-header names 中,至少有一个item 没有标头名称缓存条目匹配。
则:
-
如果 request 的目标 IP 地址空间不是 null,则返回 "cors+pna"。
-
否则,返回 "cors"。
-
-
如果 request 的目标 IP 地址空间不是 null,则 返回 "pna"。
-
否则,返回 "none"。
-
-
定义一个名为 HTTP-no-service-worker fetch 的新算法,它基于 HTTP fetch 中在通过 service worker 处理 fetch 后 response 仍为 null 时运行的现有 步骤,并稍作如下修订:
-
令 preflightMode 为给定 request 和 makeCORSPreflight 调用确定 预检模式的结果。
-
将整个条件 "If makeCORSPreflight is true and ..., Then:" 替换为:
-
如果 preflightMode 不是 "none",则:
-
-
将 "running CORS-preflight fetch given request" 替换为 "running CORS-preflight fetch given request and preflightMode"
-
在运行 CORS-preflight fetch 之后立即:
-
如果 preflightResponse 是一个网络错误:
-
如果 preflightResponse 的IP 地址空间为 null,则返回 preflightResponse。
-
将 request 的目标 IP 地址 空间设置为 preflightResponse 的IP 地址空间。
-
返回给定 fetchParams 运行 HTTP-no-service-worker fetch 的结果。
-
-
-
在运行 HTTP-network-or-cache fetch 之后立即:
-
如果 response 是一个网络错误,且 response 的IP 地址空间为非 null, 则:
-
将 request 的目标 IP 地址 空间设置为 preflightResponse 的IP 地址空间。
-
返回给定 fetchParams 运行 HTTP-no-service-worker fetch 的结果。
-
-
注: 因为 request 的目标 IP 地址空间在递归时被设置为 非 null 值,所以此递归最多只能深入 1 层。
-
-
CORS-preflight fetch 算法被调整为接受 一个新参数 preflightMode(默认 "cors"),并按如下方式处理新标头:
-
只有当 preflightMode 为 true 时,才将 `
Accept` 和 `Access-Control-Request-Headers` 追加到 preflight 的header list。 -
在运行 HTTP-network-or-cache fetch 之前立即:
-
如果 request 的目标 IP 地址空间 不是 null, 则:
-
在 preflight 的header list中 将 "
Access-Control-Request-Private-Network" 设置为 "true"。
-
-
-
紧接在 CORS 检查之后:
-
如果 preflightMode 是 "pna" 或 "cors+pna",
-
断言:request 的目标 IP 地址 空间 不是 null。
-
令 allow 为给定 "
Access-Control-Allow-Private-Network" 和 response 的header list,提取标头列表 值的结果。 -
如果 allow 不是 "
true",则返回一个网络错误。 -
令 requestWithoutTargetIpAddressSpace 为 request 的副本,但将其目标 IP 地址 空间设置为 null。
-
如果 获取 requestWithoutTargetIpAddressSpace 是否应作为 混合内容被阻止 返回 allowed, 则返回 null。
-
如果
Private-Network-Access-ID或Private-Network-Access-Name为 null 或 空,则令 targetId 为 request 的目标 IP 地址空间。将权限存储为临时 权限,然后返回 null。 -
令 targetId 为给定 "
Private-Network-Access-ID" 和 response 的header list,提取标头列表 值的结果。 -
如果 targetId 不是由 6 个 用冒号分隔的十六进制字节组成的字符串,则返回一个网络错误。
-
令 targetName 为给定 "
Private-Network-Access-Name" 和 response 的header list,提取标头列表 值的结果。 -
如果 targetName 不匹配 [ECMAScript] 正则表达式 /^[a-z0-9_-.]+$/,或超过 248 个 UTF-8 代码单元, 则返回一个网络错误。
-
令 state 为使用以下描述符请求使用权限的结果:
{ name: "private-network-access" , id: targetId, } -
返回 null。
-
-
-
-
最后,为了缓解 DNS 重新绑定攻击的影响(见 § 5.3 DNS 重新绑定),对 CORS 预检缓存进行调整,使其考虑 IP 地址 空间信息:
-
该新属性由创建新缓存条目算法,根据 request 的目标 IP 地址空间初始化。
-
该新属性由缓存条目匹配算法检查:
-
entry 的IP 地址空间 等于 request 的目标 IP 地址空间。
-
3.4.3. Fetch API
Fetch API 也需要调整。
-
向
RequestInfo追加一个可选的 entry, 其 key 是 targetAddressSpace,且其 value 是一个IPAddressSpace。partial dictionary RequestInit {IPAddressSpace ; };targetAddressSpace -
定义一个新的 {=targetAddressSpace=},表示 request 中的上述内容。
partial interface Request {readonly attribute IPAddressSpace ; };targetAddressSpace -
在将 this 的 request 设置为 request 之前, 向
new Request(input, init)追加以下步骤:-
如果 init["
targetAddressSpace"] 存在,则 对 init["targetAddressSpace"] 执行 switch:- public
- 什么也不做。
- private
- 将 request 的目标 IP 地址空间设置为 private。
- local
- 将 request 的目标 IP 地址空间设置为 local。
-
3.4.4. 禁用的标头名称
向禁用的请求标头名称列表中添加一个新条目:Access-Control-Request-Private-Network。
用户代理应完全控制此标头,就像它控制 其他 CORS 标头一样。
3.5. 与 WebSockets 集成
鉴于 WebSocket 握手具有与
<img> 标签大致相同的能力,
可能应在 WebSocket
握手之前发送预检请求。由于
建立 WebSocket 连接依赖于 Fetch
算法,这可能不需要额外的规范工作。[Issue #14]
此规范的先前版本曾提议简单地向 WebSocket 握手添加新 标头(见 § 2.3 额外的 CORS 标头)。然而, 这不足以完全防御 CSRF 攻击。
3.6. 与 HTML 集成
为支持 [FETCH] 中的检查,用户代理必须记住发起 网络请求的上下文的源 IP 地址空间。为此, 按如下方式修补 [HTML] 规范:
-
向 policy container struct 添加一个新的 IP 地址空间属性。
-
它最初为 public。
-
-
向 克隆 policy container算法添加一个附加步骤:
-
向 从 fetch response 创建 policy container算法添加一个附加步骤:
example.com 解析为一个
公共地址(比如
123.123.123.123),那么导航到 https://example.com/document.html 时创建的 Document
的 policy container 的IP 地址
空间属性将被设置为 public。
如果此 Document
随后嵌入一个 about:srcdoc iframe,则子
框架的 Document
的 policy container 的IP
地址空间属性将被设置为 public。
另一方面,如果 example.com 解析为一个本地地址(比如
127.0.0.1),那么导航到 https://example.com/document.html 时创建的 Document
的 policy container 的IP
地址空间属性将被设置为 local。
3.7. Worker
本节不具有规范性。
鉴于 WorkerGlobalScope
已经具有一个使用从 fetch response 创建
policy container算法填充的 policy container 字段,上述
与 Fetch 和 HTML 的集成同样适用于 worker 上下文和文档。
example.com 解析为一个
公共地址(比如
123.123.123.123),那么通过获取
https://example.com/worker.js 中的
脚本而创建的 WorkerGlobalScope
的 policy container 的IP 地址空间属性将被设置为 public。
此 worker 发起的任何 fetch request,如果获取连接到位于 private 或 local 地址空间 中的 IP 地址,那么它将成为一个私有网络请求。
遗憾的是,Service Worker 的 soft update
算法在获取更新后的脚本时,会将 request client 设置为 "null"。这会导致各种问题,并
干扰上文阐述的私有网络访问检查算法。
确实,在 fetch 期间没有
request client 可供复制 policy container。[Issue #83]
4. 实现注意事项
4.1. file URL 适用于何处?
尚不完全清楚 file URL 如何适配上文概述的
公共/私有方案。一方面,防止人们通过
在本地打开恶意 HTML 文件而伤害自己会很好;但另一方面,
在本地运行的代码在某种程度上处于任何连贯威胁模型之外。
目前,让我们倾向于将 file URL 视为 local,因为它们似乎
与回环地址上的任何内容一样,都是本地
系统的一部分。
4.2. 代理
在 Chromium 当前对此规范的实现中,代理 会影响它们所代理资源的地址 空间。具体而言, 通过代理获取的资源被认为是从 代理自身的 IP 地址获取的。
Document
由 foo.example 在一个 公共地址上提供,并由
用户代理通过位于 私有地址上的代理获取,那么该 Document
的
policy container 的 IP
地址空间会被设置为 private。
这可能允许网站通过观察其是否被允许向 私有地址发出请求,来得知 它曾被代理,这是一种隐私 信息泄露。虽然这需要正确猜测私有网络中某个资源的 URL, 但一次正确猜测就足够了。
预计这种情况相对罕见,不值得进行更多缓解。毕竟, 在现状下,所有网站都可以不受任何限制地向所有 IP 地址发出请求。
值得探索一种机制,使代理可以告诉 浏览器“无论如何请将此资源视为 public/private”,从而传递 一些关于代理后方 IP 地址的信息。这可能采用上文讨论的 CSP 指令形式,并作一些小的 修改。
4.3. HTTP 缓存
Chromium 当前对此规范的实现与 HTTP 缓存之间有两种值得注意的交互方式,具体取决于 从缓存加载的是哪种资源。
4.3.1. 主资源
由缓存的 response 构造的 document 会记住 该 response 最初加载自的 IP 地址。该 IP 地址空间会根据该 IP 地址为该 document重新派生。
在常见情况下,这意味着该 document 的 policy container 的 IP 地址空间会 原样恢复。然而,如果用户代理的配置 已更改,则派生出的 IP 地址空间可能不同。
http://foo.example/,从 1.2.3.4 加载主资源,
将其缓存,然后将生成的 document 的
policy container 的 IP
地址空间设置为 public。
随后用户代理重启,并应用新的配置,规定
1.2.3.4 应改为被分类为 私有地址。
用户代理再次导航到 http://foo.example/,并从
HTTP 缓存加载主资源。生成的 document 的 policy container 的 IP
地址空间现在被设置为 private。
4.3.2. 子资源
从 HTTP 缓存加载的子资源受 私有网络 访问检查约束。这尚未反映在上述算法中, 因为该检查只应用在 HTTP-network fetch 中。
指定并解释 Chromium 在这里的行为。[Issue #75]
有关安全影响的讨论,见 § 5.6 HTTP 缓存。
4.4. 推出困难
私有网络访问本质上弃用了对私有 网络的直接访问,转而支持更安全的、由用户代理中介的替代方案。Web 弃用很难。Chromium 在发布此规范部分内容的 过程中遇到了许多绊脚石。
尤其是,发布对从 非安全上下文中的 private IP 地址空间 到 local IP 地址空间的 fetch 的限制,已被证明 尤其困难,而且收益较低。确实,利用这类 fetch 要求攻击者已经在私有网络中站稳脚跟,这 大幅提高了攻击难度。因此,Chromium 暂时将这些 fetch 从限制中豁免,选择专注于来自 public IP 地址空间的 fetch。
5. 安全和隐私注意事项
5.1. 用户介入
本文档中的提案仅确保设备同意 来自公共互联网的访问。用户代理可以确保用户也同意 这种访问,因为拒绝这种访问可能符合其利益, 即使设备本身会允许它。
这种介入可以通过显式权限授予来完成,也可以通过某种类似于 PAKE 的配对仪式来完成,或通过用户代理可能设计出的任何其他巧妙界面来完成。
5.2. 混合内容
本文档的提案所添加的 CORS 限制并不能取代 混合内容检查 [MIXED-CONTENT-2]。通过 CORS 预检请求获得的设备同意是必要的,但并不充分。
注: [MIXED-CONTENT-2] 不会阻止
安全上下文从其 origin 的 host 为 localhost,或位于
127.0.0.0/8 或 ::1/128 块中的 IP
地址的资源获取内容。另见 潜在可信源的定义。
希望从 public 页面获取 private 或 local 资源(来自上述 例外以外的主机)的开发者,必须确保 连接是安全的。这可能涉及类似 [PLEX] 的解决方案,其中 Web PKI 证书被颁发给用户特定的域名, 这些域名随后解析到仅在用户 私有网络中有意义的私有 IP 地址。
某些消费级路由器 通过简单地阻止解析到非公共 IP 地址的 DNS 响应,实现了过于激进的 DNS 重新绑定攻击防护。这给像 [PLEX] 这样的解决方案造成了 障碍。相关 issue 中讨论了变通方案。[Issue #23]
这个问题空间已经被探索过几次,似乎值得 在某个时候重新审视。可以设想类似上文暗示的配对仪式, 或 [SECURE-LOCAL-COMMUNICATION] 中提出的某些想法。
5.3. DNS 重新绑定
这里描述的缓解措施作用于用户 代理在加载特定资源时实际连接到的 IP 地址。必须 对每个新建立的连接执行此检查,否则 DNS 重新绑定攻击 可能会诱骗用户代理泄露其不应泄露的信息。
对 CORS 预检缓存的修改旨在缓解 这一攻击向量。
5.4. 缓解范围
本文档中的提案只是缓解针对私有 Web 服务的攻击,无法完全解决它们。例如,路由器的 基于 Web 的管理接口必须自行设计和实现以防御 CSRF,不应依赖按照本文档规定行为的 UA。 鉴于当前私有 Web 服务实现质量的现实, 本文档所规定的缓解措施是必要的,但即便所有 UA 都实现了这种缓解,供应商也不应认为自己免除了责任。
5.5. 跨网络混淆
大多数私有网络不能彼此通信,但此规范 将它们全都视为属于 private IP 地址空间。 更进一步,私有 地址只在使用它们的私有网络中有意义。同一个 IP 地址可能在 两个不同网络中指向完全不同的设备。
这为跨网络攻击打开了大门:
-
用户连接到两个不同的私有网络:一个家庭 Wi-Fi 网络 和一个公司 VPN。他们的智能冰箱被黑了。他们打开智能冰箱的 Web 接口,随后该接口对通过 VPN 可访问的公司网站执行 CSRF 攻击。
-
用户连接到恶意网吧 Wi-Fi,该 Wi-Fi 要求用户 保持一个强制门户页面打开。他们合上笔记本电脑,回家后再次打开 笔记本电脑。该强制门户页面(无论仍然打开,还是 在用户代理恢复先前状态时从缓存重新加载) 对用户的家庭设备执行 CSRF 攻击。
-
用户连接到恶意网吧 Wi-Fi,其强制门户 网站从
http://router.example/popular-library.js缓存一个恶意脚本 (网吧网络管理员 运行一个恶意 DNS 服务器),并设置很长的过期时间。用户关闭 电脑,回家后重新启动电脑,并访问其路由器的管理接口http://router.example,该接口 嵌入/popular-library.js。恶意脚本在 管理接口的第一方上下文中加载。
这些攻击都不是新颖的——它们只是此 规范局限性的示例。
潜在缓解措施 需要注意网络变化,并 清除特定于先前网络的状态。以完全通用的 方式做到这一点,很可能除了清除所有状态之外不可能实现。也许可以 达成一种实用的折中。[Issue #28]
5.6. HTTP 缓存
5.6.1. 将检查应用于子资源
以下内容 不再准确。实现经验 表明,与缓存集成甚至在保护 网络资源免受 CSRF 攻击方面也很有用。本节需要重写。[Issue #75]
缓存的子资源当前不受此规范保护,即使 HTTP 缓存记住了源 IP 地址,而该地址可在 私有网络访问检查算法中,于 HTTP-network-or-cache fetch 期间使用。
虽然修复这种明显不一致可能是个好主意,但它与 此规范的主要目标——防止 CSRF 攻击——并不直接相关。
最多,恶意公共网站可能能够确定用户 过去是否访问过特定的私有网站。这种对用户 隐私的攻击并不比现状更糟。
此外,由于 HTTP 缓存分区,子资源只能被 能够复制 网络分区键的恶意攻击者从缓存中加载,该键属于 缓存条目。攻击者实现这一点的一种方式 是操纵 DNS(另见 § 5.3 DNS 重新绑定),以便 冒充最初嵌入该缓存资源的顶级站点。
http://router.example,它由 192.168.1.1 提供服务。该网站嵌入一个
来自 http://router.example/$BRAND-logo.png 的徽标,并将其缓存。
随后,恶意攻击者将 router.example 重新绑定到
攻击者控制的公共 IP 地址,并以某种方式诱骗用户再次访问
http://router.example。该恶意网站尝试
嵌入该徽标,并监视加载是否成功。如果成功,
攻击者就确定了用户路由器的品牌。
5.6.2. HTTP 缓存投毒
虽然此规范旨在保护私有网络服务器不 接收来自公共网站的请求,但 DNS 重新绑定可用于通过 对未认证资源进行缓存投毒来实施类似攻击。
伪装成 http://router.com 的攻击者可以在
http://router.com/totally-legit.js 缓存一个恶意脚本。之后,当用户导航到
http://router.com/ 时,页面可能请求这个被投毒的脚本,并在一个
较不公共的 IP 地址空间中执行
攻击者代码。
该攻击通过缓存分区得到部分缓解,
因为缓存分区使得攻击者必须先将顶级浏览上下文
导航到 http://router.com/ 才能缓存资源,这缺乏隐蔽性。
它也并非私有网络访问所特有,而是
明文 HTTP 缺乏认证和完整性保护的一种症状。
6. IANA 注意事项
内容安全策略指令注册表应使用以下 指令和引用进行更新 [RFC7762]:
treat-as-public-address
7. 致谢
与 Ryan Sleevi、Chris Palmer 和 Justin Schuh 的对话有助于充实 此提案的轮廓。希望他们不会太讨厌它。 Mathias Karlsson 有幸成为压垮骆驼的最后一根 稻草,而 Brian Smith 对由此产生的 讨论串的贡献一如既往地有用。