Neutron, notre moteur d’IA, a obtenu un score de 97,1 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Analyse approfondie

Analyse approfondie

Plone RCE non authentifiée, corrigée en 6.2.3 (Hotfix 20261110)

Une chaîne de cinq failles permet à un visiteur anonyme d'exécuter du code sur Plone 6.0 à 6.2 et les versions antérieures ; des mises à jour la ferment.

En bref

  • Une chaîne de cinq failles permet à un visiteur anonyme d'exécuter du code sur Plone. Les modificateurs de versionnage publient leur méthode edit sans contrôle de permission, et quatre paquets ouvrent des modules Python entiers au code restreint.
  • Toutes les versions prises en charge sont concernées : Plone 6.2, 6.1 et 6.0, ainsi que les versions antérieures jusqu'à 4.3 via le paquet de correctifs.
  • Faille principale : CVSS v3.1 9,8 Critique, CWE-862. Aucun CVE, aucune exploitation connue, et aucune des failles n'est dans le catalogue Known Exploited Vulnerabilities de la CISA au 11 novembre 2026.
  • Mettez à jour les cinq paquets vers les versions corrigées, ou installez Products.PloneHotfix20261110 et redémarrez chaque instance Zope. Voir « Comment corriger ? ».

Suis-je concerné ?

Toutes ces conditions doivent être réunies :

  • Vous utilisez Plone 6.2, 6.1 ou 6.0, ou une version antérieure jusqu'à 4.3. L'avis liste toutes les versions prises en charge et indique que les versions antérieures sont aussi concernées.
  • Votre site possède Products.CMFEditions, le paquet de versionnage qui fournit l'outil portal_modifier. Plone l'inclut par défaut.
  • Un attaquant peut joindre le site en HTTP. Le point d'entrée est l'objet portal_modifier, que l'acquisition Zope rend accessible sous n'importe quel dossier.

La chaîne fonctionne sans compte. L'une des failles s'exécute aussi lorsqu'un éditeur enregistre du contenu versionné, donc un site non exposé directement garde un chemin interne. Le fournisseur indique que l'ensemble des mises à jour de paquets est la correction recommandée ; le correctif couvre les versions antérieures qui ne reçoivent plus de mises à jour.

L'image de conteneur officielle plone/plone-backend totalise 486 034 téléchargements sur Docker Hub, un indice du nombre de déploiements qui utilisent cette pile.

Branche Versions concernées Ensemble corrigé
Plone 6.2 Toutes les versions avant 6.2.3 Products.CMFPlone 6.2.3, Products.CMFEditions 5.0.2, Products.MimetypesRegistry 4.0.2, plone.batching 3.0.3, plone.restapi 10.2.1
Plone 6.1 Toutes les versions avant 6.1.6 Products.CMFPlone 6.1.6, Products.CMFEditions 4.0.7, Products.MimetypesRegistry 3.0.2, plone.batching 2.0.8, plone.restapi 9.15.8
Plone 6.0 Toutes les versions avant 6.0.16 Products.CMFPlone 6.0.16, Products.CMFEditions 4.0.7, Products.MimetypesRegistry 3.0.2, plone.batching 2.0.8, plone.restapi 9.15.8
Plone 4.3 à 5.2 Toutes les versions Aucune mise à jour de paquet ; installer Products.PloneHotfix20261110

Vérifiez les paquets installés depuis l'environnement virtuel ou le buildout qui exécute Zope :

pip show Products.CMFPlone Products.CMFEditions Products.MimetypesRegistry plone.batching plone.restapi
# Buildout: print the pinned versions
bin/buildout annotate | grep -E 'Products.CMFPlone|Products.CMFEditions|Products.MimetypesRegistry|plone.batching|plone.restapi'
# Constraints or requirements files used to build the instance
grep -R -E 'Products.CMFPlone|Products.CMFEditions|Products.MimetypesRegistry|plone.batching|plone.restapi' constraints.txt requirements.txt buildout.cfg

La nouvelle page @@check-modifier-conditions à la racine du site Plone est le moyen le plus rapide de voir si une condition de modificateur a été modifiée. Connectez-vous en tant que Manager et ouvrez https://your-site/@@check-modifier-conditions. Elle compare les conditions actuelles aux valeurs par défaut et propose de les rétablir.

Comment corriger ?

