深入解析wolfSSL漏洞与Dolev-Yao模型引导的协议模糊测试

本文详细介绍了在wolfSSL库中发现的四个漏洞(CVE-2022-38152至CVE-2022-42905),这些漏洞可导致拒绝服务攻击。文章还深入探讨了基于Dolev-Yao模型的协议模糊测试工具tlspuffin的开发与应用,展示了其在自动化漏洞挖掘中的高效性。

将狼挡在wolfSSL之外 - Trail of Bits博客

Trail of Bits公开披露了影响wolfSSL的四个漏洞:CVE-2022-38152、CVE-2022-38153、CVE-2022-39173和CVE-2022-42905。这四个问题的CVSS评分从中等到严重不等,均可能导致拒绝服务(DoS)。这些漏洞是使用新型协议模糊测试工具tlspuffin自动发现的。本文将探讨这些漏洞,然后深入概述该模糊测试工具。

tlspuffin是一个受形式化协议验证启发的模糊测试工具。最初作为我在法国洛林大学计算机科学与应用研究所(INRIA)实习期间开发的项目,它特别针对TLS或SSH等密码协议。

在Trail of Bits实习期间,我们通过支持新协议(SSH)、添加更多模糊测试目标以及(重新)发现漏洞,进一步推动了协议模糊测试的发展。这项工作代表了首个Dolev-Yao模型引导的模糊测试工具开发的一个里程碑。通过支持额外协议,我们证明了我们的模糊测试方法对协议是不可知的。未来,我们旨在支持其他协议,如QUIC、OpenVPN和WireGuard。

针对wolfSSL

在Trail of Bits实习期间,我们添加了多个版本的wolfSSL作为模糊测试目标。wolfSSL库是一个理想的选择,因为它受到2022年初发现的两个身份验证漏洞(CVE-2022-25640和CVE-2022-25638)的影响。这意味着我们可以通过使用tlspuffin重新发现已知漏洞来验证其有效性。

由于tlspuffin是用Rust编写的,我们首先必须为wolfSSL编写绑定。在实现绑定的过程中,在OpenSSL兼容层中发现了几个错误,这些错误也已报告给wolfSSL团队。绑定准备就绪后,我们让模糊测试工具开始工作:在wolfSSL中发现异常状态。

发现的漏洞

在实习期间,我在wolfSSL中发现了几个可能导致拒绝服务(DoS)的漏洞。

DOSC:CVE-2022-38153允许中间人(MitM)攻击者或恶意服务器通过拦截和修改TLS数据包对TLS 1.2客户端执行DoS攻击。此漏洞影响wolfSSL 5.3.0。

DOSS:CVE-2022-38152是针对使用wolfSSL_clear函数而不是wolfSSL_free; wolfSSL_new序列的wolfSSL服务器的DoS漏洞。恢复会话会导致服务器因空指针解引用而崩溃。此漏洞影响wolfSSL 5.3.0至5.4.0。

BUF:CVE-2022-39173由缓冲区溢出引起,导致wolfSSL服务器的DoS。它通过假装恢复会话并在Client Hello中发送重复的密码套件来触发。攻击者可能在某些架构或目标上获得远程代码执行(RCE);然而,这一点尚未得到确认。wolfSSL 5.5.1之前的版本受影响。

HEAP:CVE-2022-42905由解析TLS记录头时的缓冲区过度读取引起。wolfSSL 5.5.2之前的版本受影响。

“对wolfSSL的几个CVE,对tlspuffin的一大步。”

这些漏洞标志着该模糊测试工具的一个里程碑:它们是使用该工具发现的第一个具有深远影响的漏洞。我们还可以自信地说,使用传统的比特级模糊测试工具很难发现此漏洞。特别有趣的是,模糊测试工具平均花费不到一小时就发现了一个漏洞并导致崩溃。

在准备wolfSSL的模糊测试设置时,我们还发现了一个严重的内存泄漏,这是由于误用wolfSSL API引起的。此问题已报告给wolfSSL团队,他们更改了文档以帮助用户避免泄漏。此外,其他几个代码质量问题也已报告给wolfSSL,他们的团队在披露后一周内修复了所有发现。如果存在“最佳协调披露”奖,wolfSSL团队肯定会赢得它。

