使用释放后内存:当POST请求体缓冲区在传输前被释放
漏洞概述
在libcurl中复现了一个堆释放后使用漏洞,通过设置CURLOPT_POSTFIELDSIZE
和CURLOPT_POSTFIELDS
指向堆缓冲区,然后在curl_easy_perform
之前释放该缓冲区。AddressSanitizer(ASan)在请求发送路径中报告了堆释放后使用读取错误。
受影响版本
从GitHub仓库获取的版本(CMake配置报告:curl版本=[8.17.0-DEV]),在macOS上使用AppleClang和ASan构建。
复现步骤
1. 使用AddressSanitizer配置和构建libcurl
1
2
3
4
5
6
7
8
|
cmake -S . -B build-asan \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBUILD_SHARED_LIBS=ON \
-DCURL_USE_LIBPSL=OFF \
-DCMAKE_C_FLAGS='-fsanitize=address -fno-omit-frame-pointer' \
-DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address' \
-DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address'
cmake --build build-asan -j 8
|
2. 启动本地HTTP服务器
1
|
python3 -m http.server 18080 --bind 127.0.0.1 &
|
3. 创建PoC程序
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
31
32
33
34
|
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void) {
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *h = curl_easy_init();
if(!h) return 1;
const char *url = "http://127.0.0.1:18080/";
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT_MS, 2000L);
curl_easy_setopt(h, CURLOPT_TIMEOUT_MS, 3000L);
const char payload[] = "test data";
size_t n = sizeof(payload) - 1;
char *cp = (char*)malloc(n);
memcpy(cp, payload, n);
curl_easy_setopt(h, CURLOPT_POSTFIELDSIZE, (long)n);
curl_easy_setopt(h, CURLOPT_POSTFIELDS, cp);
free(cp); // 当libcurl发送请求体时发生释放后使用
CURLcode res = curl_easy_perform(h);
fprintf(stderr, "perform result: %d\n", (int)res);
curl_easy_cleanup(h);
curl_global_cleanup();
return 0;
}
|
4. 编译并运行PoC
1
2
3
4
5
6
7
8
9
|
# 针对新构建的ASan libcurl编译PoC
cc -fsanitize=address -fno-omit-frame-pointer -I include \
build-asan/poc_uaf.c build-asan/lib/libcurl.dylib \
-o build-asan/poc_uaf_local
# 使用ASan运行并确保可执行文件找到本地libcurl
DYLD_LIBRARY_PATH=build-asan/lib \
ASAN_OPTIONS=halt_on_error=1:detect_invalid_pointer_pairs=1:strict_string_checks=1:strict_memcmp=1 \
./build-asan/poc_uaf_local
|
ASan报告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
* Trying 127.0.0.1:18080...
* Established connection to 127.0.0.1 (127.0.0.1 port 18080) from 127.0.0.1 port XXXXX
* using HTTP/1.x
=================================================================
==XXXXX==ERROR: AddressSanitizer: heap-use-after-free on address 0xXXXXXXXXXXXX at pc 0xXXXXXXXXXXXX bp 0xXXXXXXXXXXXX sp 0xXXXXXXXXXXXX
READ of size 9 at 0xXXXXXXXXXXXX thread T0
#0 0xXXXXXXXX in __asan_memcpy+0xXXX (libclang_rt.asan_osx_dynamic.dylib+0xXXXXX)
#1 0xXXXXXXXX in cr_buf_read sendf.c:1316
#2 0xXXXXXXXX in Curl_client_read sendf.c:1223
#3 0xXXXXXXXX in add_from_client request.c:349
#4 0xXXXXXXXX in Curl_bufq_sipn bufq.c:574
#5 0xXXXXXXXX in Curl_req_send request.c:407
#6 0xXXXXXXXX in Curl_http http.c:2926
#7 0xXXXXXXXX in multi_runsingle multi.c:2508
#8 0xXXXXXXXX in curl_multi_perform multi.c:2771
#9 0xXXXXXXXX in curl_easy_perform easy.c:846
#10 0xXXXXXXXX in main (poc_uaf_local+0xXXXX)
|
代码位置分析
读取路径中ASan标记UAF的位置:lib/sendf.c
中的cr_buf_read
函数,在发送期间请求体缓冲区被memcpy复制:
1
2
3
4
5
6
7
8
9
10
11
12
|
static CURLcode cr_buf_read(struct Curl_easy *data,
struct Curl_creader *reader,
char *buf, size_t blen,
size_t *pnread, bool *peos)
{
struct cr_buf_ctx *ctx = reader->ctx;
size_t nread = ctx->blen - ctx->index;
...
memcpy(buf, ctx->buf + ctx->index, nread);
...
return CURLE_OK;
}
|
当前树中的确切memcpy行:
1
2
3
4
|
if(nread > blen)
nread = blen;
memcpy(buf, ctx->buf + ctx->index, nread);
*pnread = nread;
|
OS/400 CCSID包装器问题
在packages/OS400/ccsidcurl.c
中处理带有显式大小和转换的CURLOPT_COPYPOSTFIELDS
时,包装器将转换后的缓冲区传递给CURLOPT_POSTFIELDS
并将其分配给STRING_COPYPOSTFIELDS
,但在返回之前释放了相同的缓冲区。
传递转换后的缓冲区并分配给STRING_COPYPOSTFIELDS
:
1
2
3
|
result = curl_easy_setopt(easy, CURLOPT_POSTFIELDS, s);
data->set.str[STRING_COPYPOSTFIELDS] = s; /* Give to library. */
break;
|
在函数结束时释放缓冲区:
1
2
3
|
va_end(arg);
free(cp);
return result;
|
影响分析
基于直接观察,在通过CURLOPT_POSTFIELDS
(带有显式CURLOPT_POSTFIELDSIZE
)设置应用程序提供的POST请求体缓冲区后释放它,会导致libcurl在传输期间解引用已释放的内存,导致进程崩溃(堆释放后使用读取)。
修复方案
修复方法是在所有权转移后清除cp:
1
2
3
|
result = curl_easy_setopt(easy, CURLOPT_POSTFIELDS, s);
data->set.str[STRING_COPYPOSTFIELDS] = s;
cp = NULL; /* 所有权已转移 */
|
讨论总结
这是一个确定性UAF/双重释放漏洞,发生在OS/400包装器(packages/OS400/ccsidcurl.c
)中,当使用带有显式大小+非ASCII转换的CURLOPT_COPYPOSTFIELDS
时。包装器将堆缓冲区交给libcurl然后释放它,导致:
- 在POST请求体发送期间发生UAF(潜在的堆数据暴露)
- 在句柄清理时发生双重释放(崩溃/DoS)
尽管IBM i用户群很小,但包装器仍随curl一起发布并具有活动示例,值得修复。