Mettez à jour chaque paquet de la ligne correspondant à votre branche Plone, puis redémarrez toutes les instances Zope. Si vous utilisez une version antérieure, installez le correctif à la place.

  1. Sur Plone 6.2, installez l'ensemble corrigé : pip install -U "Products.CMFPlone==6.2.3" "Products.CMFEditions==5.0.2" "Products.MimetypesRegistry==4.0.2" "plone.batching==3.0.3" "plone.restapi==10.2.1"
  2. Sur Plone 6.1, installez l'ensemble corrigé : pip install -U "Products.CMFPlone==6.1.6" "Products.CMFEditions==4.0.7" "Products.MimetypesRegistry==3.0.2" "plone.batching==2.0.8" "plone.restapi==9.15.8"
  3. Sur Plone 6.0, installez l'ensemble corrigé : pip install -U "Products.CMFPlone==6.0.16" "Products.CMFEditions==4.0.7" "Products.MimetypesRegistry==3.0.2" "plone.batching==2.0.8" "plone.restapi==9.15.8"
  4. Dans Buildout, épinglez les mêmes versions dans la section [versions] et relancez bin/buildout. Dans un fichier de contraintes, utilisez la forme à double égal, par exemple Products.CMFPlone==6.2.3.
  5. Sur toute version de 4.3 à 6.2 qui ne peut pas recevoir les mises à jour de paquets, installez le correctif et redémarrez chaque instance Zope : pip install Products.PloneHotfix20261110 Le correctif fonctionne avec Python 2.7 et Python 3. Si l'index de paquets ne le trouve pas sous cette orthographe, utilisez le nom normalisé products-plonehotfix20261110.
  6. Redémarrez toutes les instances Zope, pas seulement la première. Le correctif est chargé au démarrage du processus.

Si vous ne pouvez pas mettre à jour tout de suite, bloquez toute URL contenant portal_modifier au niveau du reverse proxy. L'acquisition Zope rend l'objet accessible sous n'importe quel dossier, donc bloquez-le partout dans le chemin, pas seulement à la racine du site.

  1. Dans nginx, ajoutez une location regex avant la location fourre-tout : location ~ portal_modifier { return 403; }
  2. Dans Apache avec mod_rewrite, ajoutez la règle avant la règle de proxy : RewriteEngine On RewriteRule portal_modifier - [F,L]
  3. Gardez la règle après la mise à jour. La pré-annonce la recommande, et elle ne coûte rien.

La règle du proxy est une mesure provisoire. Apache, nginx et Traefik décodent le pourcentage d'encodage avant la correspondance, donc /%70ortal_modifier est intercepté. Varnish ne décode pas le chemin, donc sa règle doit aussi couvrir les formes encodées. La règle ne corrige pas les évasions du bac à sable si un autre chemin atteint du code restreint.

Quelle est la cause racine ?

La chaîne commence parce que les modificateurs de versionnage de Plone ne déclarent jamais qui peut appeler leurs méthodes. Un modificateur est un SimpleItem qui enveloppe une opération de versionnage. Zope publie une méthode qui possède une docstring si aucune déclaration de sécurité ne la protège. ConditionalModifier.edit et ConditionalTalesModifier.edit n'avaient aucune déclaration, donc un visiteur anonyme pouvait les appeler.

La classe vulnérable dans Products/CMFEditions/Modifiers.py en 5.0.1 est :

class ConditionalModifier(SimpleItem):
    """This is a wrapper for a modifier."""

    modifierEditForm = PageTemplateFile(
        "www/modifierEditForm.pt", globals(), __name__="modifierEditForm"
    )

    def edit(self, enabled=None, title="", REQUEST=None):
        """See IConditionalModifier."""
        self.title = title

        if enabled is not None and (
            enabled == "True" or (isinstance(enabled, bool) and enabled)
        ):
            self._enabled = enabled
        else:
            self._enabled = False

ConditionalTalesModifier en hérite et ajoute la condition :

class ConditionalTalesModifier(ConditionalModifier):
    """This is a wrapper with a tales condition for a modifier."""

    def edit(self, enabled=None, condition=None, title="", REQUEST=None):
        """See IConditionalTalesModifier."""
        ConditionalModifier.edit(self, enabled, title)
        if condition is not None and condition != self.getTalesCondition():
            self._condition = Expression(condition)

Expression(condition) compile la chaîne de l'attaquant comme une expression TALES. L'expression est évaluée plus tard dans isApplicable, que la machinerie de versionnage appelle pour décider si un modificateur s'applique :

    def isApplicable(self, obj, portal=None):
        """See IConditionalTalesModifier."""
        if not self._enabled or not self.getTalesCondition() or self.isBroken():
            return False

        context = createExpressionContext(obj, portal)
        return self._condition(context)

