Curl Telnet 子选项缓冲区指针下溢漏洞导致越界读取技术分析

本文详细分析了Curl库lib/telnet.c文件中存在的一个Telnet子选项缓冲区指针下溢漏洞。该漏洞在处理特定状态时,无条件地进行指针递减,可能导致越界读取和敏感信息泄露。文章包含技术细节、漏洞复现步骤及修复建议。

Telnet 子选项缓冲区指针下溢漏洞导致越界读取

摘要

Curl 的 Telnet 协议处理器(lib/telnet.c)中存在一个缓冲区指针下溢漏洞。当处理 CURL_TS_SE 状态下的 telnet 子选项时,代码无条件地将子选项缓冲区指针递减 2(subpointer -= 2),即使 CURL_SB_ACCUM 宏因缓冲区已满而跳过写入。这导致后续调用 suboption()printsub() 时发生越界读取。

受影响版本

  • 所有包含此代码模式且支持 Telnet 的 curl 版本
  • 在 GitHub 上的最新 curl 源码(master 分支)中测试
  • 文件:lib/telnet.c
  • 易受攻击的行:1210 和 1226

技术分析

易受攻击的代码模式

lib/telnet.c 中,CURL_SB_ACCUM 宏(第 69-73 行)有条件地写入缓冲区:

1
2
3
4
5
#define CURL_SB_ACCUM(x, c)                                   \
  do {                                                        \
    if(x->subpointer < (x->subbuffer + sizeof(x->subbuffer))) \
      *x->subpointer++ = (c);                                 \
  } while(0)

然而,在 CURL_TS_SE 状态处理中(第 1207-1211 行和第 1223-1227 行),指针递减是无条件的:

路径 1(第 1207-1211 行):

1
2
3
4
CURL_SB_ACCUM(tn, CURL_IAC);
CURL_SB_ACCUM(tn, c);
tn->subpointer -= 2;    // 无条件 - 缓冲区满时导致下溢
CURL_SB_TERM(tn);

路径 2(第 1223-1227 行):

1
2
3
4
CURL_SB_ACCUM(tn, CURL_IAC);
CURL_SB_ACCUM(tn, CURL_SE);
tn->subpointer -= 2;    // 无条件 - 缓冲区满时导致下溢
CURL_SB_TERM(tn);

利用流程

  1. 恶意 telnet 服务器发送子选项数据填满 512 字节缓冲区(SUBBUFSIZE)。
  2. 缓冲区已满时,服务器发送 IAC 后跟另一个字节。
  3. CURL_SB_ACCUM 宏不执行任何操作(缓冲区已满检查失败)。
  4. subpointer -= 2 无条件执行,导致指针下溢。
  5. CURL_SB_TERM 设置 subend = subpointer(现在指向缓冲区起始位置之前)。
  6. 调用 suboption(),参数为损坏的指针。
  7. CURL_SB_LEN 宏计算出负数/回绕的长度。
  8. 在详细模式下,printsub() 读取越界内存。

内存布局(结构体 TELNET)

1
2
3
4
5
6
7
8
struct TELNET {
  // ... 其他字段 ...
  struct dynbuf out;              // 包含堆指针
  unsigned char subbuffer[512];   // 子选项缓冲区
  unsigned char *subpointer;      // 指向 subbuffer 内部
  unsigned char *subend;          // 结束标记
  // ...
};

subpointer 下溢时,它指向 dynbuf out 结构体,该结构体包含可能泄露的堆指针。

复现步骤

  1. 将附带的 PoC 脚本保存为 curl_telnet_poc.py
  2. 运行恶意 telnet 服务器:
    1
    
    python3 curl_telnet_poc.py -p 2323
    
  3. 在另一个终端中,使用详细模式连接 curl:
    1
    
    curl -v telnet://127.0.0.1:2323
    
  4. 观察详细输出中是否包含:
    • 输出中的异常子选项数据
    • 表示内存泄露的潜在非打印字符
    • 可能的崩溃(取决于内存布局)

为了更好地观察,可以使用 AddressSanitizer 构建 curl:

1
2
3
./configure CFLAGS="-fsanitize=address -g"
make
./src/curl -v telnet://127.0.0.1:2323

ASan 应报告越界读取。

支持材料/参考

影响

安全影响

  1. 信息泄露(低-中):当 curl 以详细模式(-v)运行时,printsub() 函数可能会读取并显示子选项缓冲区之前的内存内容。这可能泄露:
    • 堆指针(可用于链式攻击中的 ASLR 绕过)
    • dynbuf out 结构体的内容
    • 相邻内存中的其他敏感数据
  2. 拒绝服务(低):根据内存布局和访问模式,越界读取可能导致崩溃。

攻击场景

控制恶意 telnet 服务器的攻击者可以:

  1. 等待受害者使用 curl -v telnet://malicious-server 连接。
  2. 发送精心构造的 telnet 子选项数据以触发漏洞。
  3. 可能观察错误消息或日志中泄露的内存内容。
  4. 使用泄露的堆指针来帮助利用其他漏洞。

局限性

  • 要求受害者连接到攻击者控制的 telnet 服务器。
  • 在详细模式(-v 标志)下影响最大。
  • Telnet 协议已被弃用且很少使用。
  • 没有直接的代码执行能力。

漏洞确认与修复建议

测试输出 RCVD IAC SB (terminated by 65 65, not IAC SE) 证实了漏洞的存在。预期应找到 IAC SE(字节 255, 240),但实际上找到了 65 65(ASCII ‘A’ ‘A’)。这是因为 CURL_SB_LEN 由于无条件的 subpointer -= 2 返回了不正确的值。这是一个逻辑错误,不是内存损坏。

建议的修复方法:

1
2
3
4
5
6
7
8
// 在 ACCUM 调用之前,保存指针
unsigned char *sb_start = tn->subpointer;
CURL_SB_ACCUM(tn, CURL_IAC);
CURL_SB_ACCUM(tn, CURL_SE);
// 仅当两个字节都实际写入时才递减
if(tn->subpointer == sb_start + 2)
    tn->subpointer -= 2;
CURL_SB_TERM(tn);

应用于两处:第 1208-1211 行和第 1223-1227 行。

由于此信息泄露仅出现在详细输出中,因此不被视为安全漏洞,但它是一个 Bug。项目的最终修复方案是在错误条件下返回错误,而不是尝试继续处理(相关PR)。

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