私有网络访问

社区组报告草案,

本版本:
https://wicg.github.io/private-network-access/
问题跟踪:
GitHub
规范内联
编辑:
(Google)
前任编辑:
(Google)

摘要

本文档规定了对 Fetch 和 HTML 的修改,其目的是 减轻客户端内部网络中的设备和服务器无意暴露给整个 Web 所带来的风险。

此规范之前称为 CORS-RFC1918。

本文档状态

此规范由 Web 平台孵化器 社区组发布。 它既不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C 社区 贡献者许可协议(CLA),存在有限的选择退出权,并适用其他条件。 了解更多关于 W3C 社区和商务组的信息。

1. 引言

本节不具有规范性。

虽然 [RFC1918] 二十多年来一直规定了 “私有”和 “公共”互联网地址之间的区别,但用户代理在将二者相互隔离方面 进展甚微。公共 互联网中的网站可以向内部设备和服务器发起请求,这会促成 多种恶意行为,包括对用户路由器的攻击,例如 [DRIVE-BY-PHARMING][SOHO-PHARMING][CSRF-EXPLOIT-KIT] 中所记录的那些攻击。

这里,我们提出一种针对这类攻击的缓解措施,该措施将要求 内部设备显式选择加入来自公共 互联网的请求。

1.1. 目标

总体目标是防止用户代理无意中促成 对运行在用户本地内联网中的设备,或直接运行在 用户机器上的服务的攻击。例如,我们希望缓解针对以下目标的攻击:

1.2. 示例

1.2.1. 默认安全

MegaCorp Inc 的路由器有一个相当严重的 CSRF 漏洞,允许通过导航到 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. 选择加入

MegaCorp Inc 的某些设备实际上因各种原因需要与 公共互联网通信。它们可以通过在响应 CORS 预检请求时发送适当的 CORS 标头, 来显式选择加入接收来自 互联网的请求。

当公共互联网中的网站向该设备发出请求时, 用户代理会判定请求方是 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
...
MegaCorp Inc. 在 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. 混合内容

MegaCorp Inc 的某些设备缺少唯一的源, 使其无法 通过安全通道(例如 HTTPS)连接。然而,这些设备可能 仍然希望与公共网站通信。如果用户显式允许, 它们可以选择加入与安全公共网站的不安全连接。

当公共互联网中具有 潜在可信源的网站 从该设备请求数据时,用户代理会识别出 请求方为 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 地址空间,它可以是 三个不同值之一:

  1. local:仅包含本地 主机。换言之,其目标对每个 设备都不同的地址。

  2. private:包含 仅在当前网络内有意义的地址。换言之, 其目标会根据网络位置而不同的地址。

  3. public:包含所有 其他地址。换言之,其目标对于 IP 网络上的全球所有设备都相同的地址。

为方便起见,我们还定义以下术语:

  1. 本地 地址是其 IP 地址空间local 的 IP 地址。

  2. 私有 地址是其 IP 地址空间private 的 IP 地址。

  3. 公共 地址是其 IP 地址空间public 的 IP 地址。

如果满足以下任一条件,则 IP 地址空间 lhsIP 地址空间 rhs 较不公共

  1. lhslocal,且 rhsprivatepublic

  2. lhsprivate,且 rhspublic

确定 IP 地址空间,给定一个 IP 地址 address,运行以下步骤:

  1. 如果 address 属于 ::ffff:0:0/96 “IPv4 映射地址” 地址块,则将 address 替换为其嵌入的 IPv4 地址。

  2. 对于 非公共 IP 地址 块”表中的每个 row

    1. 如果 address 属于 row 的地址块,则返回 row 的 地址空间。

  3. 返回 public

非公共 IP 地址块
地址块 名称 引用 地址空间
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当前 URLhost 映射到一个 IP 地址, 且该 IP 地址的 IP 地址 空间request更不公开,相较于其 策略容器IP 地址 空间