À elle seule, l'absence de contrôle de permission permet à un visiteur anonyme de modifier une condition et de la déclencher. La seconde moitié de la chaîne est l'évasion du bac à sable. Le Python restreint est censé n'exposer que les noms qu'un paquet déclare publics. Quatre paquets ouvraient au contraire des modules entiers avec allow_module, ce qui publie chaque nom du module, y compris les modules et fonctions qu'il importe.

Products.CMFPlone en 6.2.2 ouvrait trois modules :

    allow_module("Products.CMFPlone.utils")
    # ...
    allow_module("Products.CMFPlone.browser.navtree")
    # ...
    allow_module("OFS.ObjectManager")

Products.MimetypesRegistry ouvrait son module common :

allow_module("Products.MimetypesRegistry.common")

plone.batching ouvrait son propre paquet :

allow_module("plone.batching")

plone.restapi ouvrait le module json de la bibliothèque standard :

allow_module("json")

Le module json est inoffensif pour la sérialisation, mais certaines de ses fonctions peuvent appeler des méthodes protégées sur des objets, ce qui élargissait aussi ce que le code restreint pouvait faire. Le graphe d'import est le point clé : un module ouvert de cette façon publie les assistants qu'il a importés, et ces assistants peuvent atteindre os. L'avis du fournisseur indique que les noms exposés permettaient au code restreint d'atteindre des modules comme os et de s'échapper du bac à sable.

Comment la faille est-elle exploitée ?

  1. L'attaquant envoie une requête à un objet portal_modifier et appelle sa méthode edit. La méthode est publiable car la classe n'a aucune déclaration de sécurité.
  2. La requête définit enabled et une valeur condition. ConditionalTalesModifier.edit stocke la valeur comme une Expression compilée.
  3. L'attaquant déclenche le modificateur, ou attend qu'un éditeur enregistre du contenu versionné pour que la machinerie appelle isApplicable.
  4. isApplicable évalue la condition stockée avec les privilèges du processus ou de l'éditeur qui a enregistré le contenu.
  5. Le Python restreint arrêterait normalement l'expression à la frontière du bac à sable. Les déclarations allow_module globales exposent les modules et imports qui mènent à os, donc l'expression s'échappe et exécute une commande.
  6. La commande s'exécute avec l'utilisateur du processus Zope. Sur un déploiement par défaut, cet utilisateur possède l'instance Plone, sa connexion à la base de données et ses fichiers téléversés.

Aucun code d'exploitation public n'est disponible. L'avis indique qu'une preuve de concept fonctionnelle existe et a été partagée avec le fournisseur, mais il ne publie pas la séquence de requêtes. Les étapes décrites dans cet avis suivent la description de l'avis et le chemin de code des paquets corrigés.

Comment se déroule l'attaque ?

L'attaque transforme une requête non authentifiée en expression TALES stockée, puis abuse des déclarations de modules trop larges pour évaluer cette expression hors du bac à sable restreint.

