Skip to main content

7. Security Considerations (安全考虑)

本节描述了 QPACK 的潜在安全关注领域:

  • 使用压缩作为基于长度的预言机 (Length-Based Oracle) 来验证关于压缩到共享压缩上下文中的秘密的猜测.

  • 通过耗尽解码器的处理或内存容量导致的拒绝服务 (Denial of Service).

7.1 Probing Dynamic Table State (探测动态表状态)

QPACK 通过利用 HTTP 等协议中固有的冗余性来减少字段区段的编码大小. 其最终目标是减少发送 HTTP 请求或响应所需的数据量.

用于编码头部和尾部字段的压缩上下文可以被能够同时定义要编码和传输的字段并观察这些字段编码后长度的攻击者探测. 当攻击者可以同时做到这两点时, 他们可以自适应地修改请求以确认关于动态表状态的猜测. 如果猜测被压缩为更短的长度, 攻击者可以观察编码后的长度并推断猜测是正确的.

即使在传输层安全协议 (Transport Layer Security Protocol, [TLS]) 和 QUIC 传输协议 ([QUIC-TRANSPORT]) 上, 这也是可能的, 因为虽然 TLS 和 QUIC 为内容提供机密性保护, 但它们仅为该内容的长度提供有限的保护.

注意: 填充方案 (Padding Schemes) 仅对具有这些能力的攻击者提供有限的保护, 可能只会迫使攻击者增加猜测次数以了解与给定猜测相关的长度. 填充方案也直接对抗压缩, 因为它增加了传输的比特数.

像 CRIME ([CRIME]) 这样的攻击证明了这些一般攻击者能力的存在. 该特定攻击利用了 DEFLATE ([RFC1951]) 基于前缀匹配移除冗余的事实. 这允许攻击者一次确认一个字符的猜测, 将指数时间攻击减少为线性时间攻击.

7.1.1 Applicability to QPACK and HTTP (对 QPACK 和 HTTP 的适用性)

QPACK 缓解但并未完全防止基于 CRIME ([CRIME]) 模型的攻击, 方法是强制猜测匹配整个字段行而不是单个字符. 攻击者只能了解猜测是否正确, 因此攻击者被简化为对与给定字段名称关联的字段值进行暴力猜测.

因此, 恢复特定字段值的可行性取决于值的熵 (Entropy). 结果, 具有高熵的值不太可能被成功恢复. 然而, 具有低熵的值仍然容易受到攻击.

只要两个互不信任的实体 (Mutually Distrustful Entities) 控制放置在单个 HTTP/3 连接上的请求或响应, 这种性质的攻击就是可能的. 如果共享的 QPACK 压缩器允许一个实体向动态表添加条目, 而另一个实体在编码选定的字段行时引用这些条目, 那么攻击者 (第二个实体) 可以通过观察编码输出的长度来了解表的状态.

例如, 当中介 (Intermediary) 执行以下任一操作时, 可能会发生来自互不信任实体的请求或响应:

  • 在单个连接上从多个客户端向源服务器发送请求, 或

  • 从多个源服务器获取响应并将它们放在指向客户端的共享连接上.

Web 浏览器还需要假设由不同 Web 源 ([RFC6454]) 在同一连接上发出的请求是由互不信任的实体发出的. 涉及互不信任实体的其他场景也是可能的.

7.1.2 Mitigation (缓解措施)

需要对头部或尾部字段保密的 HTTP 用户可以使用具有足够熵的值, 使猜测变得不可行. 然而, 这作为通用解决方案是不切实际的, 因为它强制所有 HTTP 用户采取措施来缓解攻击. 这将对 HTTP 的使用方式施加新的约束.

与其对 HTTP 用户施加约束, QPACK 的实现可以约束如何应用压缩以限制动态表探测的潜力.

理想的解决方案根据构造消息的实体隔离对动态表的访问. 添加到表中的字段值归属于某个实体, 并且只有创建特定值的实体才能提取该值.

为了提高此选项的压缩性能, 某些条目可能被标记为公共 (Public). 例如, Web 浏览器可能会在所有请求中使 Accept-Encoding 头部字段的值可用.

没有良好了解字段值来源的编码器可能会对具有相同字段名称和不同值的许多字段行引入惩罚 (Penalty). 此惩罚可能导致大量尝试猜测字段值会导致该字段在未来的消息中不再与动态表条目进行比较, 从而有效地防止进一步的猜测.

此响应可能与字段值的长度成反比. 对于给定字段名称禁用对动态表的访问可能对较短的值更快发生或具有更高的概率, 而不是对较长的值.

此缓解措施在两个端点之间最有效. 如果消息由中介重新编码而不知道哪个实体构造了给定消息, 则中介可能会无意中合并原始编码器特意保持分离的压缩上下文.

注意: 从动态表中简单地删除与字段对应的条目可能无效, 如果攻击者有可靠的方法导致值被重新安装. 例如, 在 Web 浏览器中加载图像的请求通常包括 Cookie 头部字段 (此类攻击的潜在高价值目标), 并且网站可以轻松强制加载图像, 从而刷新动态表中的条目.

7.1.3 Never-Indexed Literals (永不索引字面量)