将 IP 地址分类到三个宽泛的地址空间是一种 不完善且理论上并不严谨的方法。它是一个代理指标,用来确定 两个网络端点是否应被允许自由通信, 换言之,端点 A 是否可以在不经由 端点 C 上的用户代理中转的情况下从端点 B 到达。

这种方法存在一些缺陷:

即便如此,此规范旨在为一个 广泛影响大多数 Web 用户的安全问题提供务实的解决方案, 这些用户的网络配置并不那么复杂。

私有 网络请求的定义可以扩展为 覆盖所有跨源请求,其中 当前 URLhost 映射到的 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-NetworkAccess-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 objectcontext)、一个 Responseresponse)以及 一个 policypolicy):

  1. 如果 policydisposition 为 “enforce”,则将 contextpolicy containerIP 地址空间设置为 public

2.5. 特性检测

此规范的先前版本曾提议向 DocumentWorkerGlobalScope 添加一个 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 是否应作为混合内容被阻止?进行修订,以向 其中一个允许条件添加 以下条件:
  1. requestorigin 不是一个潜在可信源, 且 request目标 IP 地址空间privatelocal

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. 获取

下面是一种潜在解决方案的草图:

  1. Connection 对象被赋予一个新的 IP 地址空间属性,初始为 null。这同样适用于 WebSocket 连接。

  2. 获取连接算法中, 紧接在将 connection 追加到用户代理的连接池之前添加一个新步骤:

    1. connectionIP 地址空间设置为 在 connection 的远程端点 IP 地址上运行确定 IP 地址空间 算法 的结果。

      远程端点概念尚未在 [FETCH] 中指定,因此这在某种程度上 仍然是在粗略描述。[Issue #33]

  3. Request 对象被赋予一个新的 目标 IP 地址空间属性,初始为 null。

  4. Response 对象被赋予一个新的 IP 地址空间属性,其值为 一个 IP 地址 空间,初始为 null。

  5. 定义一个新的 私有网络访问检查算法。 给定一个 request request 和一个 connection connection

    1. 如果 requestorigin 是一个潜在可信 源,且 request当前 URLoriginrequestorigin 同源,则返回 null。

    2. 如果 requestpolicy container 为 null,则 返回 null。

      注: 如果 requestpolicy container 为 null,则 PNA 检查不适用于 requestfetch 算法的 使用者应注意,要么将 requestclient 设置为一个具有 非 null policy containerenvironment settings object,并让 fetch 相应地初始化 requestpolicy container,或者 直接将 requestpolicy container 设置为非 null 值。

    3. 如果 request目标 IP 地址空间不是 null,则:

      1. 断言request目标 IP 地址空间 不是 public

      2. 如果 connectionIP 地址空间不等于 then request目标 IP 地址空间, 则返回 一个网络错误

      3. 返回 null。

    4. 如果 connectionIP 地址空间requestpolicy containerIP 地址空间较不公共,则:

      1. error 为一个网络错误

      2. 如果 requestclient 不是一个安全上下文(包括其为 null 的情况), 则返回 error

      3. errorIP 地址空间属性设置为 connectionIP 地址空间

      4. 返回 error

    5. 返回 null。

  6. fetch 算法进行修订,在 requestpolicy container 被设置之后立即 添加以下步骤:

    1. 如果 request目标 IP 地址空间public,则返回一个网络错误

  7. HTTP-network fetch 算法进行修订,在 检查新获取的 connection 不是 failure 之后立即添加 3 个新步骤:

    1. responseIP 地址空间设置为 connectionIP 地址空间

    2. privateNetworkAccessCheckResult 为对 fetchParamsrequestconnection 运行私有网络访问检查的结果。

    3. 如果 privateNetworkAccessCheckResult 是一个网络错误,则返回 privateNetworkAccessCheckResult

  8. 定义一个新的算法来确定预检模式,给定一个 request request 和一个布尔值 makeCORSPreflight

    1. 如果 makeCORSPreflight 为 true,且以下条件之一为 true:

      则:

      1. 如果 request目标 IP 地址空间不是 null,则返回 "cors+pna"。

      2. 否则,返回 "cors"。

    2. 如果 request目标 IP 地址空间不是 null,则 返回 "pna"。

    3. 否则,返回 "none"。

  9. 定义一个名为 HTTP-no-service-worker fetch 的新算法,它基于 HTTP fetch 中在通过 service worker 处理 fetch 后 response 仍为 null 时运行的现有 步骤,并稍作如下修订:

    1. preflightMode 为给定 requestmakeCORSPreflight 调用确定 预检模式的结果。

    2. 将整个条件 "If makeCORSPreflight is true and ..., Then:" 替换为:

      1. 如果 preflightMode 不是 "none",则:

    3. 将 "running CORS-preflight fetch given request" 替换为 "running CORS-preflight fetch given request and preflightMode"

    4. 在运行 CORS-preflight fetch 之后立即:

      1. 如果 preflightResponse 是一个网络错误

        1. 如果 preflightResponseIP 地址空间为 null,则返回 preflightResponse

        2. request目标 IP 地址 空间设置为 preflightResponseIP 地址空间

        3. 返回给定 fetchParams 运行 HTTP-no-service-worker fetch 的结果。

    5. 在运行 HTTP-network-or-cache fetch 之后立即:

      1. 如果 response 是一个网络错误,且 responseIP 地址空间为非 null, 则:

        1. request目标 IP 地址 空间设置为 preflightResponseIP 地址空间

        2. 返回给定 fetchParams 运行 HTTP-no-service-worker fetch 的结果。

    注: 因为 request目标 IP 地址空间在递归时被设置为 非 null 值,所以此递归最多只能深入 1 层。

  10. CORS-preflight fetch 算法被调整为接受 一个新参数 preflightMode(默认 "cors"),并按如下方式处理新标头:

    1. 只有当 preflightMode 为 true 时,才将 `Accept` 和 `Access-Control-Request-Headers` 追加到 preflightheader list

    2. 在运行 HTTP-network-or-cache fetch 之前立即:

      1. 如果 request目标 IP 地址空间 不是 null, 则:

        1. preflightheader list中 将 "Access-Control-Request-Private-Network" 设置为 "true"。

    3. 紧接在 CORS 检查之后:

      1. 如果 preflightMode 是 "pna" 或 "cors+pna",

        1. 断言request目标 IP 地址 空间 不是 null。

        2. allow 为给定 "Access-Control-Allow-Private-Network" 和 responseheader list提取标头列表 值的结果。

        3. 如果 allow 不是 "true",则返回一个网络错误

        4. requestWithoutTargetIpAddressSpacerequest 的副本,但将其目标 IP 地址 空间设置为 null。

        5. 如果 获取 requestWithoutTargetIpAddressSpace 是否应作为 混合内容被阻止 返回 allowed, 则返回 null。

        6. 如果 Private-Network-Access-IDPrivate-Network-Access-Name 为 null 或 空,则令 targetIdrequest目标 IP 地址空间。将权限存储为临时 权限,然后返回 null。

        7. targetId 为给定 "Private-Network-Access-ID" 和 responseheader list提取标头列表 值的结果。

        8. 如果 targetId 不是由 6 个 用冒号分隔的十六进制字节组成的字符串,则返回一个网络错误

        9. targetName 为给定 "Private-Network-Access-Name" 和 responseheader list提取标头列表 值的结果。

        10. 如果 targetName 不匹配 [ECMAScript] 正则表达式 /^[a-z0-9_-.]+$/,或超过 248 个 UTF-8 代码单元, 则返回一个网络错误

        11. state 为使用以下描述符请求使用权限的结果:

          {
            name: "private-network-access",
            id: targetId,
          }
          
        12. 如果 state"denied", 则返回一个网络 错误

        13. 返回 null。

  11. 最后,为了缓解 DNS 重新绑定攻击的影响(见 § 5.3 DNS 重新绑定),对 CORS 预检缓存进行调整,使其考虑 IP 地址 空间信息:

    1. 向每个缓存条目添加一个新的 IP 地址空间属性 (null 或一个 IP 地址空间)。

    2. 该新属性由创建新缓存条目算法,根据 request目标 IP 地址空间初始化。

    3. 该新属性由缓存条目匹配算法检查:

      1. entryIP 地址空间 等于 request目标 IP 地址空间

3.4.3. Fetch API

Fetch API 也需要调整。

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] 规范:

  1. policy container struct 添加一个新的 IP 地址空间属性。

    1. 它最初为 public

  2. 克隆 policy container算法添加一个附加步骤:

    1. cloneIP 地址空间设置为 policyContainerIP 地址空间

  3. 从 fetch response 创建 policy container算法添加一个附加步骤:

    1. resultIP 地址空间设置为 responseIP 地址空间