flowchart LR A["Visiteur anonyme"] --> B["Méthode edit de portal_modifier
aucune déclaration de permission"] B --> C["Condition TALES stockée
comme Expression"] C --> D["isApplicable évalue
la condition"] D --> E["allow_module expose
les assistants importés"] E --> F["Le Python restreint atteint os"] F --> G["Le code s'exécute avec l'utilisateur du processus Zope"]

Que peut faire un attaquant ?

Un attaquant qui atteint la chaîne peut exécuter du code avec les privilèges du processus Zope qui sert le site Plone. L'avis indique que le chemin anonyme peut exécuter du code sur le serveur, et la même condition peut s'exécuter avec les privilèges d'un éditeur lorsque celui-ci enregistre du contenu versionné.

L'impact comprend :

  • La lecture et la modification du contenu du site et des fichiers accessibles au processus Zope.
  • La lecture de la configuration et des secrets accessibles au processus, y compris les paramètres de connexion à la base de données.
  • L'exécution de commandes sur l'hôte avec le compte de service Plone.
  • L'utilisation du site comme point d'appui vers le réseau qui joint sa base de données et ses services internes.

Les conditions sont celles de « Suis-je concerné ? » : une version concernée, l'outil portal_modifier joignable en HTTP, et aucune mise à jour ni blocage au proxy. L'avis CMFEditions note la faille principale CVSS v3.1 9,8 Critique, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, et CWE-862. L'avis ne dit pas que la faille donne root sur l'hôte. Il dit que du code s'exécute sur le serveur, et les privilèges du processus limitent ce que ce code peut faire.

Pourquoi le correctif fonctionne-t-il ?

Le correctif ferme le point d'entrée anonyme et resserre le bac à sable dans la même version. Le premier changement ajoute une déclaration de sécurité aux classes de modificateurs dans Products.CMFEditions 5.0.2 :

 class ConditionalModifier(SimpleItem):
     """This is a wrapper for a modifier."""

+    security = ClassSecurityInfo()
+
     modifierEditForm = PageTemplateFile(
         "www/modifierEditForm.pt", globals(), __name__="modifierEditForm"
     )

+    @security.protected(ManagePortal)
     def edit(self, enabled=None, title="", REQUEST=None):
         """See IConditionalModifier."""

@security.protected(ManagePortal) signifie que Zope vérifie la permission Manage portal avant d'appeler edit. Un visiteur anonyme ne passe plus. Les accesseurs reçoivent ViewManagementScreens, et les hooks de garde de taille deviennent privés.

Le second changement remplace chaque allow_module global par une liste explicite. Dans plone.batching 3.0.3 :

-from AccessControl import allow_module
+from AccessControl import ModuleSecurityInfo
 from plone.batching.batch import Batch

-allow_module("plone.batching")
+# Only declare Batch public.  Do not use allow_module here: that would make
+# every name in this module available to restricted code, including
+# imported names like allow_class.
+ModuleSecurityInfo("plone.batching").declarePublic("Batch")
 allow_class(Batch)

Products.CMFPlone 6.2.3 remplace allow_module("Products.CMFPlone.utils") par un objet ModuleSecurityInfo et une liste de noms publics. Products.MimetypesRegistry 4.0.2 ne déclare public que MimeTypeException. plone.restapi 10.2.1 supprime entièrement allow_module("json"). Le bac à sable expose toujours les noms dont les templates et scripts ont besoin, mais pas les modules que ces noms importent.

Le correctif est complet pour les cinq paquets nommés par le fournisseur. Un add-on tiers qui appelle allow_module sur son propre module peut encore élargir le bac à sable ; l'avis ne couvre pas ces add-ons. Vérifiez vos add-ons pour le même schéma si vous exécutez du code non fiable.

Comment vérifier si j'ai été compromis ?

Commencez par le nouveau vérificateur de conditions. En tant que Manager, ouvrez https://your-site/@@check-modifier-conditions à la racine du site. La page liste chaque modificateur dont la condition diffère de la valeur par défaut, affiche la valeur actuelle et propose de la rétablir. Une différence est une piste, pas une preuve : les valeurs par défaut ont changé au fil du temps, et un administrateur ou un add-on peut avoir défini une condition volontairement.

Cherchez dans les journaux du serveur web les requêtes vers l'outil de modificateurs. Décodez le pourcentage d'encodage et ignorez la casse, car l'acquisition Zope rend l'objet accessible à n'importe quelle profondeur et un attaquant peut encoder le chemin :

grep -iE 'portal[_%]?modifier' /var/log/nginx/access.log
grep -iE 'portal(%5F|_)modifier' /var/log/apache2/access.log

Une requête qui contient le marqueur mérite d'être ouverte : elle montre l'adresse source, la méthode et le code de réponse. Un 200 sur une requête edit vers un objet modificateur est une piste plus forte qu'un 404 de scanner. L'avis ne publie aucun indicateur de compromission, et une tentative échouée peut ne laisser aucune trace au-delà de la ligne de requête.

Si le vérificateur montre une condition modifiée que vous n'avez pas définie, rétablissez-la, faites tourner les secrets que le processus Zope pouvait lire et examinez le site pour du contenu modifié. Si vous confirmez une exécution de code, traitez le serveur comme compromis et reconstruisez-le depuis une sauvegarde saine, car l'exécution de code peut laisser des portes dérobées hors des fichiers que le vérificateur inspecte.

Chronologie

  • 2026-11-02 : Plone publie une pré-annonce et le contournement par proxy pour portal_modifier.
  • 2026-11-10 13:26 UTC : les paquets corrigés et Products.PloneHotfix20261110 sont téléversés sur PyPI.
  • 2026-11-10 13:30 UTC : l'avis public décrit les cinq failles et les versions corrigées.

Chronologie du pré-annonce aux détails publics, huit jours plus tard
Figure 1 : Plone a annoncé le correctif huit jours avant de publier les détails techniques. Source : publications de la communauté Plone, vérifiées le 11 novembre 2026

FAQ

Y a-t-il un CVE ?

Aucun CVE n'est attribué au 11 novembre 2026. Les cinq avis sont publiés avec des identifiants GHSA, et l'annonce indique que des numéros CVE ont été demandés. Suivez les entrées GHSA plutôt que d'attendre un CVE avant de mettre à jour.

Cela concerne-t-il Plone 5 ou Plone 4 ?

Oui. L'avis indique que les versions antérieures sont concernées jusqu'à au moins Plone 4.3. Plone 5.2 et les versions antérieures ne sont plus maintenues en sécurité, donc la voie prise en charge pour ces versions est le paquet de correctifs, pas une mise à jour de paquets.

Faut-il les cinq mises à jour de paquets ?

Mettez à jour les quatre paquets critiques et plone.restapi s'il est installé. L'annonce indique que vous pouvez ignorer plone.restapi si vous ne l'utilisez pas ; c'est le compagnon de gravité moyenne qui élargit le contournement de permission, pas le point d'entrée. Les quatre autres sont nécessaires pour la chaîne décrite par l'avis.

Est-elle exploitée dans la nature ?

Aucune exploitation n'est connue au 11 novembre 2026. Le fournisseur a publié les détails techniques le jour de la version, et la pré-annonce avertissait que les outils d'IA actuels pourraient transformer la description en exploit fonctionnel rapidement. Traitez la fenêtre entre la pré-annonce et votre mise à jour comme une exposition.

Puis-je garder le blocage au proxy au lieu de mettre à jour ?

Le blocage au proxy arrête les requêtes dont le chemin contient portal_modifier, et le fournisseur recommande de le garder après la mise à jour. Il ne corrige pas les évasions du bac à sable si un autre chemin atteint du code restreint, et il ne couvre pas un chemin éditeur interne qui déclenche une condition stockée. Utilisez-le comme mesure provisoire, pas comme correctif.

Le correctif fonctionne-t-il avec Python 2.7 ?

Oui. Le paquet de correctifs prend en charge Python 2.7 et Python 3, et il fonctionne sur Plone 4.3, 5.0, 5.1, 5.2, 6.0, 6.1 et 6.2.

Trouver les sites Plone exposés avec Ostorlab

Le scan web agentique profond d'Ostorlab cartographie un site Plone exposé sur Internet, ses points d'entrée joignables et ses composants exposés, afin de voir quelles instances répondent encore avant de les corriger. Lancez-le sur les sites de votre inventaire et utilisez les résultats pour prioriser ceux qui sont joignables depuis Internet.

Références

  • Annonce du correctif de sécurité Plone 20261110 : https://community.plone.org/t/plone-security-hotfix-20261110/23313 (vérifié le 11 novembre 2026)
  • Pré-annonce et contournement par proxy : https://community.plone.org/t/pre-announcement-plone-security-fix-20261110/23303 (vérifié le 11 novembre 2026)
  • Avis Products.CMFEditions GHSA-67qm-q795-jh7h : https://github.com/plone/Products.CMFEditions/security/advisories/GHSA-67qm-q795-jh7h (vérifié le 11 novembre 2026)
  • Avis Products.CMFPlone GHSA-92qw-564p-pmg6 : https://github.com/plone/Products.CMFPlone/security/advisories/GHSA-92qw-564p-pmg6 (vérifié le 11 novembre 2026)
  • Avis Products.MimetypesRegistry GHSA-xcj2-cmp9-fj62 : https://github.com/plone/Products.MimetypesRegistry/security/advisories/GHSA-xcj2-cmp9-fj62 (vérifié le 11 novembre 2026)
  • Avis plone.batching GHSA-6hcx-c235-cxh3 : https://github.com/plone/plone.batching/security/advisories/GHSA-6hcx-c235-cxh3 (vérifié le 11 novembre 2026)
  • Avis plone.restapi GHSA-vgxm-pwch-89q6 : https://github.com/plone/plone.restapi/security/advisories/GHSA-vgxm-pwch-89q6 (vérifié le 11 novembre 2026)
  • Products.PloneHotfix20261110 sur PyPI : https://pypi.org/project/Products.PloneHotfix20261110/ (vérifié le 11 novembre 2026)
  • Source de Products.CMFEditions 5.0.2 : https://github.com/plone/Products.CMFEditions/blob/5.0.2/src/Products/CMFEditions/Modifiers.py (vérifié le 11 novembre 2026)
  • Source de plone.batching 3.0.3 : https://github.com/plone/plone.batching/blob/3.0.3/src/plone/batching/init.py (vérifié le 11 novembre 2026)
  • Source de Products.CMFPlone 6.2.3 : https://github.com/plone/Products.CMFPlone/blob/6.2.3/src/Products/CMFPlone/init.py (vérifié le 11 novembre 2026)
  • Source de plone.restapi 10.2.1 : https://github.com/plone/plone.restapi/blob/10.2.1/src/plone/restapi/init.py (vérifié le 11 novembre 2026)
  • Nombre de téléchargements du conteneur Plone backend : https://hub.docker.com/r/plone/plone-backend (vérifié le 11 novembre 2026)