| name | replay-audit |
| description | 修改协议认证、会话建立、密码验证、首包处理代码后触发。 |
Skill: 反重放攻击防御审计
在修改涉及协议认证、会话建立、密码验证、首包处理代码后,必须对变更部分执行以下审计清单。审查系统通过录制并重放合法认证包来检测代理服务器,也会利用流加密的延展性实施选择密文攻击。
审查系统的检测原理
审查系统的重放攻击是一个两阶段过程:
- 捕获阶段:被动嗅探发现可疑连接后,从真实客户端捕获合法的握手/认证包(如首包的密码 hash、加密认证数据)
- 重放阶段:在数小时甚至数天后,向同一服务器重发捕获的包。如果服务器返回加密流量(而非标准 Web 响应),审查系统通过响应长度和行为确认目标为代理
关键洞察:重放防御不仅要防止"即时重放"(同一 nonce 重复使用),还要防止"延迟重放"(数天后的重放)。这要求 nonce/序列号的记忆具有一定的持久性。
审查系统还会利用选择密文攻击:发送 256 个连接,每个连接在原始捕获包的基础上改变一个字节。如果使用无认证的流加密,某个连接碰巧使得服务端不立即断开,审查系统即可确认该位置的有效性并逐步推断密钥流。AEAD 的认证标签可以彻底阻止此类攻击。
触发条件
- 修改了协议认证逻辑(SOCKS5/Trojan/SS2022 等)
- 修改了会话建立流程
- 修改了 AEAD nonce 管理逻辑
- 修改了首包认证处理
- 修改了 nonce/序列号记忆机制
- 修改了多用户认证或密码验证代码
- 修改了认证失败的响应行为
审计清单
1. AEAD 认证完整性
核心原理:AEAD(Authenticated Encryption with Associated Data)同时提供机密性和完整性保证。任何绕过 AEAD 认证的加密方案都会暴露在选择密文攻击之下。审查系统的选择密文攻击非常实际:对捕获的密文逐字节变异后发送 256 个变体,如果某个变体未被服务端拒绝(即未被检测为篡改),审查系统即可确认该位置的有效性,逐步推断密钥流甚至恢复明文。
1.1 认证加密强制
| 检查项 | 说明 |
|---|
| AEAD 算法强制 | 所有加密通道是否使用 AEAD(AES-128-GCM/AES-256-GCM/ChaCha20-Poly1305)?任何绕过 AEAD 的加密都是安全隐患 |
| 流加密禁用 | 是否杜绝使用流加密(RC4/AES-CFB/AES-OFB/CTR 模式无认证)?流加密的延展性允许审查系统实施 256 变体选择密文攻击:对密文的每个字节尝试 256 种可能值,观察哪个变体不被服务端拒绝。由于流加密无完整性保护,攻击者可以精确控制解密后的明文变化 |
| ECB 模式禁用 | 是否杜绝 ECB 模式?ECB 模式不仅无认证,且相同的明文块总是产生相同的密文块,模式可辨 |
| MAC-then-Encrypt 禁用 | 是否避免 MAC-then-Encrypt 模式(先加密后认证)?此模式在验证前先解密,可能泄露 padding oracle。必须使用 Encrypt-then-MAC 或直接使用 AEAD |
1.2 密文完整性验证
| 检查项 | 说明 |
|---|
| 解密前 Tag 验证 | AEAD 解密时是否在密文完整性验证通过后才进行解密操作?跳过 tag 验证直接解密会接受篡改的密文,攻击者可以注入任意数据 |
| 密文长度预检 | 解密前是否检查密文长度 >= AEAD tag 长度?AES-GCM 和 ChaCha20-Poly1305 的 tag 均为 16 字节。短于 tag 的密文应立即拒绝,不得进入解密流程 |
| Tag 验证失败拒绝 | AEAD tag 验证失败时是否拒绝整个密文?不得"部分接受"或跳过未验证的字段 |
1.3 附加数据(AD)绑定
| 检查项 | 说明 |
|---|
| AD 包含地址信息 | AEAD 的 Additional Data 是否绑定了目标地址信息?未绑定地址的密文可以被攻击者截取后重定向到不同目标 |
| AD 包含序列号 | AD 是否包含序列号或帧计数器?序列号绑定可以防止帧重排和帧删除攻击 |
| AD 包含方向标识 | AD 是否区分读写方向?方向标识防止一个方向的密文被注入到另一个方向 |
| AD 包含协议版本 | AD 是否包含协议版本号?版本绑定防止跨版本重放 |
1.4 解密失败处理
| 检查项 | 说明 |
|---|
| 静默丢弃 | AEAD 解密失败时是否静默丢弃数据,不返回错误细节?不得向对端报告"解密失败"、"tag 不匹配"等错误信息 |
| 无错误细节 | 解密失败的日志是否仅记录失败事件,不记录密文内容、nonce 值、或部分解密结果? |
| 安全回落 | 解密失败后是否回落到标准 Web 服务行为?直接断开连接(RST/FIN)暴露代理身份 |
2. 时间戳防重放
核心原理:时间戳是防重放的第一道防线。协议载荷中嵌入客户端时间戳,服务端验证其与当前时间的偏差。即使攻击者完整捕获了合法的认证包,重放时时间戳会超出服务端的接受窗口。但时间戳防御有明确局限:窗口内的即时重放和窗口内的延迟重放(时钟同步误差)无法仅靠时间戳防御,必须结合 nonce 记忆机制。
2.1 客户端时间戳
| 检查项 | 说明 |
|---|
| 时间戳包含 | 协议载荷中是否包含客户端时间戳?无时间维度的认证数据可被无限期重放 |
| 时间戳精度 | 时间戳精度是否为秒级?毫秒级精度虽更安全但对时钟同步要求更高。推荐秒级 Unix 时间戳 |
| 时间戳位置 | 时间戳是否在加密保护范围内?明文时间戳可被攻击者篡改以绕过窗口检查 |
2.2 服务端窗口校验
| 检查项 | 说明 |
|---|
| 窗口大小 | 服务端时间窗口是否为 30 秒?过大的窗口(如 300 秒)增加即时重放风险,过小的窗口(如 5 秒)在高延迟网络或时钟偏移较大的客户端上产生误拒 |
| 时钟偏移容忍 | 时间窗口是否容忍合理的服务端-客户端时钟偏差?NTP 同步通常有 ±1 秒的误差,但某些网络环境下偏差可能达到 ±10 秒 |
| 窗口对称性 | 时间窗口是否前后对称(如 ±30 秒)?仅检查"时间戳不晚于当前"是不够的,攻击者可以预生成时间戳在未来的包 |
| 未来时间戳处理 | 时间戳明显在未来(超出窗口上限)时是否拒绝?某些实现仅检查"不早于",忽略了未来时间戳 |
2.3 延迟重放防御
| 检查项 | 说明 |
|---|
| 窗口内 nonce 重复检测 | 即使时间戳在窗口内,是否仍检测同一 nonce 的重复使用?时间戳窗口只能防御"窗口外的重放",窗口内的重放必须靠 nonce 记忆防御 |
| 延迟重放窗口 | Nonce 记忆的时间窗口是否至少覆盖 72 小时?审查系统可能在数天甚至数周后重放捕获的包。推荐的延迟重放窗口:72 小时(平衡安全性和内存开销) |
| 跨越重启防护 | 服务端重启后 nonce 记忆是否恢复?如果 nonce 记忆仅在内存中,服务端重启后窗口内的包可被重放。建议将 nonce 记忆持久化到磁盘或使用基于时间窗口的确定性检测 |
3. Nonce 管理安全
核心原理:AEAD 的安全性依赖于"同一密钥下 nonce 绝不重复"这一不可违反的约束。nonce 重复的后果分两阶段:首先,密钥流复用使攻击者可直接恢复明文差分(C1 ⊕ C2 = P1 ⊕ P2);其次,利用两组认证标签联立方程可恢复认证子密钥 H,进而伪造任意消息的认证标签。机密性和完整性同时崩溃。ChaCha20-Poly1305 的 nonce 复用同样可恢复 Poly1305 的一次性密钥。Nonce 管理的任何疏忽都是灾难性的。nonce 的密码学原理和算法特定边界(字节序、TLS 1.3 记录上限 2^24、tag 长度验证、密钥长度匹配)详见 crypto-audit Section 1。
3.1 Nonce 唯一性保证
| 检查项 | 说明 |
|---|
| 计数器初始化 | 每个会话的 nonce 计数器是否从协商值(或 0)开始?不得使用未初始化的值或随机值(随机值有碰撞概率) |
| 原子递增 | Nonce 计数器递增是否为原子操作?多线程或多路复用场景下,非原子的递增可能导致 nonce 重复 |
| 密钥绑定 | Nonce 是否与特定密钥绑定?密钥轮换后 nonce 计数器必须重置,否则新密钥可能与旧密钥使用相同的 nonce |
3.2 方向隔离
| 检查项 | 说明 |
|---|
| 独立计数器 | 两个方向(C→S 和 S→C)的 nonce 是否独立计数?共享同一计数器会导致方向间 nonce 重复 |
| 方向标识 | Nonce 构造中是否包含方向标识位?即使计数器值相同,方向标识也能保证全局唯一 |
3.3 Nonce 溢出处理
| 检查项 | 说明 |
|---|
| 溢出检测 | 序列号接近上限时是否拒绝新连接?不得在溢出后回绕到 0(回绕必然导致 nonce 重复)。96-bit nonce 的溢出边界:计数器达到 2^96 - 1 时必须停止 |
| 提前预警 | 序列号接近上限(如达到 2^96 - 1 - 1024)时是否触发密钥轮换?在溢出之前主动轮换比溢出后拒绝更优雅 |
| 回绕禁止 | 是否杜绝任何形式的 nonce 回绕?回绕 = nonce 重复 = AEAD 安全性崩溃 |
3.4 UDP Nonce 处理
| 检查项 | 说明 |
|---|
| 显式 nonce | UDP 场景是否使用显式 nonce(即 nonce 值包含在数据包中)?UDP 无序交付,不得依赖隐式递增("第 N 个包用第 N 个 nonce"),因为包可能乱序到达 |
| Salt + 时间戳组合 | UDP 是否使用 salt + 时间戳的组合来构造唯一 nonce?仅用计数器不足以保证 UDP 场景的唯一性 |
| 去重检测 | UDP 是否对接收到的包进行去重检测?相同的 nonce/salt 不应被接受两次 |
3.5 Nonce 记忆持久化(关键)
两种重放检测模式:
- 计数器 nonce(如 Trojan/VLESS):仅在单个会话内有效,会话结束后密钥销毁,无需跨会话记忆。服务端重启不影响安全性(新会话使用新密钥,旧 nonce 无效)
- 连接级 salt(如 SS2022):使用
salt_pool(TTL 60 秒,per-thread)检测 salt 重放。72 小时记忆窗口的建议适用于 salt 模式,计数器 nonce 模式不需要跨会话持久化
核心原理:仅在单次会话内检测 nonce 重复完全无法防御延迟重放。审查系统可以在会话结束后数天重放捕获的包,此时服务端已不记得该 nonce 曾被使用。Nonce 记忆的生命周期必须覆盖审查系统可能的延迟重放窗口。
| 检查项 | 说明 |
|---|
| 记忆生命周期 | Nonce 记忆池的生命周期是否足够长?仅在单次会话内记忆无法防御延迟重放。推荐记忆窗口至少 72 小时 |
| 时间窗口滑动缓存 | 是否使用基于时间窗口的滑动缓存实现 nonce 记忆?推荐的实现方式:时间窗口滑动缓存(如环形缓冲区),每个 nonce/salt 与时间戳一起存储,超过窗口的条目自动淘汰 |
| 内存开销评估 | Nonce 记忆的内存开销是否在可接受范围内?以每天 1000 个连接、72 小时窗口、每条目 16 字节 nonce + 8 字节时间戳计算:1000 × 3 × 24 = 约 72000 条目 × 24 字节 ≈ 1.7 MB,完全可接受 |
| 淘汰策略 | 超出时间窗口的 nonce 条目是否被自动淘汰?不得无限增长,否则长期运行后内存耗尽 |
| 重启后恢复 | 服务端重启后 nonce 记忆是否可恢复?如果 nonce 记忆仅在进程内存中,服务端重启后窗口内的旧包可被重放。可选方案:持久化到磁盘,或在重启后强制所有客户端重新握手 |
3.6 Nonce 空间大小
| 检查项 | 说明 |
|---|
| 空间足够大 | Nonce 空间是否足够大以避免高并发下的碰撞?96-bit nonce 空间有 2^96 ≈ 7.9 × 10^28 种可能,碰撞概率极低。64-bit nonce 空间在生日攻击下约 2^32 ≈ 43 亿 次操作后有 50% 碰撞概率,不推荐 |
| UDP salt 长度 | UDP 场景的 salt 长度是否足够?推荐至少 8 字节(64-bit)salt。过短的 salt 在高并发下碰撞概率显著增加 |
4. 选择密文攻击防御
核心原理:选择密文攻击(CCA)是审查系统最强大的主动攻击手段。攻击者构造特定的密文并发送给服务端,通过观察服务端的响应行为(接受/拒绝、响应时间、响应内容)推断密钥信息。AEAD 的认证标签提供了 CCA 安全性——篡改的密文会被检测并拒绝。但实现层面必须确保拒绝行为不泄露额外信息。
4.1 篡改密文检测
| 检查项 | 说明 |
|---|
| 立即拒绝 | AEAD tag 验证失败时是否立即拒绝整个密文?不得尝试"部分接受"——即使明文的部分内容看似有效,未通过认证的数据一律丢弃 |
| 全量验证 | 是否对整个密文进行完整性验证后才处理明文?不得先处理明文再验证,否则攻击者可能通过明文处理的副作用获取信息 |
4.2 侧信道沉默
| 检查项 | 说明 |
|---|
| 时间一致性 | 解密失败后的处理时间是否与正常解密一致?不得在失败时立即返回(快路径)或在失败时做额外的错误处理(慢路径)。推荐:解密失败后执行与正常路径等量的计算再返回 |
| 资源使用一致 | 解密失败的内存分配、CPU 使用是否与正常解密一致?显著的资源使用差异可被通过资源耗尽时间差间接测量 |
| 连接行为一致 | 解密失败后的连接关闭行为是否与正常处理一致?不得在失败时发送 RST 而在成功时发送 FIN |
4.3 错误信息控制
| 检查项 | 说明 |
|---|
| 无错误细节 | 错误响应是否不包含任何解密失败的原因?禁止返回 "decryption failed"、"tag mismatch"、"invalid ciphertext" 等错误描述 |
| 无错误码区分 | 所有解密失败是否返回相同的错误表现?不得区分 "密文太短"、"tag 不匹配"、"AD 不匹配" 等不同错误类型 |
| 日志控制 | 解密失败的日志是否仅记录事件(如 "auth failed")而不记录密文内容、nonce 值、部分解密结果? |
4.4 256 变体攻击防御
核心原理:审查系统对捕获的密文逐字节变异:对于密文的每个字节位置,发送 256 个连接(每个连接该字节取 0-255 中的一个值,其余不变)。如果使用无认证的流加密,某个变体碰巧使得服务端不立即断开(即解密后的明文碰巧通过了后续校验),审查系统即可确认该位置的有效性。重复此过程可以逐步推断密钥流。
| 检查项 | 说明 |
|---|
| AEAD 一致拒绝 | 所有 256 个变体是否都被 AEAD 认证一致性拒绝?AEAD 的认证标签保证:密文任意一位翻转都会导致 tag 验证失败,且失败行为完全一致 |
| 拒绝行为完全相同 | 所有变体的拒绝行为(响应内容、响应时间、连接关闭方式)是否完全相同?如果某个变体的拒绝方式略有不同(如不同的错误消息或不同的延迟),攻击者可能利用该差异推断信息 |
| 连接状态彻底清理 | 认证失败后是否彻底清理所有连接状态?残留状态(如部分解密的缓冲区、未清理的会话上下文)可能被后续的探针利用 |
5. 首包认证
核心原理:首包是连接建立后客户端发送的第一个数据包,通常包含密码/认证信息和目标地址。首包是审查系统重放攻击的主要目标:捕获合法首包后在适当时机重放。首包认证的安全性直接决定了整个协议的抗重放能力。
5.1 密码哈希
| 检查项 | 说明 |
|---|
| 哈希传输 | 首包中的密码是否以哈希形式传输(如 SHA224)?明文密码即使加密传输,一旦加密被攻破即可直接使用。哈希形式增加了一层间接性 |
| 哈希算法选择 | 密码哈希是否使用至少 SHA-224 或更强的算法?MD5 和 SHA-1 已被证明存在碰撞攻击。推荐 SHA-224(截断到 28 字节,节省带宽且安全性充足) |
| 哈希加盐 | 如果密码哈希需要持久化验证,是否使用了加盐哈希?无盐哈希(如直接 SHA224(password))受彩虹表攻击 |
5.2 首包完整性
| 检查项 | 说明 |
|---|
| 最小长度检查 | 首包长度是否满足最小认证信息要求?过短的首包(如仅包含密码 hash 但缺少地址信息)不应触发认证逻辑,应按格式错误处理 |
| 字段边界验证 | 首包中各字段(密码 hash、地址类型、地址、端口)的边界是否经过严格验证?越界读取可能导致缓冲区溢出或信息泄漏 |
| 地址类型校验 | 首包中的地址类型(IPv4/IPv6/域名)是否在解析前校验?未知的地址类型应静默拒绝 |
5.3 地址验证
| 检查项 | 说明 |
|---|
| 格式合法性 | 目标地址格式是否合法?IPv4 地址应为 4 字节,IPv6 应为 16 字节,域名长度应在 1-253 之间 |
| 静默拒绝 | 格式错误的地址是否静默拒绝(回落到标准 Web 服务)?不得返回 "invalid address" 等错误信息 |
| 禁止地址限制 | 是否验证目标地址不在禁止列表中?连接到 localhost、内网地址、或元数据地址(如 169.254.169.254)应被静默拒绝 |
5.4 认证失败静默
| 检查项 | 说明 |
|---|
| 伪装为正常服务器 | 密码不匹配时服务端是否伪装为正常 Web 服务器?不得直接断开连接(RST/FIN)或返回空响应 |
| 回落到真实服务 | 认证失败后是否将连接透明转发到真实 Web 服务?这使审查系统看到的是标准的 Web 服务行为 |
| 响应延迟一致 | 认证失败的响应延迟是否与认证成功后代理响应的延迟接近?显著的延迟差异可被时序分析利用 |
5.5 首包与时戳绑定
| 检查项 | 说明 |
|---|
| 时间维度绑定 | 首包认证数据是否包含时间戳或单调递增的序列号?无时间维度的认证数据(如固定密码 hash)可被无限期重放,无论 AEAD 多么安全 |
| Nonce 绑定 | 首包是否绑定了唯一的 nonce 或随机盐?固定的首包格式(相同密码总是产生相同的首包内容)是重放攻击的理想目标 |
| 会话绑定 | 首包中的认证数据是否与会话上下文(如会话 ID、握手摘要)绑定?未绑定的认证数据可以在不同会话间重放 |
6. 密码认证安全
核心原理:密码认证是代理协议的基础安全机制。实现层面的任何疏忽(读取过多数据、错误响应不标准、缓冲区溢出)都可能暴露代理身份或导致安全漏洞。密码认证必须精确、标准化、且不泄漏信息。
6.1 精确读取
| 检查项 | 说明 |
|---|
| 分步读取 | 是否分步精确读取用户名和密码?以 SOCKS5 为例:先读 2 字节(版本+方法数),再读 NLEN 字节(用户名),再读 PLEN 字节(密码) |
| 禁止预读 | 是否杜绝预读固定最大长度(如一次性读取 255+255+2 字节)?预读会阻塞等待未到达的数据,增加响应延迟,且可能读取到后续的代理数据导致混乱 |
| 长度字段验证 | 读取前是否验证长度字段的合理性?如 SOCKS5 的 ULEN/PLEN 应在 1-255 之间,0 值或超大值应被拒绝 |
6.2 凭据哈希
| 检查项 | 说明 |
|---|
| 哈希后比较 | 密码是否在接收后先哈希(或直接使用哈希值)再与存储的哈希比较?不得以明文形式存储或比较密码 |
| 常量时间比较 | 密码/哈希的比较是否使用常量时间函数(如 CRYPTO_memcmp)?标准 memcmp 在首个不等字节处返回,响应时间与匹配前缀长度成正比,可被逐字节二分搜索利用 |
6.3 认证失败响应
| 检查项 | 说明 |
|---|
| 标准协议拒绝 | 认证失败是否返回标准协议格式的拒绝响应?如 SOCKS5 应返回 \x05\xff(无接受方法),不得返回自定义错误码或断开连接 |
| 响应完整性 | 拒绝响应是否为完整的标准格式?不得返回部分响应或格式错误的响应 |
6.4 缓冲区安全
| 检查项 | 说明 |
|---|
| 缓冲区边界 | 认证缓冲区是否足够大以容纳最大合法输入?SOCKS5 用户名/密码最大 255 字节 |
| 溢出保护 | 是否检查所有长度字段不超过缓冲区大小?不得信任客户端提供的长度值而不加验证 |
| 内存安全 | 认证数据是否在认证完成后从内存中清除?密码哈希等敏感数据不应长时间驻留在内存中 |
7. 多用户端口分区预言风险(关键)
核心原理:在多用户共享同一服务端口的场景下,攻击者可以构造一个密文尝试在多个候选密钥下解密。如果服务端对不同用户的认证失败行为存在差异(不同的响应内容、不同的响应时间、不同的连接关闭方式),攻击者可以利用这种差异逐步缩小密钥搜索空间——这就是分区预言攻击(Partitioning Oracle Attack)。
7.1 攻击模型详解
- 多个用户共享同一个服务器端口,每个用户使用不同的密码/UUID 进行认证
- 攻击者捕获一个合法客户端的认证包
- 攻击者修改该包的某些字节,构造一个新的密文
- 攻击者将此密文发送给服务器,观察服务器的行为
- 如果该密文恰好在某个候选密钥下解密成功(即使明文无意义),攻击者获得了一个"分区提示"
- 更重要的是:如果该密文在所有候选密钥下都解密失败,但服务端对不同用户的表现不同(如遍历用户列表时某个用户匹配到更多字节就表现不同),攻击者可以逐步排除不可能的密钥
7.2 防御要求
| 检查项 | 说明 |
|---|
| 认证失败行为完全一致 | 所有用户的认证失败行为必须完全相同——相同的响应字节、相同的响应时间、相同的连接关闭方式。这是最关键的防御要求 |
| 无用户标识泄漏 | 错误响应中不得包含任何用户标识信息。不得区分"用户不存在"和"密码错误"——统一返回"认证失败" |
| 遍历时间恒定 | 多用户匹配的时间是否恒定?如果遍历用户列表时找到匹配就立即返回,而找不到匹配就遍历完整列表,响应时间差异泄漏了用户是否存在的信息 |
| 响应字节完全相同 | 所有认证失败的 TCP 响应字节是否完全相同?不得因用户数量或匹配状态影响响应字节内容 |
| 考虑单用户单端口 | 如果多用户一致性难以保证,是否考虑了单用户单端口的架构?每个用户独占一个端口可以从根本上消除分区预言攻击面 |
TLS 1.3 0-RTT 重放风险
TLS 1.3 的 early data(0-RTT)面临天然的重放风险 — 攻击者可以截获并重放 0-RTT 数据,服务端无法区分原始请求和重放。Reality 的自定义 TLS 1.3 握手不使用 0-RTT,审计应确认此点。如果未来任何方案支持 0-RTT,必须实现重放保护(如服务器端 anti-replay 窗口或 single-use ticket)。
8. 重放响应行为分析防御
核心原理:即使服务端检测到了重放攻击并拒绝处理,如果拒绝行为本身与正常 Web 服务器不一致,审查系统仍然可以通过响应行为分析确认目标为代理。防御的核心思想是:重放被拒绝后的响应必须与正常 Web 服务器的响应完全一致——审查系统无法区分"这是一个合法的 Web 服务器恰好在处理正常请求"和"这是一个代理服务器在模拟 Web 服务器的行为来掩盖重放拒绝"。
8.1 响应长度一致性
| 检查项 | 说明 |
|---|
| 长度不可区分 | 合法请求的代理响应和重放请求的拒绝响应,其长度是否做了混淆?如果重放被拒绝时响应长度明显不同于正常的代理加密流量长度,审查系统可以通过长度差异推断代理存在 |
| 填充一致性 | 是否使用填充使不同类型的响应长度趋同?推荐策略:将拒绝响应填充到与正常代理响应长度分布一致的范围内 |
| 无固定长度短响应 | 重放拒绝是否避免了固定长度的短响应?固定长度的短响应(如每次都返回 200 字节的 HTML)本身就是可疑特征——正常 Web 服务器的响应长度分布是多样的 |
8.2 拒绝行为模拟
| 检查项 | 说明 |
|---|
| 模拟标准 Web 服务 | 重放被检测到后,服务端是否模拟标准 Web 服务器的行为?最佳策略:将重放的请求完整转发到真实 Web 服务器,返回真实 Web 内容 |
| 禁止空响应 | 重放拒绝是否避免了返回空响应?空响应(0 字节)在审查系统看来极为可疑——正常 Web 服务器不会对合法请求返回空响应 |
| 禁止断开连接 | 重放拒绝是否避免了直接断开连接(RST/FIN)?正常 Web 服务器不会在收到合法 TLS 记录后立即断开 |
| 回落目标可达 | 回落目标(真实 Web 服务器)是否始终可达?如果回落目标不可达,重放请求的响应行为会与正常请求不一致(连接超时 vs 正常响应),行为差异暴露代理 |
8.3 时间侧信道防御
| 检查项 | 说明 |
|---|
| 检测开销隐藏 | 重放检测(如 nonce 查表)的时间开销是否被掩盖?nonce 查表是 O(1) 操作,但仍然有一定开销。推荐:在检测路径上添加与正常路径等量的延迟 |
| 响应时间分布一致 | 重放请求的响应时间分布是否与正常请求的响应时间分布一致?如果正常请求响应时间在 50-200ms 范围,重放请求的响应时间也应在同一范围 |
| 无快速拒绝路径 | 是否杜绝了"检测到重放就立即返回"的快速路径?快速路径的响应时间显著低于正常路径,时间差异可被精确测量 |
8.4 响应内容模拟
| 检查项 | 说明 |
|---|
| 合理的 Web 内容 | 重放被拒绝后的响应是否包含合理的 Web 内容(如 HTML 页面、HTTP 响应头)? |
| 内容多样性 | 多次重放的响应内容是否有多样性?每次返回完全相同的 HTML 页面是可疑的——正常 Web 服务器对于不同的请求路径通常返回不同的内容 |
| TLS 层合规 | 如果重放请求通过 TLS 到达,拒绝响应是否通过 TLS 加密返回?不得在 TLS 层返回明文错误(如 TLS Alert),因为审查系统可以观察到 TLS Alert 的模式 |
交叉引用
crypto-audit 覆盖了本 skill 未深入探讨的 AEAD 密码学原理(算法特定边界、字节序、TLS 1.3 记录上限、tag 长度验证)、HKDF 密钥派生、常量时间操作、证书与签名维度
security-audit 提供了安全审计 skills 的编排指南,确定修改特定代码时应按何种顺序执行哪些审计
反模式代码示例与审计流程
审计的具体执行步骤和所有禁止/正确的代码模式对照详见 anti-patterns.md。