假设 example.com 解析为一个 公共地址(比如 123.123.123.123),那么导航到 https://example.com/document.html 时创建的 Documentpolicy containerIP 地址 空间属性将被设置为 public

如果此 Document 随后嵌入一个 about:srcdoc iframe,则子 框架的 Documentpolicy containerIP 地址空间属性将被设置为 public

另一方面,如果 example.com 解析为一个本地地址(比如 127.0.0.1),那么导航到 https://example.com/document.html 时创建的 Documentpolicy containerIP 地址空间属性将被设置为 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 中的 脚本而创建的 WorkerGlobalScopepolicy containerIP 地址空间属性将被设置为 public

此 worker 发起的任何 fetch request,如果获取连接到位于 privatelocal 地址空间 中的 IP 地址,那么它将成为一个私有网络请求

遗憾的是,Service Workersoft 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 地址获取的。

如果由 Documentfoo.example 在一个 公共地址上提供,并由 用户代理通过位于 私有地址上的代理获取,那么该 Documentpolicy containerIP 地址空间会被设置为 private

Document 随后将被允许向浏览器可访问的其他 私有地址发出请求。

这可能允许网站通过观察其是否被允许向 私有地址发出请求,来得知 它曾被代理,这是一种隐私 信息泄露。虽然这需要正确猜测私有网络中某个资源的 URL, 但一次正确猜测就足够了。

