漏洞概述
报告#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; /* ← 缺失的关键行! */
|
漏洞原理与影响
- 静态全局变量:
t1555_curl被声明为static CURL *t1555_curl;。
- 跨函数调用持久存在:作为静态变量,其值在函数调用之间会保留。
- 风险场景:如果
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句柄结构中的函数指针。
复现步骤
- 识别
test_cleanup被多次调用的测试流程(例如,重复的测试运行或线程化的测试执行)。
- 观察
t1555_curl在未进行NULL检查或重新赋值的情况下被释放。
- 在指针被重新初始化之前,再次触发清理例程,引发释放后使用/双重释放。
- 监视崩溃或内存损坏指标(例如,分段错误、堆不一致)。
修复方案
在调用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;
|
影响分析报告
尽管此漏洞存在于测试代码中,但其影响超出了“仅仅是一个测试缺陷”的范畴,原因如下:
- 普遍性:cURL几乎无处不在(操作系统发行版、嵌入式系统、云基础设施)。
- 集成性:测试套件是现代开发流水线的组成部分。
- 可利用性:简单、可靠的触发机制。
- 后果:在广泛分发的软件中存在内存损坏原语。
直接安全影响:
- 内存损坏利用:导致释放后使用或双重释放,可能泄露敏感数据或破坏堆元数据。
- 控制流劫持:攻击者可能通过精确的堆操作覆盖函数指针,导致在测试运行器进程上下文中执行任意代码。
攻击向量:
- 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,也未发放赏金。