3. TLS 记录层协议:记录格式、内容类型、加密与HMAC、分片与重组

好,咱们正式进入协议内部了。

说句实话,我跟无数个安全分析工具打过交道。很多新手来问我:“为什么我的TLS解密工具抓出来的包不对?”或者“明明握手都成功了,为什么上层应用数据乱码?”

嗯,这些问题十有八九,都出在记录层。

TLS别看它复杂,核心就两个层次:握手层管连接建立,记录层管数据传输。我们今天聊的,就是最底层的那个搬运工——记录层协议。你想想看,所有加密数据、心跳包、告警信息,最后都得塞到记录层这个信封里发出去。

3.1 记录格式:一个固定大小的信封

每个TLS记录,本质上就是个二进制信封。我习惯把它想象成一个快递包裹,外面贴着单子,里面装着货。这个单子的格式,是固定的。

字段 长度 说明
内容类型 1 字节 告诉接收方,这个信封里装的是什么
协议版本 2 字节 TLS版本号,常见如 0x0303(TLS1.2),0x0304(TLS1.3)
长度 2 字节 负载数据的字节数,最大 2^14 字节
负载 可变 实际数据,可能是明文或加密后的密文+MAC

这里有个细节我希望你记住:长度字段很重要。我在做异常检测时,首先看的就是长度。如果报文头的长度和实际负载长度对不上,那要么是网络传输出了问题,要么就是有人在搞鬼。

抓包小技巧
你可以在Wireshark里快速过滤TLS记录。
比如 tls.record.content_type == 23 只看应用数据。
我个人习惯先按内容类型分组,再去看具体每个记录的长度是否合理。

3.2 内容类型:信封上的标签

为什么说内容类型是个关键字段?因为它直接决定了接收方该怎么处理这个记录。我曾经遇到过一种攻击,就是篡改内容类型,让应用层把一个加密的告警包当成普通数据去解析,结果呢?连接直接崩溃,或者更糟,产生信息泄露。

常见的几种内容类型,我给你列一下:

  • 20 (0x14) - Change Cipher Spec:通知对方我要切换加密参数了。这个包很特殊,就1个字节,值固定为1。在TLS1.3里它被降级为历史遗留,但你依然会经常看到。
  • 21 (0x15) - Alert:告警信息。我建议你重点关注,因为异常的Alert往往就是攻击的前兆。比如fatal级别的告警,通常意味着协议错误或证书问题。
  • 22 (0x16) - Handshake:握手消息。里面包裹的就是ClientHello、ServerHello那些。握手阶段的数据都在这里。
  • 23 (0x17) - Application Data:应用层数据。这才是真正的业务数据。在我们检测异常时,如果应用数据包出现内容类型为23,但内部却是乱码或者长度异常,基本可以确定有问题。
  • 24 (0x18) - Heartbeat:心跳包。TLS1.3和部分TLS1.2实现会使用它来保活。那场著名的Heartbleed漏洞,就是出在了这个看似无害的心跳类型上。
避坑指南 | 我曾经踩过的坑
有一次排查一个HTTPS中间件,发现某个客户端连上来后,发送了一个内容类型为0xFF的包。
我当时的第一反应是“协议解析出错”,但后来发现,这是攻击者自定义的恶意探测包。
所以,不要假设所有包都是标准的。任何一个非标准的内容类型,都值得你拉响警报。

3.3 加密与HMAC:不是单纯的加密,而是组合拳

好,来到最关键的部分了。很多人以为TLS加密就是“先加密再发送”,其实没这么简单。记录层的加密是一套组合拳:

  1. 计算MAC:先对明文数据计算一个消息认证码(MAC)。
  2. 追加MAC:把计算出的MAC值附加到明文数据后面。
  3. 加密:对整个(数据+MAC)进行加密。
  4. 添加记录头:最后才加上5字节的记录头。

你可能会问:“为什么要先MAC再加密?”

这个问题我问过不少人。答案很简单:确保完整性和机密性。MAC保证了数据在传输中没被篡改,加密保证了数据在传输中没被偷看。这种模式叫“MAC-then-encrypt”。当然TLS1.3改用了AEAD(Authenticated Encryption with Associated Data)模式,更现代也更安全,但老协议里你依然会看到这种传统做法。

