OpenSSL HTTP/3 中 CURLINFO_TLS_SSL_PTR 返回无效指针的安全漏洞分析

本文详细分析了 libcurl 在 OpenSSL HTTP/3 连接中返回无效 SSL 连接指针的问题,包括漏洞复现步骤、潜在影响及可能的远程代码执行向量,提供了技术细节和修复建议。

OpenSSL HTTP/3 中 CURLINFO_TLS_SSL_PTR 返回无效指针的安全漏洞分析

摘要

curl_easy_getinfo CURLINFO_TLS_SSL_PTR 在 OpenSSL HTTP/3 连接中返回无效的 SSL 连接指针。使用此 SSL 连接会导致崩溃,并可能产生其他影响。此问题在 libcurl 8.14.1 中未出现,表明该漏洞存在于 libcurl 本身(或 libcurl 8.14.1 未触发其他支持库中的漏洞)。一些调试输出表明这可能是一个释放后使用/悬空指针问题。如果是这样,该问题可能导致远程代码执行。在撰写本报告时,这一点尚未得到确认。

受影响版本

  • 8.15.0(发布版本)
  • 8.15.1-DEV(7c23e88d17e0939b4e01c8d05f430e167e148f4b)

复现步骤

  1. 使用 OpenSSL 3.5.1 及足够新的 nghttp2、nghttp3 和 ngtcp2 编译 libcurl(--with-openssl --with-nghttp2 --with-ngtcp2 --with-nghttp3)。
  2. 使用 -fsanitize=address 编译以下概念验证应用:
 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
#include <curl/curl.h>
#include <openssl/ssl.h>
#include <stdio.h>

static size_t header_callback(char *buffer, size_t size,
                              size_t nitems, void *userdata)
{
  size_t actual = nitems * size;
  CURL *curl = (CURL *) userdata;
  size_t printactual = actual;

  if (printactual && buffer[printactual - 1] == '\n')
  {
    printactual--;
    if (printactual && buffer[printactual - 1] == '\r')
      printactual--;
  }
  printf("H: %.*s\n", (int) printactual, buffer);

  if ((actual == 1 && buffer[0] == '\n') ||
      (actual == 2 && buffer[0] == '\r' && buffer[1] == '\n'))
  {
    const struct curl_tlssessioninfo *info = NULL;
    CURLcode res = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &info);
    printf("D: headers completed, res %d info %p\n", res, info);
    if (info && !res)
    {
      printf("D: backend %d internals %p\n", info->backend,  info->internals);
      if(CURLSSLBACKEND_OPENSSL == info->backend)
      {
        SSL *ssl = info->internals;
        printf("D: OpenSSL ver. %s\n", SSL_get_version(ssl));
        STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
        printf("D: chain %p\n", chain);
      }
    }
  }
  return actual;
}

int main(void)
{
  printf("libcurl version: %s\n", curl_version());

  CURL *curl = curl_easy_init();
  if (curl)
  {
    curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se");
    curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3ONLY);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, curl);
    curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }

  return 0;
}
  1. 执行 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
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
66
67
68
69
70
71
72
73
74
$ ./curlpoc
libcurl version: libcurl/8.15.1-DEV OpenSSL/3.5.1 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.66.0 ngtcp2/1.14.0-DEV nghttp3/1.1 OpenLDAP/2.6.10
H: HTTP/3 200
H: content-length: 11441
H: server: nginx/1.27.5
H: content-type: text/html
H: x-frame-options: SAMEORIGIN
H: last-modified: Sun, 20 Jul 2025 02:11:09 GMT
H: etag: "2cb1-63a52df596e87"
H: cache-control: max-age=60
H: expires: Sun, 20 Jul 2025 02:14:40 GMT
H: x-content-type-options: nosniff
H: content-security-policy: default-src 'self' curl.haxx.se www.curl.se curl.se; style-src 'unsafe-inline' 'self' curl.haxx.se www.curl.se curl.se; require-trusted-types-for 'script';
H: strict-transport-security: max-age=31536000
H: via: 1.1 varnish, 1.1 varnish
H: accept-ranges: bytes
H: date: Sun, 20 Jul 2025 04:44:47 GMT
H: age: 69
H: x-served-by: cache-bma-essb1270054-BMA, cache-hel1410021-HEL
H: x-cache: HIT, HIT
H: x-cache-hits: 1, 15
H: x-timer: S1752986688.638651,VS0,VE0
H: vary: Accept-Encoding
H: alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
H:
D: headers completed, res 0 info 0x622000010600
D: backend 1 internals 0x61c000001080
D: OpenSSL ver. unknown
AddressSanitizer:DEADLYSIGNAL
=================================================================
==81895==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000005c9 (pc 0x00010472785c bp 0x00016b6ed690 sp 0x00016b6ed690 T0)
==81895==The signal is caused by a READ memory access.
==81895==Hint: address points to the zero page.
    #0 0x00010472785c in SSL_get_peer_cert_chain+0x28 (libssl.3.dylib:arm64+0x1385c)
    #1 0x000104700c5c in header_callback+0x2d0 (curlpoc:arm64+0x100000c5c)
    #2 0x00010488a494 in cw_out_ptr_flush+0x10c (libcurl.4.dylib:arm64+0x1a494)
    #3 0x00010488a000 in cw_out_do_write+0xa8 (libcurl.4.dylib:arm64+0x1a000)
    #4 0x000104889c74 in cw_out_write+0x70 (libcurl.4.dylib:arm64+0x19c74)
    #5 0x00010488a870 in cw_pause_write+0x200 (libcurl.4.dylib:arm64+0x1a870)
    #6 0x0001048c6d1c in cw_download_write+0x94 (libcurl.4.dylib:arm64+0x56d1c)
    #7 0x0001048c59cc in Curl_client_write+0x48 (libcurl.4.dylib:arm64+0x559cc)
    #8 0x0001048a3180 in http_write_header+0x54 (libcurl.4.dylib:arm64+0x33180)
    #9 0x0001048a2068 in http_rw_hd+0x1598 (libcurl.4.dylib:arm64+0x32068)
    #10 0x00010489dfac in Curl_http_write_resp_hd+0x28 (libcurl.4.dylib:arm64+0x2dfac)
    #11 0x0001048f55ac in cb_h3_end_headers+0xe0 (libcurl.4.dylib:arm64+0x855ac)
    #12 0x00010497ecd8 in nghttp3_conn_read_bidi nghttp3_conn.c
    #13 0x00010497e4c4 in nghttp3_conn_read_stream nghttp3_conn.c:526
    #14 0x0001048f4790 in cb_recv_stream_data+0x4c (libcurl.4.dylib:arm64+0x84790)
    #15 0x000104a2c4dc in conn_recv_stream ngtcp2_conn.c:7103
    #16 0x000104a2b178 in conn_recv_pkt ngtcp2_conn.c:9077
    #17 0x000104a1fcc4 in ngtcp2_conn_read_pkt_versioned ngtcp2_conn.c:9855
    #18 0x0001048f5cb8 in recv_pkt+0x80 (libcurl.4.dylib:arm64+0x85cb8)
    #19 0x0001048f67c4 in vquic_recv_packets+0x12c (libcurl.4.dylib:arm64+0x867c4)
    #20 0x0001048f34cc in cf_ngtcp2_recv+0xe8 (libcurl.4.dylib:arm64+0x834cc)
    #21 0x0001048d5730 in Curl_sendrecv+0x254 (libcurl.4.dylib:arm64+0x65730)
    #22 0x0001048b96d8 in multi_runsingle+0x980 (libcurl.4.dylib:arm64+0x496d8)
    #23 0x0001048b8b68 in curl_multi_perform+0x200 (libcurl.4.dylib:arm64+0x48b68)
    #24 0x00010488db84 in curl_easy_perform+0x188 (libcurl.4.dylib:arm64+0x1db84)
    #25 0x00010470096c in main+0x84 (curlpoc:arm64+0x10000096c)
    #26 0x0001991feb94 in start+0x17b8 (dyld:arm64e+0xfffffffffff3ab94)

