Contents

用TCPDUMP捕获SSL握手

1. 概述

SSL 是通过 TCP 连接交换加密数据的最常用协议。并且为了建立 SSL 连接,两个端点必须交换公钥、加密算法、协议版本等。这种交换称为 SSL 握手。

由于这是一个非对称密钥证书交换,因此握手通常有可能会失败。例如,如果服务器的证书过期或者客户端和服务器无法协商 SSL/TLS 协议版本,握手将失败。

**在大多数情况下,我们可以通过分析客户端和服务器之间的 SSL 握手消息来找到失败的原因。**在本教程中,我们将研究一种通过网络捕获这些消息的方法。

2. tcpdump命令

*tcpdump *命令 允许我们捕获 Linux 系统中任何网络接口上的 TCP 数据包。

通常,大量 TCP 流量在典型的 SSL 交换中流动。虽然tcpdump非常有用并且可以捕获任意数量的数据,但这通常会导致转储文件很大,有时达到千兆字节的数量级。此类转储文件有时无法分析。例如,在 Wireshark 中分析此类转储需要大量资源。

为了克服这个问题,tcpdump命令提供了一些过滤选项 。因此,只有那些满足过滤条件的 TCP 数据包才会被捕获到输出转储中。

3. SSL握手简介

让我们快速浏览一下客户端和服务器在 SSL 握手期间交换的消息:

  1. 客户问候– 由客户发起。它包含协议版本、客户端支持的密码套件和安全随机数。
  2. Server Hello – 由服务器返回以响应 Client Hello。包含服务器选择的协议版本、从客户端列表中选择的密码套件、加密算法和其他 TLS 版本特定的扩展。
  3. 服务器证书– 由服务器发起。包含客户端将验证的公共证书链。
  4. 证书请求– 由服务器发起。仅当服务器还需要对客户端进行身份验证时才会发送此消息,就像双向 SSL 中的情况一样。
  5. Server Hello Done – 由服务器发起。表示Server Hello结束。
  6. 客户端证书——由客户端返回以响应客户端请求。客户端将其证书链发送到服务器。
  7. 客户端密钥交换——由客户端发起。它生成一个预主密钥并使用服务器的公共证书对其进行加密。然后它发送主密钥与服务器交换加密算法。
  8. 证书验证- 由服务器发起。这表明客户端证书的身份验证成功。
  9. 已完成——由客户端和服务器发送,表示身份验证和密钥交换成功。

如果在建立连接期间出现一些 SSL 故障,分析上述消息是一个很好的起点。

虽然我们不会详细讨论这些消息,但重要的是要认识到这些消息是 TCP 数据包的一部分。因此,如果我们只想捕获这些消息,与我们在上一节中研究的选项相比,我们需要高级过滤选项。

考虑到这一点,让我们探索tcpdump中的一些数据过滤选项,看看我们如何使用它们来仅过滤 SSL 握手消息。

4. 在tcpdump中过滤 SSL 握手消息

除了端口主机等元数据外,tcpdump命令还支持对 TCP 数据进行过滤。**换句话说,  tcpdump允许我们将数据包中的数据字节与过滤器表达式进行匹配。**例如,我们可以过滤带有某些 TCP 标志的数据包:

tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'

此命令将仅捕获 SYN和 FIN数据包,并可能有助于分析 TCP 连接的生命周期。

同样,如果我们知道数据字节的结构,我们可以过滤 SSL 握手消息。从TLS 规范 中,我们知道握手协议中的每条消息都以唯一的数值开头。例如,所有握手消息都包含 22,以十六进制表示为0x16  ,作为第一个数据字节:

/uploads/tcpdump_capture_ssl_handshake/1.jpg

所以,基于这个事实,让我们看看如何过滤握手消息。

4.1. 捕获客户问候语

假设我们要分析来自客户端的 SSL 连接建立尝试。为此,我们必须检查客户端和服务器之间的Client Hello消息。**Client Hello消息在 TCP 数据包的第六个数据字节中包含01 **。因此,要过滤此类数据包:

