报告 #3442060 - curl项目状态机中的无限循环问题
时间线
摘要
漏洞影响:当curl尝试从恶意FTP服务器下载文件时,会触发代码执行中的无限循环。
我在curl项目的FTP功能中发现了这个问题。如 https://github.com/curl/curl/blob/master/docs/cmdline-opts/disable-epsv.md 中所述,curl使用EPSV模式作为默认的FTP文件传输方法。简单来说,EPSV模式的工作方式如下:FTP服务器打开一个TCP端口并等待客户端连接,然后通过该端口向客户端发送数据。
在curl的状态机中,/lib/multi.c中的 state_performing 函数在 [1] 处调用 Curl_sendrecv 函数来接收对端的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static CURLMcode state_performing(struct Curl_easy *data,
struct curltime *nowp,
bool *stream_errorp,
CURLcode *resultp)
{
char *newurl = NULL;
bool retry = FALSE;
CURLMcode rc = CURLM_OK;
CURLcode result = *resultp = CURLE_OK;
*stream_errorp = FALSE;
if(mspeed_check(data, nowp) == CURLE_AGAIN)
return CURLM_OK;
/* read/write data if it is ready to do so */
result = Curl_sendrecv(data, nowp); // [1] call Curl_sendrecv
|
Curl_sendrecv 函数在 [2] 处调用 sendrecv_dl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp)
{
struct SingleRequest *k = &data->req;
CURLcode result = CURLE_OK;
DEBUGASSERT(nowp);
if(Curl_xfer_is_blocked(data)) {
result = CURLE_OK;
goto out;
}
/* We go ahead and do a read if we have a readable socket or if the stream
was rewound (in which case we have data in a buffer) */
if(k->keepon & KEEP_RECV) {
result = sendrecv_dl(data, k); //[2] call sendrecv_dl
if(result || data->req.done)
goto out;
}
|
sendrecv_dl 函数进一步调用 xfer_recv_resp 来接收数据。如果我们的恶意FTP服务器打开了一个EPSV端口但未向客户端发送任何数据,xfer_recv_resp 将返回-1,并且 result 的值将被设置为 CURLE_AGAIN。随后,在 [4] 处,result 被设置为 CURLE_OK。这意味着即使 xfer_recv_resp 未能接收到任何数据,sendrecv_dl 函数仍然返回 CURLE_OK。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static CURLcode sendrecv_dl(struct Curl_easy *data,
struct SingleRequest *k)
{
...
rcvd_eagain = FALSE;
nread = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &result); // [3] call xfer_recv_resp
if(nread < 0) {
if(CURLE_AGAIN != result)
goto out; /* real error */
rcvd_eagain = TRUE;
result = CURLE_OK; //[4]set result to CURLE_OK
if(data->req.download_done && data->req.no_body &&
!data->req.resp_trailer) {
DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, "
"not waiting for EOS"));
nread = 0;
/* continue as if we received the EOS */
}
else
break; /* get out of loop */
}
|
让我们继续检查 state_performing 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
static CURLMcode state_performing(struct Curl_easy *data,
struct curltime *nowp,
bool *stream_errorp,
CURLcode *resultp)
{
char *newurl = NULL;
bool retry = FALSE;
CURLMcode rc = CURLM_OK;
CURLcode result = *resultp = CURLE_OK;
*stream_errorp = FALSE;
if(mspeed_check(data, nowp) == CURLE_AGAIN)
return CURLM_OK;
/* read/write data if it is ready to do so */
result = Curl_sendrecv(data, nowp);
if(data->req.done || (result == CURLE_RECV_ERROR)) { // [5] data->req.done == 0, result==CURLE_OK
|
由于未接收到任何数据,data->req.done 标志保持为0。此外,因为 result 是 CURLE_OK,所以 [5]、[6] 和 [7] 处的条件检查结果都为假。代码随后执行到 [8](未出错,未完成)。
这导致curl的当前状态机标志(data->mstate)保持不变,仍设置为 MSTATE_PERFORMING。随后,curl代码反复进入 state_performing 函数,导致无限循环。
受影响版本
1
2
3
4
5
|
➜ src ./curl -V
curl 8.17.0-DEV (x86_64-pc-linux-gnu) libcurl/8.17.0-DEV zlib/1.2.11 libpsl/0.19.1
Release-Date: [unreleased]
Protocols: dict file ftp gopher http imap ipfs ipns mqtt pop3 rtsp smtp telnet tftp ws
Features: alt-svc AsynchDNS IPv6 Largefile libz PSL threadsafe UnixSockets
|
复现步骤
- 下载附带的
./ftp_poc.py 文件并运行:sudo python3 ./ftp_poc.py。这将在当前机器的21端口启动一个恶意FTP服务。
- 运行以下curl命令,将命令中的
192.168.23.1 替换为您自己的恶意FTP服务器的地址:
1
|
./curl -u anonymous:123 'ftp://192.168.23.1/test' -o ./test
|
curl程序将进入无限代码循环,不会自行退出。
影响
摘要:当curl尝试从恶意FTP服务器下载文件时,会触发代码执行中的无限循环。
bagder (curl staff) 发表了一条评论。 13天前
感谢您的报告!
我们将花些时间调查您的报告,并尽快向您提供详细信息和可能的后续问题!很可能在接下来的24小时内。
我们始终努力尽快修复报告的问题。严重性为低或中等的问题,我们会在常规发布周期中合并到下一个版本。只有对于更严重的问题,我们可能会提前发布修复。
bagder (curl staff) 发表了一条评论。 13天前
@kak1 这与服务器只是停止发送任何数据,而curl只是坐在那里等待更多数据直到时间结束(除非提供了额外的超时选项)有什么不同?这似乎是curl设计的工作方式?
dgustafsson (curl staff) 发表了一条评论。 13天前
如果文件传输是无限的,那么curl的正确行为是无限下载它。
kak1 发表了一条评论。 13天前
区别在于,在这个问题中,即使手动设置了超时,curl进程也不会终止。然而,我在循环中没有找到任何内存分配代码,因此不会发生内存泄漏。目前,它只能导致CPU使用。如果此类问题不被视为安全漏洞,请关闭此报告。
bagder (curl staff) 发表了一条评论。 13天前
即使手动设置了超时,curl进程也不会终止
这与我针对您的PoC服务器的curl工作方式不符。它可以正常超时:
1
2
3
4
5
6
|
$ curl ftp://localhost:9021/ -v -m2
...
curl: (28) Operation timed out after 2002 milliseconds with 0 bytes received
$ curl ftp://localhost:9021/RE -v -m2
...
curl: (28) Operation timed out after 2002 milliseconds with 0 out of 4 bytes received
|
kak1 发表了一条评论。 13天前
请使用我提供的PoC文件和命令,因为触发此问题需要特定的协议交互。我仅为此目的调整了所提供的命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
➜ curl sudo python3 ./ftp_poc.py
FTP server 0.0.0.0:21
waiting for connect...
client ('192.168.23.1', 28513) connect
-> 220 Welcome to Simple FTP Server
<- USER anonymous
-> 331 Please specify the password.
<- PASS 123
-> 230 Login successful.
<- PWD
-> 257 "/" is the current directory
<- EPSV
-> 229 Entering Extended Passive Mode (|||32971|)
EPSV port: 32971
<- TYPE I
-> 200 Switching to Binary mode.
<- SIZE test
-> 213 213 4
<- RETR test
-> 150 Opening BINARY mode data connection for test (4 bytes).
-> 226 Transfer complete.
|
1
2
3
4
|
➜ src ./curl -u anonymous:123 'ftp://192.168.23.1/test' -o ./test --connect-timeout 30
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 4 0 0 0 0 0 0 --:--:-- 0:09:32 --:--:-- 0
|
kak1 发表了一条评论。 13天前
添加 -m 2 参数后,curl超时了。看来这个问题可以缓解。请关闭此报告。
bagder (curl staff) 发表了一条评论。 更新于13天前
您需要使用 -m (--max-time)。--connect-timeout 在您的情况下不起任何作用,因为它能及时连接。
bagder (curl staff) 关闭了报告并将状态更改为不适用。 13天前
认为这不是一个安全问题。
bagder (curl staff) 请求披露此报告。 13天前
根据项目的透明度政策,我们希望所有报告都被披露并公开。
bagder (curl staff) 披露了此报告。 13天前
报告于 2025年11月26日 UTC 上午8:34
报告者 kak1
报告给 curl
参与者
报告ID #3442060
N/A
严重性 无评级 (—)
披露时间 2025年11月26日 UTC 上午9:32
弱点 无
CVE ID 无
赏金 无
账户详情 无