==81895==Register values:
 x[0] = 0x000061c000001080   x[1] = 0x0000000000000000   x[2] = 0x00000000000120a8   x[3] = 0x0000000000000002
 x[4] = 0x0000000104700fa0   x[5] = 0x000000016b6ed690   x[6] = 0x000000016af04000   x[7] = 0x0000000000000001
 x[8] = 0x0000000000000301   x[9] = 0x00000002076b8588  x[10] = 0x0000000000000002  x[11] = 0x0000010000000000
x[12] = 0x00000000fffffffd  x[13] = 0x0000000000000000  x[14] = 0x0000000000000000  x[15] = 0x0000000000000000
x[16] = 0x0000000104727834  x[17] = 0x00000002086fde70  x[18] = 0x0000000000000000  x[19] = 0x000000016b6ed6e0
x[20] = 0x0000000000000000  x[21] = 0x0000000000000002  x[22] = 0x000061c000001080  x[23] = 0x000000016b6ed6c0
x[24] = 0x0000000000000001  x[25] = 0x000000016b6ed6a0  x[26] = 0x000000702d6fdad4  x[27] = 0x0000007000020000
x[28] = 0x000000010470098c     fp = 0x000000016b6ed690     lr = 0x0000000104700c60     sp = 0x000000016b6ed690
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (libssl.3.dylib:arm64+0x1385c) in SSL_get_peer_cert_chain+0x28
==81895==ABORTING
Abort trap: 6

支持材料/参考

本报告不含 AI 生成内容,供您愉快阅读。停止垃圾内容!

影响

摘要

  • 至少:访问 HTTP/3 站点时导致拒绝服务。
  • 潜在:如果返回的指针指向已释放的内存,可能导致内存损坏。根据目标平台的不同,可能实现远程代码执行。需要进一步分析以确定完整影响。

详细分析

问题根源

在 OpenSSL HTTP/3 连接中,curl_easy_getinfo CURLINFO_TLS_SSL_PTR 返回的指针实际上是另一个 OpenSSL 对象的指针,而非有效的 SSL 连接指针。此错误在 libcurl 8.15.0 中引入,仅影响 HTTP/3 连接。

潜在利用向量

  1. 拒绝服务(DoS):使用返回的无效指针会导致应用程序崩溃。
  2. 内存损坏:如果返回的指针指向已释放的内存,可能导致内存损坏。
  3. 远程代码执行(RCE):如果攻击者能够通过堆操纵构造有效的函数指针,可能实现远程代码执行。但此利用需要满足多个复杂条件,包括堆地址泄露和特定的堆布局。

利用条件

  • 应用程序必须使用此回调。
  • 恶意代码必须与应用程序共享同一堆,并能够以特定方式操纵堆。
  • 攻击者代码必须能够触发应用程序执行 HTTP/3 请求,并在堆设置完全符合预期时触发此回调。
  • 应用程序在回调中必须仅使用一组相当罕见的 OpenSSL 函数,且回调在未成功利用时立即崩溃。
  • 成功的攻击很可能需要获取堆地址,这需要通过此错误之外的其他信息。

修复建议

已提交修复 PR:https://github.com/curl/curl/pull/18066

结论

尽管此漏洞理论上可能导致远程代码执行,但由于利用条件复杂且要求严格,实际安全影响有限。建议用户升级到修复版本以避免潜在问题。

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