Curl NTLM认证内存泄漏漏洞技术分析

本文详细分析了Curl库中NTLM认证机制存在的一个内存泄漏漏洞,当处理大型NTLMv2 TargetInfo时,在特定条件下会导致内存无法释放,最大可泄漏65KB内存。

内存泄漏在Curl_auth_create_ntlm_type3_message函数中

报告摘要

报告ID: #3393539
提交者: tjbecker_theori
提交时间: 2025年10月21日 23:41 UTC

漏洞描述

在处理NTLMv2认证时,如果解码后的type-2 “TargetInfo"足够大,使得ntresplen+header_size超过NTLM_BUFSIZE(1024),代码会提前返回而没有释放ntlmv2resp,导致内存泄漏。

漏洞位置: https://github.com/curl/curl/blob/7d5d0645e5562f65005488fbc7fd270a71239a5f/lib/vauth/ntlm.c#L789

安全影响

安全影响等级: 低

恶意对等方可以将TargetInfo设置为超过阈值,导致客户端内存泄漏。通过重复连接,可以逐渐耗尽客户端资源。

该漏洞是使用Theori的Xint Code(AI驱动工具)发现的,但经过我们人类研究人员的验证和确认。

受影响版本

影响最新版本8.16.0

复现步骤

使用curl模糊测试器作为PoC问题的方法,但在实际环境中,该问题可能由恶意HTTP服务器触发。

  1. 构建curl模糊测试器:

    1
    2
    
    git clone https://github.com/curl/curl-fuzzers.git
    cd curl-fuzzers && ./mainline.sh
    
  2. 运行附加的脚本生成PoC输入:

    1
    
    python poc.py # 输出 ./input.bin
    
  3. 在生成的输入上运行模糊测试器:

    1
    
    ./build/curl_fuzzer_http input.bin
    

内存泄漏详情

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
=================================================================
==2757954==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 964 byte(s) in 1 object(s) allocated from:
    #0 0x563de804999d in calloc
    #1 0x563de80e4a90 in curl_dbg_calloc
    #2 0x563de84520ca in Curl_ntlm_core_mk_ntlmv2_resp
    #3 0x563de83e6420 in Curl_auth_create_ntlm_type3_message
    #4 0x563de8357698 in Curl_output_ntlm
    #5 0x563de82ed9f2 in output_auth_headers http.c
    #6 0x563de82ed30f in Curl_http_output_auth
    #7 0x563de82e3afd in Curl_http
    #8 0x563de812b460 in multi_do multi.c
    #9 0x563de81288a9 in state_do multi.c
    #10 0x563de8116b00 in multi_runsingle multi.c
    #11 0x563de8114894 in curl_multi_perform
    #12 0x563de808c3f7 in fuzz_handle_transfer(fuzz_data*)
    #13 0x563de808a70d in LLVMFuzzerTestOneInput
    #14 0x563de9487ff9 in main
    #15 0x7f5e7782a1c9 in __libc_start_call_main
    #16 0x7f5e7782a28a in __libc_start_main
    #17 0x563de7fae964 in _start

SUMMARY: AddressSanitizer: 964 byte(s) leaked in 1 allocation(s).

技术讨论

初始补丁

bagder (curl工作人员) 提供了初始补丁:

 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
diff --git a/lib/vauth/ntlm.c b/lib/vauth/ntlm.c
index 791fc87d11..d860fbbd50 100644
--- a/lib/vauth/ntlm.c
+++ b/lib/vauth/ntlm.c
@@ -786,35 +786,35 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data,
   });
 
   /* ntresplen + size should not be risking an integer overflow here */
   if(ntresplen + size > sizeof(ntlmbuf)) {
     failf(data, "incoming NTLM message too big");
-    return CURLE_OUT_OF_MEMORY;
+    result = CURLE_TOO_LARGE;
+    goto error;
   }
   DEBUGASSERT(size == (size_t)ntrespoff);
   memcpy(&ntlmbuf[size], ptr_ntresp, ntresplen);
   size += ntresplen;
 
   DEBUG_OUT({
     curl_mfprintf(stderr, "\n   ntresp=");
     ntlm_print_hex(stderr, (char *)&ntlmbuf[ntrespoff], ntresplen);
   });
 
-  free(ntlmv2resp);/* Free the dynamic buffer allocated for NTLMv2 */
-
   DEBUG_OUT({
     curl_mfprintf(stderr, "\n   flags=0x%02.2x%02.2x%02.2x%02.2x 0x%08.8x ",
                   LONGQUARTET(ntlm->flags), ntlm->flags);
     ntlm_print_flags(stderr, ntlm->flags);
     curl_mfprintf(stderr, "\n****\n");
   });
 
   /* Make sure that the domain, user and host strings fit in the
      buffer before we copy them there. */
   if(size + userlen + domlen + hostlen >= NTLM_BUFSIZE) {
-    failf(data, "user + domain + hostname too big");
-    return CURLE_OUT_OF_MEMORY;
+    failf(data, "user + domain + hostname too big for NTLM");
+    result = CURLE_TOO_LARGE;
+    goto error;
   }
 
   DEBUGASSERT(size == domoff);
   if(unicode)
     unicodecpy(&ntlmbuf[size], domain, domlen / 2);
@@ -840,10 +840,13 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data,
   size += hostlen;
 
   /* Return the binary blob. */
   result = Curl_bufref_memdup(out, ntlmbuf, size);
 
+error:
+  free(ntlmv2resp);/* Free the dynamic buffer allocated for NTLMv2 */
+
   Curl_auth_cleanup_ntlm(ntlm);
 
   return result;
 }

最大泄漏大小

通过将target_info_len设置为65535,确认最大泄漏大小为65KB:

1
Direct leak of 65599 byte(s) in 1 object(s) allocated from:

独立复现方法

构建curl:

1
2
3
./buildconf
./configure --with-ssl
CFLAGS="-fsanitize=address" make -j

运行PoC:

1
2
cat resp.bin | nc -l 8000 &
./src/curl --ntlm -u "user:pass" http://localhost:8000

安全影响评估

攻击场景

  • 长运行进程代表外部/用户请求发出请求的系统
  • 定期轮询端点(例如健康检查、状态报告等)
  • 接受来自非受信任用户的请求URL的应用程序(例如webhooks、回调、SSO等)

实际风险

由于此泄漏实际上需要中间人攻击才能发生,而此类攻击者有更大的潜力造成实际损害,而不是每次认证错误触发64K泄漏,因此这被视为"只是一个错误"而不是安全漏洞。

修复状态

该错误已在git中合并,并将包含在下一个版本8.17.0中。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计