以下部分将重点关注其中两个漏洞,因为它们具有更高的影响和表达性的攻击痕迹。

DOSC:针对客户端的拒绝服务

在wolfSSL 5.3.0中,MitM攻击者或恶意服务器可以使TLS客户端崩溃。该错误存在于AddSessionToCache函数中,该函数在客户端从服务器接收到新会话票证时调用。

假设wolfSSL的会话缓存的每个桶至少包含一个条目。一旦新的会话票证到达,客户端将重用先前存储的缓存条目以尝试将其缓存到会话缓存中。此外,由于新的会话票证相当大,为700字节,它将使用XMALLOC在堆上分配。

在以下示例中,SESSION_TICKET_LEN为256:

1
2
3
4
5
if (ticLen > SESSION_TICKET_LEN) {
    ticBuff = (byte*)XMALLOC(ticLen, NULL, DYNAMIC_TYPE_SESSION_TICK);
    
}
ssl.c:13442

此分配导致cacheTicBuff的初始化,因为ticBuff已经初始化,cacheSession->ticketLenAlloc为0,ticLen为700:

1
2
3
4
5
if (ticBuff != NULL && cacheSession->ticketLenAlloc < ticLen) { 
    cacheTicBuff = cacheSession->ticket;
    
}
ssl.c:13500

cacheTicBuff设置为先前会话的票证cacheSession->ticket。cacheTicBuff指向的内存未在堆上分配;实际上,cacheTicBuff指向cacheSession->_staticTicket。这是有问题的,因为如果cacheTicBuff不为空,它稍后会被释放。

1
2
3
if (cacheTicBuff != NULL)
     XFREE(cacheTicBuff, NULL, DYNAMIC_TYPE_SESSION_TICK);
ssl.c:13557

进程通过执行XFREE函数终止,因为传递的指针未在堆上分配。

请注意,票证长度本身并不是崩溃的原因。此漏洞与Heartbleed(在OpenSSL中发现的缓冲区过度读取漏洞)非常不同。在wolfSSL中,崩溃不是由溢出缓冲区引起的,而是由逻辑错误引起的。

发现异常状态

模糊测试工具在大约一小时内发现了该漏洞。模糊测试工具通过用700字节的大数组(large_bytes_vec)替换实际票证来修改NewSessionTicket(new_message_ticket)消息。这种对原本正常的痕迹的变异导致对非分配值调用XFREE。这最终导致接收如此大票证的客户端崩溃。

DOSC(CVE-2022-38153)的可视化利用。每个框代表一个TLS消息。每条消息由不同的字段组成,如协议版本或密码套件向量。可视化是使用tlspuffin模糊测试工具生成的,并反映了DY攻击痕迹的结构,这将在下一节中介绍。

执行上述痕迹的单个实例不足以到达易受攻击的代码。由于错误存在于wolfSSL的会话缓存中,我们需要让客户端缓存填满以触发崩溃。根据经验,我们发现大约需要30个先前的连接才能可靠地使其崩溃。随机行为的原因是缓存由多行或多桶组成;wolfSSL的默认编译配置包含11个桶。基于TLS会话ID的哈希,会话存储在这些桶之一中。仅当当前桶已经包含先前的会话时,才会触发DoS。

重现此漏洞很困难,因为需要准备状态才能达到该行为。通常,全局状态(如wolfSSL缓存)使模糊测试更难以应用。理想情况下,人们可能假设程序的每次执行在给定相同输入时产生相同的输出。如果此假设被违反,因为程序使用全局状态,则重现和调试变得更具挑战性;这在模糊测试未知目标时代表了一个普遍挑战。

幸运的是,tlspuffin允许研究人员重新创建与模糊测试工具观察到崩溃时存在的程序状态相似的状态。我们能够重新执行模糊测试工具认为有趣的所有痕迹,这使我们能够在更受控的环境中观察wolfSSL的崩溃,并使用GDB调试wolfSSL。在分析导致无效释放的调用堆栈后,很明显该错误与会话缓存有关。

DOSC的根本原因在于使用共享的全局状态。非常令人惊讶的是,wolfSSL在库的多次调用之间共享状态。从概念上讲,会话缓存的生命周期应绑定到TLS上下文,该上下文已经作为TLS会话的容器。每个SSL会话与TLS上下文共享状态。维护全局可变状态的增加增加了整个代码库的复杂性。因此,应仅在绝对必要时使用。

