libcurl SOCKS5 GSS-API竞争条件漏洞分析

本文详细分析了libcurl中SOCKS5 GSS-API认证过程中存在的竞争条件漏洞,多个并发认证共享全局gss_context而缺乏同步机制,导致数据竞争和未定义行为。

Race condition on global gss_context during SOCKS5 GSS-API negotiation in libcurl

摘要

并发SOCKS5 GSS-API认证共享一个文件作用域的全局gss_context而缺乏同步机制,导致数据竞争和未定义行为。

全局上下文定义位置

1
static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;

每次协商时通过地址传递给GSS初始化例程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gss_major_status = Curl_gss_init_sec_context(data,
                                             &gss_minor_status,
                                             &gss_context,
                                             server,
                                             &Curl_krb5_mech_oid,
                                             NULL,
                                             gss_token,
                                             &gss_send_token,
                                             TRUE,
                                             &gss_ret_flags);

漏洞验证

使用Helgrind和libcurl的DEBUGBUILD GSS存根(CURL_STUB_GSS_CREDS=KRB5)来避免libkrb5内部噪音,观察到了可重现的数据竞争,直接引用了全局符号gss_context:

Helgrind证据(来自docker_helgrind.log):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
==XXXX== Possible data race during read of size 8 at ...
==XXXX==    at 0x1CEFC0: stub_gss_init_sec_context (curl_gssapi.c:126)
==XXXX==    by 0x1CEE43: Curl_gss_init_sec_context (curl_gssapi.c:329)
==XXXX==    by 0x1BCB3B: Curl_SOCKS5_gssapi_negotiate (socks_gssapi.c:184)
...
==XXXX== This conflicts with a previous write of size 8 by thread #Y
==XXXX==    at 0x1CF4A4: stub_gss_init_sec_context (curl_gssapi.c:264)
==XXXX==    by 0x1CEE43: Curl_gss_init_sec_context (curl_gssapi.c:329)
==XXXX==    by 0x1BCB3B: Curl_SOCKS5_gssapi_negotiate (socks_gssapi.c:184)
==XXXX==  Address ... is 0 bytes inside data symbol "gss_context"

受影响版本

  • 从当前master构建(配置摘要报告:curl版本:8.17.0-DEV)
  • Ubuntu 24.04(arm64)容器
  • GSS-API提供者:MIT Kerberos(系统libgssapi_krb5),也通过CURL_STUB_GSS_CREDS=KRB5使用curl的DEBUGBUILD GSS存根进行了测试

重现步骤

1. 启动Ubuntu容器并安装先决条件

1
2
3
4
5
6
7
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq clang make autoconf automake libtool pkg-config libkrb5-dev python3 valgrind > /dev/null
  update-ca-certificates > /dev/null 2>&1 || true
'

2. 配置和构建libcurl(调试,GSS-API,最小依赖)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  if [ -f Makefile ]; then make distclean || true; fi
  if [ -x ./buildconf ]; then ./buildconf; else autoreconf -fi; fi
  export CC=clang CFLAGS="-O0 -g -fno-omit-frame-pointer" LDFLAGS=""
  ./configure --enable-debug --with-gssapi --disable-shared \
    --without-ssl --without-libidn2 --without-libpsl --without-libssh2 \
    --without-brotli --without-zstd --without-nghttp2 --without-nghttp3 --without-ngtcp2
  make -j"$(nproc)"
'

3. 启动选择GSS-API(方法1)的最小SOCKS5代理

 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
import socket, threading, struct

def handle(c):
    try:
        data=c.recv(262)
        if len(data)<3: return
        c.sendall(b"\x05\x01")
        hdr=c.recv(4)
        if len(hdr)<4: return
        ln=struct.unpack("!H", hdr[2:4])[0]
        _=c.recv(ln)
        c.sendall(b"\x01\x01\x00\x01D")
        hdr=c.recv(4)
        if len(hdr)<4: return
        ln=struct.unpack("!H", hdr[2:4])[0]
        _=c.recv(ln)
        c.sendall(b"\x01\x02\x00\x01\x00")
    finally:
        try: c.close()
        except: pass

s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(("0.0.0.0",1081)); s.listen()
print("listening 0.0.0.0 1081", flush=True)
while True:
    c,_=s.accept()
    threading.Thread(target=handle,args=(c,),daemon=True).start()

4. 使用libcurl构建多线程PoC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <pthread.h>
#include <curl/curl.h>
static void *worker(void *u){
  CURL *e=curl_easy_init();
  curl_easy_setopt(e,CURLOPT_URL,(char*)u);
  curl_easy_setopt(e,CURLOPT_PROXY,"socks5://127.0.0.1:1081");
  curl_easy_setopt(e,CURLOPT_SOCKS5_GSSAPI_SERVICE,"rcmd");
  curl_easy_setopt(e,CURLOPT_SOCKS5_AUTH,(long)CURLAUTH_GSSAPI);
  for(int i=0;i<50;++i) curl_easy_perform(e);
  curl_easy_cleanup(e);
  return 0;
}
int main(void){
  curl_global_init(CURL_GLOBAL_DEFAULT);
  enum { N=16 }; pthread_t th[N];
  for(int i=0;i<N;++i) pthread_create(&th[i],0,worker,"http://example.com");
  for(int i=0;i<N;++i) pthread_join(th[i],0);
  curl_global_cleanup();
  return 0;
}

5. 在Helgrind下运行以显示curl中的竞争

1
2
3
4
5
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  export CURL_STUB_GSS_CREDS=KRB5
  valgrind --tool=helgrind --quiet --fair-sched=try ./test_gss_race | tee docker_helgrind.log
'

影响分析

共享的全局gss_context在连接之间无同步地并发访问和修改。这带来了实际风险:

  • 由于GSS上下文处理中的未定义行为和竞争导致进程崩溃/拒绝服务
  • 在负载下SOCKS5代理协商期间的身份验证失败或异常行为

未观察到或声称内存泄露或RCE,已验证的影响是在多个句柄/认证并发运行时与并发相关的不稳定性(DoS/UB)。

修复状态

curl开发团队已通过PR #18711修复此问题,将gss_context改为Curl_SOCKS5_gssapi_negotiate()的局部变量,并在所有退出时删除,与其他curl GSS路径(SPNEGO/SASL使用每连接上下文)保持一致。

安全评估

该漏洞被评估为信息性安全问题,主要影响可用性:

  • 触发条件:在同一进程中进行并发SOCKS5+GSS协商
  • 主要风险:崩溃/中止(DoS)、间歇性身份验证失败、损坏的GSS状态
  • 未证明机密性/完整性受损
  • CWE-362:竞争条件
  • 严重性:低(仅影响可用性)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计