cURL测试套件中的释放后使用漏洞:全局句柄清理不当剖析

本文深入分析了cURL测试套件中一个因全局静态指针清理不当导致的释放后使用漏洞。报告详细描述了漏洞成因、攻击场景、影响分析,并提供了复现步骤、修复建议及概念验证代码。

漏洞概述

报告#3452725详细描述了一个存在于cURL测试套件中的释放后使用漏洞。该漏洞源于lib1555.c测试文件中一个全局静态指针t1555_curl的清理过程存在缺陷。在测试清理函数test_cleanup中,代码调用了curl_easy_cleanup(t1555_curl)来释放句柄,但既没有在清理前验证指针的有效性,也没有在释放后将指针重置为NULL。当清理函数被多次调用时(例如,在循环或并发测试执行中),这种不当管理可能导致释放后使用或双重释放的条件。

漏洞代码分析

问题根源位于测试文件curl/tests/libtest/lib1555.c中的test_lib1555函数。关键代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static CURL *t1555_curl; // 全局静态指针

static CURLcode test_lib1555(const char *URL)
{
  // ... 初始化与配置代码 ...
  res = curl_easy_perform(t1555_curl);

test_cleanup:
  /* undocumented cleanup sequence - type UA */
  curl_easy_cleanup(t1555_curl);  /* 第83行:释放句柄 */
  curl_global_cleanup();
  return res;  /* BUG: t1555_curl 在清理后没有被设置为 NULL! */
}

漏洞点: 在调用curl_easy_cleanup(t1555_curl);之后,缺少一行关键代码:

1
t1555_curl = NULL;  /* ← 缺失的关键行! */

漏洞原理与影响

  1. 静态全局变量t1555_curl被声明为static CURL *t1555_curl;
  2. 跨函数调用持久存在:作为静态变量,其值在函数调用之间会保留。
  3. 风险场景:如果test_lib1555()函数被再次调用,t1555_curl仍然指向已释放的内存。虽然easy_init()可能会分配一个新的句柄,但旧的悬垂指针可能在其他地方被使用(例如,在progressCallback回调函数中)。

内存状态可视化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
BEFORE cleanup:
┌─────────────┐     ┌─────────────────────┐
│ t1555_curl  │────▶│ CURL Handle Object  │
│ 0x7ffdf000  │     │ at heap 0x7ffdf000  │
└─────────────┘     └─────────────────────┘

AFTER curl_easy_cleanup(t1555_curl):
┌─────────────┐     ┌─────────────────────┐
│ t1555_curl  │────▶│ FREED MEMORY        │  ← 危险!
│ 0x7ffdf000  │     │ at heap 0x7ffdf000  │
└─────────────┘     └─────────────────────┘
                     (内存已归还给堆分配器)

WHAT SHOULD HAPPEN:
┌─────────────┐     ┌─────────────────────┐
│ t1555_curl  │────▶│ NULL                │  ← 安全!
│ 0x0         │     │                     │
└─────────────┘     └─────────────────────┘

攻击场景与潜在利用

攻击者可以构造一个重复触发清理例程的测试序列,利用指针验证缺失和释放后重置的缺陷。这可能引发内存损坏,进而被用来执行任意代码、导致应用程序崩溃或危害测试环境。

利用场景示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 如果 test_lib1555() 被调用两次: */
test_lib1555("http://example.com");  /* 第一次调用 - 句柄被释放但未置NULL */
/* ... 攻击者在此处进行堆喷洒 ... */
test_lib1555("http://example.com");  /* 第二次调用 - 使用悬垂指针! */
/* 在 progressCallback 内部: */
static int progressCallback(...)
{
  /* 使用 t1555_curl,它指向攻击者控制的内存! */
  curl_easy_recv(t1555_curl, buffer, 256, &n);  /* 从受控内存读取 */
  curl_easy_send(t1555_curl, buffer, n, &n);    /* 写入受控内存 */
}

这个单一的缺失行创造了一个可武器化的原语:

  • 读原语:通过curl_easy_recv()对损坏的句柄进行操作。
  • 写原语:通过curl_easy_send()对损坏的句柄进行操作。
  • 控制流劫持:通过覆盖CURL句柄结构中的函数指针。

