检查客户端问候以获取 https 分类

信息安全 tls C
2021-08-21 18:33:21

我需要检测网络流量中的 https 数据包。到目前为止,我将所有“443”标记为 https,但我不想再为这些使用端口信息。

检查客户端问候消息是否足够:

//Check 22 and version info 0300 0301 or 0302
if (packet->payload[0] == 0x16 && packet->payload[1] == 0x03
  && (packet->payload[2] == 0x00 || packet->payload[2] == 0x01 || packet->payload[2] == 0x02)

{
    int temp = ntohs(get_u16(packet->payload, 3)) + 5;//Get lenght 
//Check lenght is valid and 6th byte is client hello(which is 1)
    if (temp < packet->payload_length && temp > 50 && packet->payload[5]) == 1) 
        MARK AS HTTPS 
}

由于我的项目设计,我不能检查多个数据包。你能告诉我像上面这样检查客户你好是否可以?

3个回答

SSL/TLS中,消息作为记录的一部分发送。应该预料的是,客户端首先发送一条ClientHello消息,该消息本身包含在一个或多个记录中。

记录格式为:

record type: 1 byte (0x16 for "records contains some handshake message data")
protocol version: 2 bytes (0x03 0x00 for SSL 3.0, 0x03 0x01 for TLS 1.0, and so on)
record length: 2 bytes (big endian)
then the record data...

对于第一条记录(从客户端到服务器),客户端将首先发送一个ClientHello消息,这是一种握手消息,因此封装在如上所示的记录中(记录的第一个字节将是 0x16)。理论上,客户端可能会发送ClientHello拆分为几条记录,并且可能以一条或几条空记录开头,但这不太可能。ClientHello消息本身以它自己的四字节标头开始,其中一个字节表示消息类型(0x01 表示ClientHello,然后是超过三个字节的消息长度(又是大端)。

一旦客户端发送了它的ClientHello,它就会期待来自服务器的响应,所以ClientHello它的记录中将是单独的。

所以你可以期待一个以以下 9 个字节开头的有效负载:

0x16 0x03 X Y Z 0x01 A B C

和:

  • X将是 0, 1, 2, 3...或更多,具体取决于客户端用于第一条消息的协议版本。目前,定义的 SSL/TLS 版本是SSL 3.0TLS 1.0TLS 1.1TLS 1.2未来可能会定义其他版本。他们可能会使用3.X编号方案,因此您可以期望第二个标头字节保持为 0x03,但您不应任意限制第三个字节。

  • YZ是记录长度的编码;ABCClientHello消息长度的编码。由于ClientHello消息以 4 字节标头(不包括其长度)开头,并且应该在其记录中单独存在,因此您应该具有:A = 0256*X+Y = 256*B+C+4

如果您看到 9 个这样的字节来验证这些条件,那么很可能这是ClientHello来自 SSL 客户端。


一些非最新的 SSL 客户端也可能支持较旧的协议版本,称为 SSL 2.0。这些客户端将发出一个ClientHello遵循 SSL 2.0 规则的消息和记录,其中消息记录以某种方式合并。该 SSL 2.0ClientHello消息将声明客户端也知道 SSL 3.0 或更新版本,但它不会以上面解释的 9 字节序列开头。

SSL 2.0ClientHello结构在附录 E.2 或 RFC 5246中进行了解释。尽管这样的客户端很少(有一个关于完全禁止 SSL 2.0 支持的RFC),但仍然有很多部署在那里。


您的代码有几个问题:

  • 它没有检测到 SSL 2.0ClientHello消息。
  • 它检查第三个头字节(我上面的描述中的X)是否等于 0、1 或 2,这排除了 TLS 1.2。这太局限了。
  • 它假设整体ClientHello将在单个记录中(这是一个合理的假设),并且这ClientHello将被编码在单个数据包中(这是一个不太合理的假设)。
  • 它不会尝试查看握手消息中的长度并用记录长度来证实它。

相应地,逃避检测将很容易(通过使用 SSL 2.0 ClientHello,通过使用标记有 TLS 1.2 版本的记录,通过发出ClientHello不适合单个数据包的大消息......方法很多);并且一些现有部署的客户端将不会被检测到:不仅可以故意避免检测,但也有可能不情愿

由于我的项目设计,我不能检查多个数据包。你能告诉我像上面这样检查客户你好是否可以?

如果您不希望客户端绕过您的检测,那么在您获得的第一个数据包中检查客户端 hello 开头的典型字节可能就足够了。但是,如果您使用它来阻止 https 流量但允许其他任何内容,那么通过将客户端 hello 的第一个字节分布在多个 TCP 数据包上来很容易绕过您的检测。这是可能的,因为 https 使用 TCP 作为底层协议,而 TCP 是一种不关心数据包边界的流传输。

另请注意,有些应用程序试图看起来像 https 流量,以欺骗防火墙让它们通过。一个典型的例子是 Skype,它试图通过 HTTP(s) 代理进行隧道传输。

此外,您并没有真正检查 https,而只是检查 SSL。HTTPS 是 SSL 隧道中的 HTTP,但您无法检测隧道中的协议是否真的是 HTTP 或其他协议,如 IMAP、POP、SMTP 等。

汤姆你的回答非常详细和有用。我很感激。我考虑了要点并试图改进我的实施。因为它有点大,所以我正在写一个新的答案。它变得有点长,我不希望有人分析,但如果有人需要,它会很有用。

if (flow_special_packet_counter == 0)
{
    //SSLv2 no server name extension I don't have to mark it now I will wait for server hello 
    if (pkt->pllen >= 27 && pkt->pl[2] == 0x01 && pkt->pl[3] == 0x03
    && (pkt->pl[4] == 0x00 || pkt->pl[4] == 0x01 || pkt->pl[4] == 0x02 || pkt->pl[4] == 0x03)
    && (pkt->pllen - pkt->pl[1]) == 2)
    // SSLv2 Record
    {
        flow_special_packet_counter++;
        return;
    }
    //SSLv3 client hello is important for me I will try to mark it if only package lenght is bigger I will wait for server hello 
    else if (pkt->pllen >= 47 && pkt->pl[0] == 0x16 && pkt->pl[1] == 0x03
      && (pkt->pl[2] == 0x00 || pkt->pl[2] == 0x01 || pkt->pl[2] == 0x02 || pkt->pl[2] == 0x03 ))
    // SSLv3 Record
    {
        if (pkt->pl[5] != 0x02)//No client hello 
             return;

        unsigned short sslv3ClientHelloLength = ntohs(getU16n2h(pkt->pl[3]));
        if (sslv3ClientHelloLength > (pkt->pllen - 5))//client hello bigger than payload than wait for second 
        {
            flow_special_packet_counter++;  
            return;
        }
        else 
        {
            if((pkt->pllen - sslv3ClientHelloLength) == 5)
               MARKS AS HTTPS return;
            else 
               return;
        }

    }
    else if (flow_special_packet_counter == 0 && flow_pktcount > 10) //Enough checking this flow is not https 
    {
        NEVER HTTPS return; 
    }
}
else if (flow_special_packet_counter == 1)
{
    if (flow_pktcount > 20)
    NEVER HTTPS return;
    if (pkt->pllen >= 47 && pkt->pl[0] == 0x16 && pkt->pl[1] == 0x03
      && (pkt->pl[2] == 0x00 || pkt->pl[2] == 0x01 || pkt->pl[2] == 0x02 || pkt->pl[2] == 0x03 && pkt->pl[5] == 0x02))
      {
            MARKS AS HTTPS return;//Double records been check I think that is enough now time for testing :) 
      }  
}