tcpdump "tcp port 8081 and (tcp[((tcp[12] & 0xf0) >>2)] = 0x16) \\
  && (tcp[((tcp[12] & 0xf0) >>2)+5] = 0x01)" -w client-hello.pcap

让我们了解命令选项的不同部分:

  • tcp port 8081 – 仅在端口 8081 上捕获数据包,假设这是应用程序服务器的 SSL 端口
  • and (tcp[12] & 0xf0) – 读取数据包的第 13 个字节并保留高 4 位
  • && ((tcp[12] & 0xf0) »2) – 当我们将上面的内容乘以 4 时,它给出了 TCP 标头大小

正如我们从上面的 SSL 转储中看到的那样,TLS 标头位于 TCP 数据包之前。因此,要获得第一个和第六个数据字节,我们需要计算 TCP 标头大小并跳过匹配这些字节。上面的第二项和第三项就是这样做的。

现在,tcp[TCP header size] 指向数据包中数据的第一个字节。因此术语tcp[((tcp[12] & 0xf0) »2)] = 0x16检查此字节是否等于 22,即SSL 握手的数字代码。并且,tcp[((tcp[12] & 0xf0) »2)+5] = 0x01将过滤第六个字节为1 的数据包,代表Client Hello

同样,我们可以捕获我们之前讨论过的任何握手消息。例如,我们可以使用tcp[((tcp[12] & 0xf0) »2)+5] = 0x02作为Server Hello消息。

4.2. 捕获具有特定 TLS 版本的数据包

SSL 协议一直在不断发展。SSLv3之后,该协议被TLS继承,大同小异。现代应用程序通常通过 TLSv1.3 交换消息。但是,许多仍然支持 TLSv1.0、TLSv1.1 和 TLSv1.2 以实现向后兼容性。

TLS 规范为每个 TLS 版本分配了一个唯一的数字代码:

  • SSLv3 – 0x300
  • TLSv1.0 – 0x0301
  • TLSv1.1 – 0x0302
  • TLSv1.2 – 0x0303
  • TLSv1.3 – 0x0304

**在 SSL 握手消息中,数据的第十和第十一个字节包含 TLS 版本。**因此,可以应用tcpdump 过滤器:

tcpdump "tcp port 8081 and (tcp[((tcp[12] & 0xf0) >>2)] = 0x16) \\
  && (tcp[((tcp[12] & 0xf0) >>2)+9] = 0x03) \\
  && (tcp[((tcp[12] & 0xf0) >>2)+10] = 0x03)"

tcp [((tcp[12] & 0xf0) »2)+9] = 0x03和 tcp[((tcp[12] & 0xf0) »2)+10] = 0x03检查第十和第十一个字节通过 TLSv1.2 过滤所有数据包。此命令将捕获所有交换 TLSv1.2 的 SSL 握手数据包。

4.3. 通过 TLS 捕获应用程序数据包

到目前为止,我们只捕获了 SSL 握手消息。握手完成后,客户端和服务器可以交换应用程序数据。此类应用数据包的第二个和第三个数据字节中还包含 TLS 版本

tcpdump "tcp port 8081 and (tcp[((tcp[12] & 0xf0) >>2)] = 0x17) \\
  && (tcp[((tcp[12] & 0xf0) >>2)+1] = 0x03) \\
  && (tcp[((tcp[12] & 0xf0) >>2)+2] = 0x03)" -w appdata.pcap

在这里,我们添加了一个过滤器来捕获第一个字节为17 ,第二个和第三个字节为03 的数据包。这是因为17是应用程序数据包的数字代码,而0303表示 TLSv1.2,正如我们之前所见。

4.4. 捕获 SSL 连接失败

为了过滤失败,我们将根据失败检查包含1521的第一个字节

tcpdump "tcp port 8081 and (tcp[((tcp[12] & 0xf0) >>2)] = 0x15) || (tcp[((tcp[12] & 0xf0) >>2)] = 0x21)" -w error.pcap

此命令将捕获第一个数据字节为1521的数据包。