摘要

  • 早期 TLS 能在既有加密通道内执行第二次握手,却没有用密码学方式证明两次握手属于同一段会话历史。
  • RFC 5746 把上一次 Finished 消息中的验证值带入下一次握手;TLS 1.3 则直接取消了重新协商。

一条连接里的两个发送者

攻击者先与服务器建立一条正常的 TLS 连接,再发送自己选择的应用层前缀。这个前缀可以是一条尚未结束的请求,也可以是等待后续数据补全的命令。随后,攻击者把受害者发起的新 TLS 握手转送到这条已经加密的服务器端连接中。

在受害者看来,这是自己与真实服务器之间的首次握手;在服务器看来,它却是攻击者连接上的一次重新协商。新握手完成后,攻击者仍读不到受害者后续的加密流量。真正失效的是归属判断:服务器可能把攻击者的前缀与受害者经过认证的数据拼成同一个应用请求,并把整段行为算在受害者名下。

RFC 5746 的攻击时序图把三个概念分开了:传输连接没有中断,不等于密码学身份没有变化;数据始终被加密,也不等于前后两段数据拥有同一个作者。套接字的连续存在不能回答应用最关心的问题——新的认证状态究竟继承了哪一段历史。

让上一轮握手成为证据

修复方案为每条连接保存三个状态:是否启用安全重新协商的标志,以及客户端和服务器在紧邻的上一次握手中发送的 verify_data。这些值来自 Finished 消息,已经承载了双方对上一轮握手 transcript 的确认。

RFC 5746 定义了类型为 0xff01 的 renegotiation_info 扩展。首次握手时,其中的绑定字段为空,表示双方支持安全重新协商,但不存在需要继承的旧握手。真正重新协商时,ClientHello 携带此前的客户端 verify_data;ServerHello 则携带客户端与服务器两个旧值的连接。任一方发现扩展缺失或数值不符,都必须终止握手。

攻击者能够搬运受害者的新握手,却无法让它自动带上攻击者那条连接的旧证明。因此,修复并非因为第二次握手被旧密钥包裹就相信它,而是要求它准确、可验证地指明自己接续的前一轮协商。

修复已部署协议的兼容性代价

当时一些旧实现遇到不认识的 ClientHello 扩展会直接失败,尽管规范要求忽略未知扩展。为减少升级阻力,RFC 5746 又定义了 TLS_EMPTY_RENEGOTIATION_INFO_SCSV。它放在密码套件列表中,却不是可协商的真实密码套件,只用来表达与空 renegotiation_info 相同的初始支持信号。

兼容手段没有消除全部不确定性。未回应扩展的服务器可能允许不安全的重新协商,也可能早已禁用全部重新协商,因而并不易受该攻击。客户端无法仅靠 TLS 判断是哪一种情况。若要取得最强保证,就应在得不到确认时断开;若要兼容更多旧系统,就必须接受无法证明的状态。标准修复因此也把“缺少证据时怎么办”变成明确的运营选择。

TLS 1.3 选择删除

RFC 8446 禁止 TLS 1.3 重新协商。连接建立后若在不应出现的时点收到 ClientHello,服务器必须以错误终止连接。更新密钥和握手后认证仍然存在,但被拆成各自边界清楚的机制,不再恢复旧式的通用重新协商。

RFC 9325 对 TLS 1.2 保留了严格要求:客户端和服务器都必须实现 renegotiation_info,服务器不确认时客户端必须结束连接。三份 RFC 没有给出 2009 年实际受害数量,也没有统计今天仍使用重新协商的系统规模;它们清楚证明了协议缺口、修复办法和后来的删除路径。历史留下的规则是:身份或密钥发生转换时,协议必须证明它继承了什么;加密通道本身不是这份证明。

来源