复现步骤

  1. 识别test_cleanup被多次调用的测试流程(例如,重复的测试运行或线程化的测试执行)。
  2. 观察t1555_curl在未进行NULL检查或重新赋值的情况下被释放。
  3. 在指针被重新初始化之前,再次触发清理例程,引发释放后使用/双重释放。
  4. 监视崩溃或内存损坏指标(例如,分段错误、堆不一致)。

修复方案

在调用curl_easy_cleanup之前实施防护条件,并在清理后将指针设置为NULL

修复代码示例

1
2
3
4
5
6
7
8
test_cleanup:
  /* undocumented cleanup sequence - type UA */
  if (t1555_curl) {
      curl_easy_cleanup(t1555_curl);
      t1555_curl = NULL;  /* ← 添加此行以修复漏洞 */
  }
  curl_global_cleanup();
  return res;

影响分析报告

尽管此漏洞存在于测试代码中,但其影响超出了“仅仅是一个测试缺陷”的范畴,原因如下:

  1. 普遍性:cURL几乎无处不在(操作系统发行版、嵌入式系统、云基础设施)。
  2. 集成性:测试套件是现代开发流水线的组成部分。
  3. 可利用性:简单、可靠的触发机制。
  4. 后果:在广泛分发的软件中存在内存损坏原语。

直接安全影响

  • 内存损坏利用:导致释放后使用或双重释放,可能泄露敏感数据或破坏堆元数据。
  • 控制流劫持:攻击者可能通过精确的堆操作覆盖函数指针,导致在测试运行器进程上下文中执行任意代码。

攻击向量

  • CI/CD管道:许多组织在构建管道中自动运行测试套件。利用此漏洞可能危害构建环境。
  • 权限提升:如果测试以提升的权限运行,成功的利用可能导致权限提升。
  • 信息泄露:释放后使用可能泄露包含敏感数据的堆内存。
  • 拒绝服务:可靠的测试套件崩溃,扰乱开发工作流。

CVSS 3.1评分:8.1(高危) 攻击向量:网络(如果测试通过网络触发);攻击复杂性:低;所需权限:无;用户交互:无;范围:已更改;机密性影响:高;完整性影响:高;可用性影响:高。

概念验证代码

报告包含了多个POC代码,用于演示该漏洞。一个简化的示例如下,展示了释放后指针仍指向已释放内存,并且后续的堆分配可能占据该内存区域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ... 包含头文件 ...
static CURL *g_curl = NULL;
int main() {
    // 初始化并创建句柄
    curl_global_init(CURL_GLOBAL_ALL);
    g_curl = curl_easy_init();
    // ... 配置并执行 ...
    curl_easy_cleanup(g_curl);
    // BUG: g_curl 未设置为 NULL
    printf("[+] 清理后, g_curl = %p (悬垂指针!)\n", g_curl);
    // 堆喷洒以演示内存可能被重用
    char *spray[100];
    for(int i = 0; i < 100; i++) {
        spray[i] = malloc(100);
        memset(spray[i], 'A' + (i % 26), 100);
    }
    // 检查 g_curl 是否指向我们的喷洒内存
    if(g_curl && *(char*)g_curl >= 'A' && *(char*)g_curl <= 'Z') {
        printf("[!] 成功:堆喷洒落入了已释放的curl句柄内存!\n");
    }
    // ... 清理 ...
}

时间线与处理结果

  • 5天前:报告者rootx1337向curl项目提交报告。
  • 5天前:项目维护者bagder关闭了报告并将状态改为“不适用”,评论认为“这是测试代码”,并怀疑报告由AI生成而未应用人类智能。
  • 5天前:根据项目的透明性政策,维护者请求并披露了此报告。
  • 5天前:维护者发布评论称“用户因发布AI垃圾内容已被封禁”。

报告状态:已披露,但被项目方评估为非安全漏洞(Not Applicable),认为其存在于测试代码中。报告未分配CVE ID,也未发放赏金。

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