4. TLS 流量特征分析:记录层与协议结构
大家好,欢迎来到第四章。前面我们聊了TLS握手的基本流程,也看了加密套件如何工作。但真正要分析加密流量,核心还是要看懂TLS的“包装纸”——也就是记录层协议。
很多人刚开始抓包时,看到一堆“Application Data”就头大。其实,TLS自己把数据分得很清楚。你只要搞懂它的格式,加密流量也就没那么神秘了。我个人习惯先从记录层格式入手,这就像拆快递,先看外包装,再看里面装了什么。
4.1 TLS 记录层格式
记录层的结构非常固定。它像是一个通用的信封,信封外面写着“这是什么类型的数据”、“用了哪个版本的TLS”、“数据有多长”。说白了,就是三层元数据加上一层负载。
每个TLS记录层包(Record)的格式如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| Content Type | 1 | 记录类型(20=ChangeCipherSpec, 21=Alert, 22=Handshake, 23=Application Data) |
| Protocol Version | 2 | 协议版本(如 0x0303 代表 TLS 1.2,0x0304 代表 TLS 1.3) |
| Length | 2 | 后续负载的长度,最大值为 2^14 字节(16384) |
| Fragment(负载) | 可变 | 实际内容,取决于 Content Type |
注意这里有个细节:Protocol Version 字段在 TLS 1.3 之后是有争议的。我遇到过几次抓包发现版本字段写的是 0x0303,但实际上是对端在跑 TLS 1.3。为什么?因为 TLS 1.3 为了兼容性,在记录层还是写 TLS 1.2 版本号。真正的版本信息藏在了 Handshake 消息内部的扩展里。记得不要只看记录层的版本号就下结论,否则会踩坑。
Length 字段非常关键。如果发现记录层长度大于 16384,很可能是分片或者重组出了问题。我曾经在分析一个自定义加密隧道时,就是因为没处理分片,导致解密失败。
4.2 Content Type:数据的分拣员
记录层的第一个字节就是 Content Type。它决定了服务器或客户端收到这个包后,该交给哪个模块处理。说白了,就是数据的分拣标签。
Content Type 的取值虽然只有几种,但意义完全不同。我们直接看表:
| 类型值(十进制) | 类型名 | 用途 | 常见场景 |
|---|---|---|---|
| 20 | ChangeCipherSpec(CCS) | 通知对方切换加密参数 | TLS 1.2 中每个握手完成时发送;TLS 1.3 中已移除该类型(但仍可能出现) |
| 21 | Alert | 传递警告或错误 | 证书错误、解密失败、连接关闭等 |
| 22 | Handshake | 传输握手消息(ClientHello、ServerHello 等) | 连接建立阶段的核心流量 |
| 23 | Application Data | 传输加密后的应用层数据 | 实际业务流量,比如 HTTP、DNS over TLS |
| 24 | Heartbeat | TLS 心跳检测(RFC 6520) | 心跳扩展启用后使用,较少见 |
这里要特别提一下 Alert 类型。很多人看到抓包里出现 Alert 就紧张,觉得出问题了。其实 Alert 分为两个等级:warning(等级 1)和 fatal(等级 2)。比如,连接正常关闭时,会发一个 close_notify 的 warning 级别的 Alert。只有 fatal 级别的 Alert 才会终止连接。
4.3 Handshake / Alert / Application Data 结构
我们真正需要反复打交道的,是 Handshake 和 Application Data。先说 Handshake。
4.3.1 Handshake 结构
当 Content Type 为 22 时,负载部分就是 Handshake 消息。它本身又有一个子头部。每次拆这种嵌套结构,我都有种剥洋葱的感觉。
+------------+------------+------------+-----------------------+
| Handshake | Length | Message | ... |
| Type (1B) | (3B) | Body | (更多握手消息) |
+------------+------------+------------+-----------------------+
关键字段解释:
- Handshake Type:1 字节。比如
1代表 ClientHello,2代表 ServerHello,11代表 Certificate,16代表 EncryptedExtensions(TLS 1.3 中常用)。 - Length:3 字节。这是个“大端”编码,表示后续 Message Body 的长度。注意是 3 字节,不是 2 字节。
- Message Body:可变。根据 Handshake Type 的不同,解析方式也不同。
举个例子,如果你想提取 ClientHello 中的 SNI(服务器名称指示)字段,你需要先找到 Handshake Type=1 的消息,然后跳过固定的一些头部,再进入扩展字段寻找。别着急,我们后面会详细讲。
4.3.2 Alert 结构
当 Content Type 为 21 时,负载就是 Alert 消息。格式极其简单:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| Level | 1 | 1=warning, 2=fatal |
| Description | 1 | 错误码,如 40=handshake_failure, 42=bad_certificate |
很多安全扫描工具就是通过监听 TLS Alert 来发现问题的。比如,你看到大量 fatal / handshake_failure,八成是证书链验证出问题了。
4.3.3 Application Data 结构
当 Content Type 为 23 时,负载是加密后的应用层数据。这也是我们分析学最难啃的部分。为什么呢?因为数据已经加密了,你看到的是一堆随机字节。但是!通过分析加密流量的长度、时间间隔、数据包大小分布,我们可以推断出一些上层信息。我把它叫做“流量指纹”。
举个例子,HTTP/2 的 TLS 流量有个明显特征:应用数据记录的长度不会超过 16384(MTU 限制),但有时候即使没有数据发送,也会发一些空记录或者“心跳”记录。这些模式是可以被机器学习模型捕捉到的。
4.4 TLS 扩展字段:隐藏在握手里的细节
TLS 为什么这么灵活?很大程度上是因为它的扩展机制。扩展字段出现在 ClientHello 和 ServerHello 的尾部,由一系列 Type-Length-Value(TLV)结构组成。你可以把它想象成协议里的“插件”系统,用于协商各种额外特性。
常见的扩展类型码如下(我挑几个最常用的):
| 扩展类型码(十进制) | 扩展名称 | 作用 | 存在版本 |
|---|---|---|---|
| 0 | server_name(SNI) | 告知服务器要连接的域名 | TLS 1.2, 1.3 |
| 5 | status_request(OCSP Stapling) | 允许服务器在握手时直接提供证书吊销状态 | TLS 1.2, 1.3 |
| 13 | signature_algorithms | 客户端支持的签名算法列表 | TLS 1.2+ |
| 16 | application_layer_protocol_negotiation(ALPN) | 协商应用层协议(如 h2、http/1.1) | TLS 1.2, 1.3 |
| 43 | supported_versions | TLS 1.3 中使用,明确列出支持的版本 | TLS 1.3 |
| 51 | key_share | TLS 1.3 中用于密钥交换 | TLS 1.3 |
我个人认为,ALPN 扩展是分析 TLS 流量时最有价值的信息之一。为什么?因为它直接告诉你,加密隧道里面跑的是什么协议。是 HTTP/2(h2)?还是 HTTP/1.1?还是别的自定义协议?这个信息在解密前就能拿到。我在做企业级加密流量检测时,就经常根据 ALPN 来分类流量——比如 h2 流量可能跟 Web 浏览有关,而自定义 ALPN 可能代表私有应用。
再提一个细节:SNI 扩展。在多数 TLS 流量中,SNI 字段是明文传输的。这意味着,即使你不解密,也可以知道客户端要访问哪个域名。这对网络管理员做流量审计非常有用。但要注意,现在有些加密技术(如 ESNI,Encrypted SNI)已经让 SNI 不可见了。未来的趋势一定是越来越难分析。
最后再强调一点:扩展字段的排列顺序也是有意义的。在 TLS 1.3 中,一些扩展是强制性的,比如 supported_versions 和 key_share。如果你在做流量分类或异常检测,不妨把扩展列表当作一组“特征”。不同客户端(比如 Chrome 和 Firefox)的扩展数量和顺序都不一样。我曾在一次渗透测试中,就靠分析扩展列表识别出了对方的浏览器类型,进而推测出可能的漏洞攻击面。
好了,这一章的内容就到这里。TLS 记录层和扩展字段就像是一栋大楼的骨架和管线——外表看起来都一样,但内行能从细节看出门道。希望你们能在实际抓包中多观察记录层的这些字段,慢慢建立“流量直觉”。