预计这种情况相对罕见,不值得进行更多缓解。毕竟, 在现状下,所有网站都可以不受任何限制地向所有 IP 地址发出请求。

值得探索一种机制,使代理可以告诉 浏览器“无论如何请将此资源视为 public/private”,从而传递 一些关于代理后方 IP 地址的信息。这可能采用上文讨论的 CSP 指令形式,并作一些小的 修改。

4.3. HTTP 缓存

Chromium 当前对此规范的实现与 HTTP 缓存之间有两种值得注意的交互方式,具体取决于 从缓存加载的是哪种资源。

4.3.1. 主资源

由缓存的 response 构造的 document 会记住 该 response 最初加载自的 IP 地址。该 IP 地址空间会根据该 IP 地址为该 document重新派生。

在常见情况下,这意味着该 documentpolicy containerIP 地址空间会 原样恢复。然而,如果用户代理的配置 已更改,则派生出的 IP 地址空间可能不同。

用户代理导航到 http://foo.example/,从 1.2.3.4 加载主资源, 将其缓存,然后将生成的 documentpolicy containerIP 地址空间设置为 public

随后用户代理重启,并应用新的配置,规定 1.2.3.4 应改为被分类为 私有地址

用户代理再次导航到 http://foo.example/,并从 HTTP 缓存加载主资源。生成的 documentpolicy containerIP 地址空间现在被设置为 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] 不会阻止 安全上下文从其 originhostlocalhost,或位于 127.0.0.0/8::1/128 块中的 IP 地址的资源获取内容。另见 潜在可信源的定义。

