cURL调试日志磁盘耗尽漏洞导致拒绝服务攻击分析
漏洞描述
tool_debug_cb
函数在使用--trace
或--trace-ascii
选项处理大量数据时,会向日志文件写入大量调试数据。如果攻击者能够诱使cURL下载或上传极大量数据(例如通过超大HTTP响应或无限制上传),此调试功能生成的日志文件将无限增长。
这会导致运行cURL的系统磁盘空间耗尽,进而破坏同一服务器上运行的其他服务。
当使用--dump-header
选项时,该部分会将原始HTTP头数据写入heads->stream
。如果heads->stream
恰好与跟踪输出文件相同,或者是另一个具有无限增长潜力的文件,则会加剧此问题。
漏洞代码分析
头文件写入漏洞代码
1
2
3
4
5
6
7
8
9
10
11
12
|
/* In tool_debug_cb */
if(per->config->headerfile && heads->stream) {
size_t rc = fwrite(ptr, size, nmemb, heads->stream); // <-- 易受攻击的写入
if(rc != cb)
return rc;
/* flush the stream to send off what we got earlier */
if(fflush(heads->stream)) {
errorf(per->config->global, "Failed writing headers to %s",
per->config->headerfile);
return CURL_WRITEFUNC_ERROR;
}
}
|
TRACE_PLAIN段漏洞代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/* In tool_debug_cb, in the TRACE_PLAIN section */
case CURLINFO_HEADER_OUT:
if(size > 0) {
size_t st = 0;
size_t i;
for(i = 0; i < size - 1; i++) {
if(data[i] == '\n') { /* LF */
if(!newl) {
log_line_start(output, timebuf, idsbuf, type);
}
(void)fwrite(data + st, i - st + 1, 1, output); // <-- 易受攻击的写入
st = i + 1;
newl = FALSE;
}
}
if(!newl)
log_line_start(output, timebuf, idsbuf, type);
(void)fwrite(data + st, i - st + 1, 1, output); // <-- 易受攻击的写入
}
newl = (size && (data[size - 1] != '\n'));
traced_data = FALSE;
break;
|
数据输出漏洞代码
1
2
3
4
5
6
7
8
9
10
11
|
case CURLINFO_DATA_OUT:
case CURLINFO_DATA_IN:
case CURLINFO_SSL_DATA_IN:
case CURLINFO_SSL_DATA_OUT:
if(!traced_data) {
// ...
fprintf(output, "[%zu bytes data]\n", size); // <-- 易受攻击的写入
newl = FALSE;
traced_data = TRUE;
}
break;
|
dump函数漏洞代码
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
|
/* In function dump */
// ...
fprintf(stream, "%s%s%s, %zu bytes (0x%zx)\n", timebuf, idsbuf,
text, size, size); // <-- 易受攻击的写入(初始行)
for(i = 0; i < size; i += width) {
fprintf(stream, "%04zx: ", i); // <-- 易受攻击的写入(行前缀)
if(tracetype == TRACE_BIN) {
for(c = 0; c < width; c++)
if(i + c < size)
fprintf(stream, "%02x ", ptr[i + c]); // <-- 易受攻击的写入(十六进制数据)
else
fputs(" ", stream); // <-- 易受攻击的写入(填充)
}
for(c = 0; (c < width) && (i + c < size); c++) {
// ...
fprintf(stream, "%c", ((ptr[i + c] >= 0x20) && (ptr[i + c] < 0x7F)) ?
ptr[i + c] : UNPRINTABLE_CHAR); // <-- 易受攻击的写入(ASCII表示)
// ...
}
fputc('\n', stream); // <-- 易受攻击的写入(换行符)
}
fflush(stream);
|
本质上,漏洞不是fwrite
或fprintf
调用本身的错误,而是缺少对这些调用的保护包装器或检查来限制写入跟踪日志文件的累积数据。修复涉及添加缺失的检查。
概念验证(POC)
无限数据服务器设置 [unli.py]
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
35
36
37
38
39
40
|
import http.server
import socketserver
import time
PORT = 8002
class MyHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
print(f"[{time.ctime()}] Received GET request from {self.client_address[0]}")
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Transfer-Encoding", "chunked")
# FIX: Changed self0 to self
self.end_headers()
# Stream data continuously
try:
i = 0
while True:
chunk = f"This is chunk {i}: {'A' * 1024}\n".encode('utf-8') # 1KB of data per chunk
self.wfile.write(f"{len(chunk):X}\r\n".encode('ascii')) # Chunk size in hexadecimal
self.wfile.write(chunk)
self.wfile.write(b"\r\n")
self.wfile.flush()
i += 1
# Optional: add a small delay to observe file growth more easily
# time.sleep(0.01)
except Exception as e:
print(f"[{time.ctime()}] Client disconnected or error: {e}")
finally:
# End chunked encoding
self.wfile.write(b"0\r\n\r\n")
self.wfile.flush()
with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
print(f"serving at port {PORT}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nServer shutting down.")
|
运行带跟踪选项的curl
1
|
curl http://localhost:8002/test.txt -o /dev/null --trace output.log
|
1
2
3
4
|
└─# curl http://localhost:8002/test.txt -o /dev/null --trace output.log
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 37.0M 0 37.0M 0 0 7857k 0 --:--:-- 0:00:04 --:--:-- 7856k
|
现在您将观察到output.log
文件大小随着服务器持续流式传输数据而快速增加,curl记录每一个比特。这演示了导致拒绝服务(DoS)的磁盘空间耗尽。
修复方案
要解决您演示的磁盘空间耗尽漏洞,主要修复需要专注于无任何大小限制地向跟踪日志文件写入数据的代码部分。
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
35
|
int tool_debug_cb(CURL *handle, curl_infotype type,
char *data, size_t size,
void *userdata)
{
struct OperationConfig *operation = userdata;
struct GlobalConfig *global = operation->global;
FILE *output = tool_stderr;
// ... 其他变量声明 ...
// --- 开始修复 ---
// 确保跟踪流已打开且设置了大小限制
if (global->trace_stream && global->trace_fopened && global->max_trace_log_size > 0) {
// 估计此块要写入的数据大小
// 需要创建此`estimated_write_size`函数
// 它应考虑格式化开销(例如,十六进制转储占用更多空间)
size_t estimated_write_size = estimate_formatted_data_size(type, size, global->tracetype);
// 检查当前写入是否会超过限制
if (global->current_trace_log_size + estimated_write_size > global->max_trace_log_size) {
// 关闭文件并禁用跟踪以防止进一步写入
warnf(global, "Trace log file '%s' reached maximum size (%s). Stopping further trace logging.",
global->trace_dump, /* 使用类似tool_strbytel(global->max_trace_log_size)的辅助函数格式化大小 */);
fclose(global->trace_stream);
global->trace_stream = NULL; // 确保不再发生写入
global->trace_fopened = FALSE;
// 您可能还想完全禁用跟踪选项
global->tracetype = TRACE_NONE;
global->trace_dump = NULL;
return 0; // 停止回调进一步处理
}
}
// --- 结束修复 ---
// ... tool_debug_cb代码的其余部分 ...
}
|
在dump()函数中修复
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
static void dump(const char *timebuf, const char *idsbuf, const char *text,
FILE *stream, const unsigned char *ptr, size_t size,
trace tracetype, curl_infotype infotype, struct GlobalConfig *global) // 添加全局参数
{
size_t i;
size_t c;
unsigned int width = 0x10;
if(tracetype == TRACE_ASCII)
width = 0x40;
// --- 开始修复 ---
// 将初始行的大小添加到总数中
int written_bytes = fprintf(stream, "%s%s%s, %zu bytes (0x%zx)\n", timebuf, idsbuf, text, size, size);
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
// --- 结束修复 ---
for(i = 0; i < size; i += width) {
// --- 开始修复 ---
// 在写入每行前检查限制
if (global && stream == global->trace_stream && global->current_trace_log_size >= global->max_trace_log_size) {
// 如果已超过限制,只需中断并刷新剩余内容
break;
}
// --- 结束修复 ---
// 写入行头(例如,"0000: ")
written_bytes = fprintf(stream, "%04zx: ", i);
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
if(tracetype == TRACE_BIN) {
for(c = 0; c < width; c++)
if(i + c < size) {
written_bytes = fprintf(stream, "%02x ", ptr[i + c]);
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
} else {
written_bytes = fputs(" ", stream);
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
}
}
for(c = 0; (c < width) && (i + c < size); c++) {
// ... (CRLF处理逻辑保留) ...
written_bytes = fprintf(stream, "%c", ((ptr[i + c] >= 0x20) && (ptr[i + c] < 0x7F)) ?
ptr[i + c] : UNPRINTABLE_CHAR);
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
// ... (CRLF处理逻辑) ...
}
written_bytes = fputc('\n', stream); // 换行
if (global && stream == global->trace_stream && written_bytes > 0) {
global->current_trace_log_size += written_bytes;
}
}
fflush(stream);
}
|
附加修复说明
禁用跟踪:一旦达到限制,必须有效禁用跟踪机制(例如,通过设置global->trace_stream = NULL
并将global->tracetype
更改为TRACE_NONE
)以防止任何进一步的写入尝试。
影响
系统不稳定和崩溃(高影响)
项目团队回应
dfandrich (curl staff) 评论
“是的,确实如此,如果您将大量数据下载到磁盘,您将需要大量磁盘空间。但安全角度在哪里?调试日志不适用于生产系统,但如果应用程序希望使用它们,则必须有足够的资源来保存它们。通过资源使用导致的DoS问题已在libcurl-security.md文档中涵盖,健壮的应用程序必须考虑到这一点。”
jimfuller2024 (curl staff) 评论
“除非我遗漏了什么,这看起来不像是特定于curl的安全问题。”
bagder (curl staff) 评论
“认为这不是安全问题。我猜想是使用AI以某种方式编写了这段文字?”
“@tryhackplanet请向我们解释您提交此报告的理由。您是如何发现它的?您是如何得出结论认为这是一个安全漏洞的?”
“鉴于没有回答,我不得不得出结论,这是又一个愚蠢的AI生成的谎言,人类只是转发给我们,没有过滤或应用思考过程。滥用是这个术语。”
报告状态变更
bagder将报告关闭并将状态更改为垃圾邮件,标记为"AI垃圾"。
bagder请求披露此报告:“根据项目透明度政策,我们希望所有报告都公开披露并公开。”
报告详情
- 报告时间: 2025年7月14日 2:20am UTC
- 报告者: tryhackplanet
- 报告对象: curl
- 报告ID: #3250490
- 状态: 垃圾邮件
- 严重性: 中等 (4 ~ 6.9)
- 披露时间: 2025年7月14日 11:31am UTC
- 弱点: LLM04: 模型拒绝服务
- CVE ID: 无
- 赏金: 无