实现还可以选择通过不压缩敏感字段并将其值编码为字面量来保护这些字段.

拒绝将字段行插入动态表仅在所有跳 (Hops) 上都避免这样做时才有效. 永不索引字面量位 (Never-Indexed Literal Bit) (参见第 4.5.4 节) 可用于向中介发出信号, 表明特定值有意作为字面量发送.

中介禁止 (MUST NOT) 使用另一种会对其进行索引的表示重新编码使用设置了 'N' 位的字面表示的值. 如果使用 QPACK 进行重新编码, 则必须 (MUST) 使用设置了 'N' 位的字面表示. 如果使用 HPACK 进行重新编码, 则必须 (MUST) 使用永不索引字面表示 (参见 [RFC7541] 的第 6.2.3 节).

标记字段值永不应被索引的选择取决于几个因素. 由于 QPACK 不能防止猜测整个字段值, 短或低熵值更容易被对手恢复. 因此, 编码器可能选择不索引具有低熵的值.

编码器还可能选择不索引被认为对恢复具有高价值或敏感的字段的值, 例如 Cookie 或 Authorization 头部字段.

相反, 编码器可能更喜欢索引那些如果暴露几乎没有价值的字段的值. 例如, User-Agent 头部字段通常在请求之间不会变化, 并发送到任何服务器. 在这种情况下, 确认已使用特定 User-Agent 值几乎没有价值.

请注意, 这些决定使用永不索引字面表示的标准将随着时间的推移而演变, 随着新攻击的发现.

7.2 Static Huffman Encoding (静态 Huffman 编码)

目前没有已知的针对静态 Huffman 编码的攻击. 一项研究表明, 使用静态 Huffman 编码表会产生信息泄漏; 然而, 同一研究得出结论, 攻击者无法利用这种信息泄漏来恢复任何有意义的信息量 (参见 [PETAL]).

7.3 Memory Consumption (内存消耗)

攻击者可以尝试导致端点耗尽其内存. QPACK 旨在限制端点分配的峰值和稳定内存量.

QPACK 使用动态表的最大大小和最大阻塞流数量的定义来限制编码器可以导致解码器消耗的内存量. 在 HTTP/3 中, 这些值由解码器通过设置参数 SETTINGS_QPACK_MAX_TABLE_CAPACITY 和 SETTINGS_QPACK_BLOCKED_STREAMS 分别控制 (参见第 3.2.3 节和第 2.1.2 节). 动态表大小的限制考虑了存储在动态表中的数据大小, 加上少量开销余量. 对阻塞流数量的限制只是解码器所需最大内存量的代理. 实际的最大内存量将取决于解码器用于跟踪每个阻塞流的内存量.

解码器可以通过为动态表的最大大小设置适当的值来限制用于动态表的状态内存量. 在 HTTP/3 中, 这是通过为 SETTINGS_QPACK_MAX_TABLE_CAPACITY 参数设置适当的值来实现的. 编码器可以通过选择比解码器允许的更小的动态表大小并向解码器发出信号 (参见第 4.3.1 节) 来限制其使用的状态内存量.

解码器可以通过为最大阻塞流数量设置适当的值来限制用于阻塞流的状态内存量. 在 HTTP/3 中, 这是通过为 SETTINGS_QPACK_BLOCKED_STREAMS 参数设置适当的值来实现的. 有被阻塞风险的流不会在编码器上消耗额外的状态内存.

编码器分配内存来跟踪未确认字段区段中的所有动态表引用. 实现可以通过仅使用它希望跟踪的对动态表的引用数量来直接限制状态内存量; 不需要向解码器发送信号. 然而, 限制对动态表的引用将降低压缩效率.

编码器或解码器消耗的临时内存量可以通过顺序处理字段行来限制. 解码器实现在解码字段区段时不需要保留完整的字段行列表. 如果编码器实现使用单遍算法, 则在编码字段区段时不需要保留完整的字段行列表. 请注意, 出于其他原因, 应用程序可能需要保留完整的字段行列表; 即使 QPACK 不强制发生这种情况, 应用程序约束也可能使这成为必要.

虽然协商的动态表大小限制占 QPACK 实现可以消耗的大部分内存, 但由于流量控制而无法立即发送的数据不受此限制影响. 实现应该限制未发送数据的大小, 特别是在选择发送内容的灵活性有限的解码器流上. 对过多未发送数据的可能响应可能包括限制对等方打开新流的能力, 仅从编码器流读取, 或关闭连接.

7.4 Implementation Limits (实现限制)

QPACK 的实现需要确保整数的大值、整数的长编码或长字符串字面量不会产生安全弱点.

实现必须为它接受的整数值以及编码长度设置限制; 参见第 4.1.1 节. 同样, 它必须为它接受的字符串字面量的长度设置限制; 参见第 4.1.2 节. 这些限制应该 (SHOULD) 足够大以处理 HTTP 实现可以配置为接受的最大单个字段.

如果实现遇到大于其能够解码的值, 则如果在请求流上, 这必须 (MUST) 被视为类型为 QPACK_DECOMPRESSION_FAILED 的流错误, 或者如果在编码器或解码器流上, 则必须 (MUST) 被视为相应类型的连接错误.