希望从 public 页面获取 privatelocal 资源(来自上述 例外以外的主机)的开发者,必须确保 连接是安全的。这可能涉及类似 [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 地址可能在 两个不同网络中指向完全不同的设备。

这为跨网络攻击打开了大门:

这些攻击都不是新颖的——它们只是此 规范局限性的示例。

潜在缓解措施 需要注意网络变化,并 清除特定于先前网络的状态。以完全通用的 方式做到这一点,很可能除了清除所有状态之外不可能实现。也许可以 达成一种实用的折中。[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

本文档(见 § 2.4 treat-as-public-address 内容安全策略 指令

7. 致谢

与 Ryan Sleevi、Chris Palmer 和 Justin Schuh 的对话有助于充实 此提案的轮廓。希望他们不会太讨厌它。 Mathias Karlsson 有幸成为压垮骆驼的最后一根 稻草,而 Brian Smith 对由此产生的 讨论串的贡献一如既往地有用。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性参考文献

[CSP3]
Mike West; Antonio Sartori. 内容安全策略 第 3 级. URL: https://w3c.github.io/webappsec-csp/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch 标准. 现行标准. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[MIXED-CONTENT]
Emily Stark; Mike West; Carlos IbarraLopez. 混合内容. URL: https://w3c.github.io/webappsec-mixed-content/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[RFC7762]
M. West. 内容安全策略指令注册表的初始分配. 2016 年 1 月. 资料性. URL: https://www.rfc-editor.org/rfc/rfc7762
[SECURE-CONTEXTS]
Mike West. 安全上下文. URL: https://w3c.github.io/webappsec-secure-contexts/
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. URL: https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL 标准. 现行标准. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行标准. URL: https://webidl.spec.whatwg.org/
[WEBSOCKETS]
Adam Rice. WebSockets 标准. 现行标准. URL: https://websockets.spec.whatwg.org/

资料性参考文献

[AVASTIUM]
Avast:一个可通过 Web 访问的 RPC 端点可以启动 'SafeZone'(也称 Avastium),这是移除了关键安全检查的 Chromium 分支。. URL: https://bugs.chromium.org/p/project-zero/issues/detail?id=679
[CSRF-EXPLOIT-KIT]
Kafeine. 专用于 CSRF Pharming 的漏洞利用套件. URL: http://malware.dontneedcoffee.com/2015/05/an-exploit-kit-dedicated-to-csrf.html
[DRIVE-BY-PHARMING]
Sid Stamm; Zulfikar Ramzan; Markus Jakobsson. Drive-By Pharming. URL: https://link.springer.com/chapter/10.1007/978-3-540-77048-0_38
[IPV4-REGISTRY]
IANA IPv4 特殊用途地址注册表. URL: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
[IPV6-REGISTRY]
IANA IPv6 特殊用途地址注册表. URL: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
B. Carpenter; S. Cheshire; R. Hinden. 在地址字面量和统一资源标识符中表示 IPv6 区域 标识符. 2023 年 4 月 12 日. Internet-Draft. URL: https://www.ietf.org/archive/id/draft-ietf-6man-rfc6874bis-07.html
[MIXED-CONTENT-2]
Emily Stark; Mike West; Carlos Ibarra Lopez. 混合内容第 2 级. 2020 年 10 月 14 日. W3C 首次公开工作草案. URL: https://w3c.github.io/webappsec-mixed-content/level2.html
[PLEX]
Filippo Valsorda. Plex 如何为所有用户使用 HTTPS. URL: https://blog.filippo.io/how-plex-is-doing-https-for-all-its-users/
[RFC1122]
R. Braden, Ed.. 互联网主机需求 - 通信层. 1989 年 10 月. 互联网标准. URL: https://www.rfc-editor.org/rfc/rfc1122
[RFC1918]
Y. Rekhter; et al. 私有互联网的地址 分配. 1996 年 2 月. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc1918
[RFC2544]
S. Bradner; J. McQuaid. 网络互连设备的 基准测试方法. 1999 年 3 月. 资料性. URL: https://www.rfc-editor.org/rfc/rfc2544
[RFC3927]
S. Cheshire; B. Aboba; E. Guttman. IPv4 链路本地地址的动态 配置. 2005 年 5 月. 提议标准. URL: https://www.rfc-editor.org/rfc/rfc3927
[RFC4193]
R. Hinden; B. Haberman. 唯一本地 IPv6 单播 地址. 2005 年 10 月. 提议标准. URL: https://www.rfc-editor.org/rfc/rfc4193
[RFC4291]
R. Hinden; S. Deering. IP 版本 6 地址 架构. 2006 年 2 月. 标准草案. URL: https://www.rfc-editor.org/rfc/rfc4291
[RFC6555]
D. Wing; A. Yourtchenko. Happy Eyeballs:双栈主机的 成功. 2012 年 4 月. 提议标准. URL: https://www.rfc-editor.org/rfc/rfc6555
[RFC6598]
J. Weil; et al. IANA 为共享 地址空间保留的 IPv4 前缀. 2012 年 4 月. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc6598
[RFC6762]
S. Cheshire; M. Krochmal. 组播 DNS. 2013 年 2 月. 提议标准. URL: https://www.rfc-editor.org/rfc/rfc6762
[RFC8305]
D. Schinazi; T. Pauly. Happy Eyeballs 版本 2: 通过并发实现更好的连接性. 2017 年 12 月. 提议标准. URL: https://www.rfc-editor.org/rfc/rfc8305
[SECURE-LOCAL-COMMUNICATION]
“与本地网络设备安全通信” 会议纪要:TPAC,2015. URL: http://www.w3.org/2015/10/28-local-minutes.html
[SOHO-PHARMING]
Team Cymru. SOHO Pharming. URL: https://331.cybersec.fun/TeamCymruSOHOPharming.pdf
[TREND-MICRO]
TrendMicro 监听 localhost 的 node.js HTTP 服务器 可以执行命令. URL: https://bugs.chromium.org/p/project-zero/issues/detail?id=693

IDL 索引

enum IPAddressSpace { "public", "private", "local" };

dictionary PrivateNetworkAccessPermissionDescriptor
    : PermissionDescriptor {
  DOMString id;
};

partial dictionary RequestInit {
  IPAddressSpace targetAddressSpace;
};

partial interface Request {
  readonly attribute IPAddressSpace targetAddressSpace;
};

问题索引

一旦对 IPv4 映射 IPv6 地址的访问 被完全阻止,就移除此特殊情况。[Issue #36]
私有网络 请求的定义可以扩展为 覆盖所有跨源请求,其中 当前 URLhost 映射到的 IP 地址所属的 IP 地址空间不是 public。这将防止私有 网络中的恶意服务器攻击其他服务器。当前认为发布这种 更改所需的工作量不值得其收益。以后可以将其作为 增量改进发布。[Issue #39]
远程端点概念尚未在 [FETCH] 中指定,因此这在某种程度上仍然是在粗略 描述。[Issue #33]
鉴于 WebSocket 握手具有与 <img> 标签大致相同的能力,可能应在 WebSocket 握手之前发送预检请求。由于 建立 WebSocket 连接依赖于 Fetch 算法,这可能不需要额外的规范工作。[Issue #14]
遗憾的是,Service Workersoft update 算法在获取更新后的 脚本时,会将 request client 设置为 "null"。 这会导致各种问题,并 干扰上文阐述的私有网络访问 检查算法。 确实,在 request client 可供复制 policy container 的情况下,在 fetch 期间并不存在这样的对象。[Issue #83]
在获得实现经验后重新评估此项。
指定并解释 Chromium 在这里的行为。[Issue #75]
某些消费级路由器 通过简单地阻止解析到非公共 IP 地址的 DNS 响应,实现了过于激进的 DNS 重新绑定攻击防护。这给像 [PLEX] 这样的解决方案造成了障碍。 相关 issue 中讨论了变通方案。[Issue #23]
潜在缓解措施 需要注意网络变化,并 清除特定于先前网络的状态。以完全通用的 方式做到这一点,很可能除了清除所有状态之外不可能实现。也许可以 达成一种实用的折中。[Issue #28]
以下内容不再准确。实现经验 表明,与缓存集成甚至在保护 网络资源免受 CSRF 攻击方面也很有用。本节需要重写。[Issue #75]