2. TLS记录协议层参数审计归纳总结
说起记录协议层的审计,我敢说这是整个TLS审计里最容易被低估的一块。很多人只盯着握手协议里的密码套件,觉得记录层不就是分片、加密、MAC嘛,没什么好查的。但我在项目里吃过一次亏——对方所有握手参数都合规,唯独记录层的最大片段长度设得太小,导致性能大幅下降,审计报告里还被标注了一个“配置不优化”的建议项。
所以这一章我们就来把这个“小透明”翻个底朝天。你想想看,所有应用数据都要经过记录层这个管道,管道本身的参数才是决定安全与性能平衡的关键。
2.1 记录层参数的核心审计点
记录层参数虽然不多,但每个都有明确的合规要求和安全隐患。我习惯把它们分成四类来审:
- 内容类型(Content Type):决定这个记录是握手消息、警报还是应用数据。合规的系统只会使用
20-23(ChangeCipherSpec、Alert、Handshake、Application Data)。出现0或255等非法值,基本可以断定是协议实现缺陷或中间人注入。 - 版本号(Protocol Version):每个记录头里的版本字段必须与握手协商一致。我记得审计过一个“降级中间件”,它在记录层写死了
03 01(TLS 1.0),但握手层明明是 TLS 1.2。 - 有效载荷(Plaintext/Fragment):最大长度默认
2^14字节。超过这个值,合规实现必须拒绝。另外,压缩后的数据长度也有上限,但现在基本没人开压缩了。 - 序列号(Sequence Number):这是一个隐式参数——审计里最容易疏忽的地方。每个方向有独立的 64 位序列号,用于防重放。如果审计日志里出现序列号回绕或跳跃异常,可能是重放攻击的先兆。
2.2 版本协商与记录头验证
记录头只有 5 个字节:内容类型(1 字节)+ 版本(2 字节)+ 长度(2 字节)。但就是这 5 个字节,我见过无数合规问题。
版本字段的审计规范,说白了就一句话:记录头里的版本号必须等于握手阶段协商出的版本。但很多实现在这个细节上不够严格——
| 审计场景 | 预期行为 | 不合规示例 |
|---|---|---|
| ClientHello 阶段 | 记录头版本 03 03(TLS 1.2) |
写 03 01 试试降级 |
| ServerHello 响应 | 记录头版本 = 协商版本 | 落后于握手层协商版本 |
| 应用数据传输 | 记录头版本固定为 03 03 或更高 |
回到 03 00(SSL 3.0 标记) |
| 重协商记录 | 记录头版本必须保持与会话一致 | 重协商期间临时改变版本号 |
我个人建议:审计日志里至少捕获每个记录头的版本字段,并计算它与握手协商结果的偏差。如果偏差 > 0,直接标记为严重告警。
2.3 有效载荷加密与MAC审计
TLS 1.2 及之前版本,每个应用数据记录都包含 MAC || 明文 || 填充(CBC 模式);TLS 1.3 则去掉了独立的 MAC 层,用 AEAD 一并完成加密认证。审计时我关注两点:
- MAC 算法是否与握手一致: 比如握手说了用
SHA256,记录层解密时就要用HMAC-SHA256。审计日志里最好记录实际验证时的 MAC 算法标识。 - 填充长度是否合规: 对于 CBC 模式,填充长度不能超过 255 字节,且每个填充字节的值必须等于
填充长度 - 1。这是在 Lucky13 攻击 之后加强的检查项。
我曾经审计过一个厂商的设备,它对 extended_master_secret 扩展有正确的握手实现,但记录层的加密密钥居然独立派生——差点让中间人攻击变得可行。所以记录层的加密参数,永远不要脱离握手参数单独审计。
2.4 序列号与防重放审计
序列号是记录层审计里的“隐形英雄”。它不出现在网络包头部,却隐藏在每一条记录的处理过程中。合规系统应当保证:
- 每个会话有独立的序列号空间,客户端和服务端各一个。
- 序列号从 0 开始,每发送 1 条记录递增 1。
- 到达
2^64 - 1时必须重新握手或终止会话。 - 会话恢复后,序列号必须重置。
seq_num_start 和 seq_num_end 加入日志。虽然序列号是隐式的,但 TLS 记录头的长度字段隐式限定了每条记录的顺序,我们可以通过计算到达记录的累积数量,反向推导序列号的变化。嗯,听起来绕,但配合 TCP 的序列号一起分析会清晰很多。
2.5 记录层参数审计知识图谱
下面这个 SVG 图,展示了记录协议层参数审计的核心逻辑。我花了点时间整理,你看一遍就能建立全局印象:安全基线在中间,左侧是入参审计,右侧是日志映射。箭头代表审计流和数据流。
2.6 记录层审计日志的映射规范
审计日志怎么写?我有一套固定的映射表,可以让你直接套用在任何合规审计工具里:
| 原始参数 | 日志字段名 | 合规值示例 | 告警阈值 |
|---|---|---|---|
| 内容类型 | record.content_type |
20-23 | 其他值即告警 |
| 版本号 | record.version |
03 03 (TLS 1.2) |
偏离协商版本+1 |
| 载荷长度 | record.payload_length |
0 ~ 16384 | 大于 16384 告警 |
| MAC算法 | record.mac_algorithm |
SHA256 | 与协商不一致告警 |
| 填充验证 | record.fill_ok |
true | false 即告警 |
| 序列号步长 | record.seq_delta |
1 | 步长不等于1告警 |
关于序列号步长的特别说明: 我审计时见过一种情况,由于网络分片,同一个记录被 TCP 拆成多个报文到达,但 TLS 记录层要求按序重组。这种情况下,序列号步长还是1,但 TCP 序列号会跳跃。所以审计时一定要区分 TCP 序列号 和 TLS 记录层序列号——这是两个完全不同的东西。
2.7 记录层审计实践清单
最后,给各位一份我每次做记录层审计必查的清单。你可以做成脚本,每次跑一遍:
- 抓取全量记录头,统计内容类型的分布——有没有非法值?
- 比对握手阶段的版本协商结果,检查每个记录头的版本字段是否一致。
- 抽取所有记录的有效载荷长度,检查是否超过
2^14。 - 验证每条记录的 MAC(如果协议版本支持独立 MAC),记录验证结果。
- 计算序列号的变化曲线,标记所有跳跃 > 0 或回绕的异常点。
- 检查重协商或会话恢复后的序列号是否重置。
- 将以上结果映射到审计日志字段,确保字段名符合本次项目的日志模板。
SSL_get_record_protocol() 在回调函数里获取记录层的版本和内容类型。我写过一个补丁,直接在回调里写审计日志,比事后抓包分析快得多。