BUF:服务器上的缓冲区溢出

在wolfSSL 5.5.1之前的版本中,恶意客户端可以在恢复的TLS 1.3握手期间导致缓冲区溢出。如果攻击者通过发送恶意制作的Client Hello,然后是另一个恶意制作的Client Hello来恢复或假装恢复先前的TLS会话,则可能发生缓冲区溢出。必须至少发送两个Client Hello:一个假装恢复先前的会话,另一个作为对Hello Retry Request消息的响应。

恶意的Client Hello包含支持的密码套件列表,其中至少包含⌊sqrt(150)⌋ + 1 = 13个重复项,总共少于150个密码。缓冲区溢出发生在握手期间第二次调用RefineSuites函数时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Refine list of supported cipher suites to those common to server and 
client.
*
* ssl         SSL/TLS object.
* peerSuites  The peer's advertised list of supported cipher suites.
*/
static void RefineSuites(WOLFSSL* ssl, Suites* peerSuites)
{
    byte   suites[WOLFSSL_MAX_SUITE_SZ];
    word16 suiteSz = 0;
    word16 i, j;

  XMEMSET(suites, 0, WOLFSSL_MAX_SUITE_SZ);

  for (i = 0; i < ssl->suites->suiteSz; i += 2) {
      for (j = 0; j < peerSuites->suiteSz; j += 2) {
          if (ssl->suites->suites[i+0] == peerSuites->suites[j+0] &&
              ssl->suites->suites[i+1] == peerSuites->suites[j+1]) {
              suites[suiteSz++] = peerSuites->suites[j+0];
              suites[suiteSz++] = peerSuites->suites[j+1];
          }
      }
  }

  ssl->suites->suiteSz = suiteSz;
  XMEMCPY(ssl->suites->suites, &suites, sizeof(suites));
#ifdef WOLFSSL_DEBUG_TLS
[...]
#endif
} tls13.c:4355

RefineSuites函数期望一个包含可接受密码套件列表的struct WOLFSSL,位于ssl->suites,以及一个对等密码套件数组。两个输入都由WOLFSSL_MAX_SUITE_SZ限制,该值等于150个密码套件或300字节。

假设ssl->suites由单个密码套件(如TLS_AES_256_GCM_SHA384)组成,并且用户可控的peerSuites列表包含重复13次的相同密码。RefineSuites函数将为ssl->suites中的每个套件迭代peerSuites,并在匹配时将套件附加到suites数组。suites数组的最大长度为WOLFSSL_MAX_SUITE_SZ套件。

使用刚刚提到的输入,suites的长度现在等于13。suites数组现在被复制到struct WOLFSSL,位于上面列表的最后一行。因此,ssl->suites数组现在包含13个TLS_AES_256_GCM_SHA384密码套件。

在 presumably resumed TLS握手期间,如果客户端触发Hello Retry Request,则再次调用RefineSuites函数。struct WOLFSSL在之间未重置,并保持先前的13个密码套件。因为TLS对等方控制peerSuites数组,我们假设它再次包含13个重复的密码套件。

RefineSuites函数将为ssl->suites中的每个元素迭代peerSuites,并在匹配时将套件附加到suites。因为ssl->suites数组已经包含13个TLS_AES_256_GCM_SHA384密码套件,总共13 x 13 = 169个密码套件被写入suites。169个密码套件超过了分配的最大允许WOLFSSL_MAX_SUITE_SZ密码套件。suites缓冲区在堆栈上溢出。

到目前为止,我们无法利用此错误,例如获得远程代码执行,因为可以溢出suites缓冲区的字节集很小。只有有效的密码套件值可以溢出缓冲区。

由于空间限制,我们不提供将正常痕迹变异为攻击痕迹所需的突变的详细审查,如我们对DOSC所做的那样。

为了理解我们如何发现这些漏洞,值得研究tlspuffin是如何开发的。

下一代协议模糊测试

历史证明,密码协议的实现容易出错。在将RFC或科学文章等规范转换为实际程序代码时,很容易引入逻辑缺陷。2017年,研究人员发现著名的WPA2协议存在严重缺陷(KRACK)。像FREAK这样的漏洞,或像2022年初在wolfSSL中发现的

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计