我举个例子,一个加密后的应用数据记录,在Wireshark里你看到的会是乱码。但一旦解密,你会看到:

解密后的负载 = 原始应用数据 + HMAC

其中HMAC的长度取决于你使用的哈希算法。比如SHA-256就是32字节。如果解密后HMAC不对,接收方会直接丢弃这个记录,并发送一个bad_record_mac告警。嗯,这种情况,我在分析恶意连接时见过太多次了。

场景 正常行为 异常行为
HMAC校验失败 丢弃记录,发送告警 攻击者在尝试篡改密文,或者在做重放攻击
加密后的长度异常 长度字段+加密负载长度一致 可能是在尝试填充攻击,或者利用分片漏洞
核心点:
在异常检测中,你不需要解密全部数据。你只需要关注以下几点:
- 解密后长度是否合理?
- HMAC是否总是失败(同一个源IP)?
- 是否收到了非预期的告警(如unexpected_message)?
这些特征,比解密数据本身更能说明问题。

3.4 分片与重组:大数据包的切割艺术

TLS记录层允许最大负载是2^14字节(16KB)。但实际上,应用层的数据可能比这个还大,或者底层TCP报文也可能因为MTU限制而需要分片。这时候,记录层就要做“分片”和“重组”。

分片规则很简单:

  • 分片单位:一个TLS记录是独立分片的。你不能把两个握手消息放在同一个记录里,除非它们本来就都是握手消息。
  • 重组:接收方根据记录头的“长度”字段,一个一个地读取完整的记录。
  • 注意:如果一个TLS记录被TCP分成了多个IP包,那么接收方需要先由TCP层重组完成,再由TLS记录层处理。如果TCP重组失败,TLS记录也就丢了。

那么,分片与异常检测有什么关系?关系大了。我总结了几种常见攻击:

  • 分片重叠攻击:攻击者故意发送多个TLS记录,让它们的内容在TCP层发生重叠。这样,代理或防火墙看到的内容可能和客户端看到的不一致。
  • 长分片攻击:发送一个长度远超2^14字节的记录,导致接收方缓冲区溢出或拒绝服务。
  • 分片延迟注入:攻击者把恶意数据分散到多个TLS记录中,利用时间差来绕过检测。

我曾经处理过一个真实案例。某个HTTPS代理在解析TLS记录时,它只检查了第一个分片的内容,忽略了后续分片。攻击者就把恶意脚本藏在第二个分片里。代理一看第一个分片没问题,就放行了。结果客户端一重组,恶意脚本就执行了。你看,这就是典型的“分片感知盲区”。

我的建议
检测分片攻击时,你可以在你的分析工具里记录:
1. 每个TLS记录的序列号和长度。
2. 是否有记录分片的长度总和超过了TCP负载?
3. 相邻记录的内容是否在逻辑上连续?
这些特征很容易在Wireshark的TLS专家信息里看到。

最后,咱们来看一张图,理解整个记录层的处理流程。

TLS记录层处理流程 应用层数据 步骤1:分片(如果数据 > 16KB) 拆分成多个TLS记录 步骤2:压缩(可选,不推荐) 大部分现代TLS已禁用 步骤3:计算MAC + 加密 MAC-then-encrypt / AEAD 步骤4:添加5字节记录头 (内容类型 + 版本 + 长度) 发送给TCP层 检测点 • 记录长度 • 内容类型 • 分片完整性 • 加密后长度 • HMAC失败率

这张图把整个流程串起来了。说白了,你在分析网络流量时,就是在检查每一个步骤是否都按规矩来。

嗯,记录层的内容就到这里。记住,它是所有TLS数据的载体,任何异常,最终都会在这里留下痕迹。你只要抓住“记录头格式异常”、“内容类型不符合预期”、“加密后的长度与记录头不符”、“大量的bad_record_mac告警”这几个核心点,基本上就能快速定位绝大多数记录层的攻击行为。