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

Élévation de privilèges dans le planificateur Cronicle (CVE-2026-85076)

Un non-administrateur Cronicle peut injecter des variables d'environnement via un événement et exécuter du code dans une autre tâche. Corrigé en 0.9.124.

En bref

  • Un utilisateur Cronicle non administrateur disposant de privilèges sur les événements peut ajouter des clés non déclarées à params. Le lanceur les transforme en variables d'environnement, ce qui permet de remplacer LD_PRELOAD, PATH et NODE_OPTIONS.
  • Cronicle avant 0.9.124 est concerné. La version 0.9.124 valide les clés de paramètres par rapport au schéma du plugin. Les versions suivantes, jusqu'à 0.9.144, ajoutent d'autres durcissements des remplacements et de l'API des tâches.
  • Le dossier CVE de GitHub la note CVSS v4.0 9,0 Critique, l'avis du dépôt High. Aucune exploitation dans la nature n'est signalée, et la CVE n'est pas dans le catalogue KEV de la CISA au 7 novembre 2026.
  • Mettez à jour vers 0.9.144. Si vous ne pouvez pas, réservez la création et l'exécution d'événements aux administrateurs de confiance et auditez les événements stockés ; voir « Comment corriger ? ».

Suis-je concerné ?

Vous êtes exposé uniquement lorsque toutes ces conditions sont réunies :

  • Le serveur exécute Cronicle avant 0.9.124.
  • L'attaquant dispose d'un compte Cronicle qui n'est pas administrateur.
  • Ce compte peut créer, modifier ou exécuter des événements. L'avis nomme les privilèges run_events, create_events et edit_events.
  • L'attaquant peut atteindre l'API ou l'interface web de Cronicle.

Cronicle est un planificateur de tâches multi-serveur avec une interface web. Il exécute les tâches en tant qu'utilisateur configuré pour le plugin ou l'événement. Un non-administrateur qui peut créer des événements peut déjà exécuter des commandes en tant que cet utilisateur.

Cette faille ajoute la possibilité de modifier l'environnement d'une tâche appartenant à un autre utilisateur ou plugin. Cela inclut les variables qui chargent du code avant le démarrage de la tâche.

Branche Versions concernées Version corrigée
Cronicle toutes les versions avant 0.9.124 0.9.124 (17 juillet 2026) ; 0.9.144 (7 novembre 2026) ajoute d'autres durcissements

Le dossier CVE liste toutes les versions avant 0.9.124 comme concernées. La version 0.9.124 est la correction de cette CVE. Les versions 0.9.141 à 0.9.144 durcissent des chemins de remplacement d'événements et de tâches liés, donc exécutez 0.9.144.

Vérifiez la version en cours avec le script de contrôle :

/opt/cronicle/bin/control.sh version

Dans un conteneur, exécutez la même commande à l'intérieur :

docker exec <container> /opt/cronicle/bin/control.sh version

Toute version avant 0.9.124 est concernée.

Comment corriger ?

  1. Sauvegardez le répertoire de données et la configuration de Cronicle.
  2. Exécutez /opt/cronicle/bin/control.sh upgrade 0.9.144 sur chaque serveur. Sans argument de version, la commande met à jour vers la dernière version stable. La mise à jour conserve les données, les utilisateurs et la configuration.
  3. Pour un conteneur, récupérez la nouvelle image et recréez le conteneur. Le simple téléchargement de l'image ne remplace pas un conteneur en cours d'exécution.
  4. Confirmez la nouvelle version avec /opt/cronicle/bin/control.sh version.
  5. Dans un cluster, répétez la mise à jour sur chaque serveur. Cronicle exige la même configuration sur tous les serveurs.

Si vous ne pouvez pas mettre à jour, réservez la création et l'exécution d'événements aux administrateurs de confiance. L'avis nomme run_events, create_events et edit_events comme les privilèges qui atteignent la faille. Retirer ces privilèges aux rôles non administrateurs ferme le chemin documenté, mais supprime aussi une fonctionnalité dont ces utilisateurs dépendent.

Auditez les événements stockés pour les clés de paramètres que le plugin sélectionné ne définit pas. Une clé comme ld_preload, node_options ou bash_env ne fait pas partie d'un schéma de plugin normal. L'avis ne publie aucun contournement du fournisseur au-delà de la mise à jour.

Quelle est la cause racine ?

Le lanceur de tâches copie chaque clé de l'objet params d'un événement dans l'environnement du processus enfant, et l'API des événements vérifie seulement que params est un objet. Rien ne compare les clés aux paramètres déclarés par le plugin.

Dans lib/job.js en 0.9.123, la tâche copie les paramètres de l'événement :

job.params = event.params || {};

Plus tard, au démarrage du processus enfant, le lanceur met chaque clé en majuscules et l'écrit dans l'environnement :

// add plugin params as env vars, expand $INLINE vars (unless escaped)
if (job.params) {
    for (var key in job.params) {
        child_opts.env[key.toUpperCase()] =
            (''+job.params[key]).replace(/(\\?)\$(\w+)/g, function(m_all, m_g1, m_g2) {
            return m_g1 ? ('$' + m_g2) : ((m_g2 in child_opts.env) ? child_opts.env[m_g2] : '');
        });
    }
}

L'API accepte l'objet événement après une simple vérification de type :

if (("params" in event) && (typeof(event.params) != 'object')) {
    this.doError('api', "Malformed event parameter: params (must be object)", callback);
    return false;
}

Les paramètres du plugin contrôlés par l'administrateur sont appliqués en premier. Les paramètres de l'événement sont appliqués après, donc une clé qui entre en collision avec une variable définie par l'administrateur l'emporte. Le lanceur ne vérifie pas que la clé appartient au plugin.

L'objet params est aussi stocké sur l'événement lorsqu'un attaquant le crée ou le modifie. L'injection devient donc persistante pour les événements que l'attaquant peut modifier. Pour run_event, les paramètres ne s'appliquent qu'à cette exécution.

Comment la faille est-elle exploitée ?

  1. L'attaquant se connecte avec un compte non administrateur qui peut créer, modifier ou exécuter des événements.
  2. L'attaquant crée un événement, ou modifie un événement existant, et ajoute une clé non déclarée à params. L'exemple de l'avis utilise ld_preload avec un chemin vers une bibliothèque partagée.
  3. L'attaquant exécute l'événement, ou attend sa planification. Le lanceur met la clé en majuscules et l'écrit dans l'environnement enfant. ld_preload devient LD_PRELOAD, et le chargeur dynamique charge la bibliothèque de l'attaquant avant le code de la tâche.
  4. L'attaquant peut aussi utiliser node_options, bash_env ou pythonpath. L'exemple en deux étapes de l'avis écrit une charge utile Node.js avec un événement, puis exécute un second événement en tant qu'utilisateur plus privilégié avec node_options défini à --require=/tmp/evil.js.
  5. Le code injecté s'exécute avec l'utilisateur et les accès de la tâche cible. L'avis décrit l'impact comme une élévation de privilèges, une exécution de code arbitraire, un accès aux données ou une perturbation du service, selon le plugin et l'environnement d'exécution.

L'avis publie une requête qui crée un événement avec un paramètre supplémentaire. Adapté de l'avis, le corps de la requête a cette forme :

POST /api/app/create_event/v1
Content-Type: application/json

{
  "session_id": "USER_SESSION_ID",
  "title": "Legitimate Event",
  "enabled": 1,
  "category": "general",
  "target": "allgrp",
  "plugin": "shellplug",
  "params": {
    "script": "#!/bin/sh\necho hello",
    "ld_preload": "/tmp/evil.so"
  }
}

La clé ld_preload n'est pas définie par le plugin. Le lanceur la transforme quand même en LD_PRELOAD pour la tâche.

Comment se déroule l'attaque ?

Un non-administrateur ajoute une clé que le plugin n'a jamais définie. Le lanceur transforme cette clé en variable d'environnement, et la tâche cible charge le code de l'attaquant avant de démarrer.

flowchart LR A["Un non-administrateur crée ou exécute un événement"] --> B["params contient ld_preload ou node_options"] B --> C["L'API vérifie seulement que params est un objet"] C --> D["job.params copie chaque clé"] D --> E["job.js met chaque clé en majuscules dans child_opts.env"] E --> F["La tâche cible démarre avec LD_PRELOAD ou NODE_OPTIONS"] F --> G["Le code de l'attaquant s'exécute en tant qu'utilisateur de la tâche"]

Que peut faire un attaquant ?

Un non-administrateur disposant de privilèges sur les événements peut exécuter du code dans le contexte de la tâche d'un autre événement. L'impact dépend de l'utilisateur du plugin cible et de ce que la tâche peut atteindre.

Les conditions sont :

  • L'attaquant dispose d'un compte Cronicle non administrateur.
  • Le compte peut créer, modifier ou exécuter des événements.
  • L'événement cible utilise un plugin qui s'exécute avec plus d'accès que l'attaquant, ou l'attaquant doit modifier l'environnement de cette tâche.
  • L'attaquant peut atteindre l'API ou l'interface web.
  • Pour une injection persistante, l'attaquant peut modifier l'événement cible. Pour une injection ponctuelle, l'attaquant peut l'exécuter avec des paramètres modifiés.

L'avis note la faille High et décrit l'impact comme une élévation de privilèges, une exécution de code arbitraire, un accès aux données ou une perturbation du service. Le dossier CVE de GitHub la note CVSS v4.0 9,0 Critique. La différence vient du modèle de notation et des prérequis supposés ; les deux sont indiqués ici.

La faille ne donne pas à elle seule le compte administrateur de Cronicle ni l'utilisateur root. Le code injecté s'exécute en tant qu'utilisateur configuré pour la tâche. Une tâche qui s'exécute en tant que root, ou un plugin qui démarre un processus root, donne cet accès à l'attaquant. L'avis ne démontre pas de chemin root spécifique.

Pourquoi le correctif fonctionne-t-il ?

Le correctif valide chaque clé de paramètre d'événement par rapport aux paramètres déclarés par le plugin et ne copie dans la tâche que les paramètres déclarés. Le correctif de Cronicle ajoute un assistant requireValidEventParams() et modifie le lanceur de tâches pour utiliser le schéma du plugin.

Le nouvel assistant dans lib/api.js :

requireValidEventParams: function(params, plugin, callback) {
    // Only allow Event params that are defined by the admin-controlled Plugin.
    // This prevents arbitrary param keys from becoming child environment variables.
    var allowed_params = Object.create(null);
    if (plugin.params) plugin.params.forEach( function(param) {
        allowed_params[ param.id ] = true;
    } );

    for (var key in params) {
        if (!Object.prototype.hasOwnProperty.call(params, key)) continue;
        if (!allowed_params[key]) {
            this.doError('event', "Unknown Plugin parameter: " + key, callback);
            return false;
        }
    }

    return true;
},

Le lanceur de tâches construit désormais job.params à partir du schéma du plugin :

-               job.params = event.params || {};
                job.now = event.now || Tools.timeNow(true);
                job.event_title = event.title;
                job.plugin_title = plugin.title;
...
-               // plugin params may have changed outside of event, 
-               // so recopy missing / hidden ones
+               // Only allow params defined by the admin-controlled Plugin schema,
+               // so arbitrary Event / API keys cannot become child environment variables.
+               var supplied_params = Tools.isaHash(event.params) ? event.params : {};
+               job.params = {};
+               
+               // Plugin params may have changed outside of the Event, so recopy
+               // missing values, and always use the trusted value for hidden params.
                if (plugin.params) plugin.params.forEach( function(param) {
-                   if (!(param.id in job.params) || (param.type == 'hidden')) {
+                   if (
+                       (param.type != 'hidden') &&
+                       Object.prototype.hasOwnProperty.call(supplied_params, param.id)
+                   ) {
+                       job.params[ param.id ] = supplied_params[ param.id ];
+                   }
+                   else {
                        job.params[ param.id ] = param.value;
                    }
                } );

Après le correctif, une clé non déclarée est rejetée par l'API et n'est pas copiée dans l'environnement. Un paramètre de plugin masqué prend toujours la valeur du plugin, donc un événement ne peut pas le remplacer.

Les versions 0.9.141 à 0.9.144 ajoutent des corrections liées. La note de version 0.9.144 indique « Prevent script parameter overrides in run_event ». La version 0.9.143 limite les API de mise à jour des tâches à une liste stricte de propriétés. Ce sont des défenses en profondeur pour la même frontière de confiance.

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

Commencez par la version en cours. Un serveur avant 0.9.124 est exposé.

/opt/cronicle/bin/control.sh version

Examinez la planification stockée pour les événements dont les paramètres contiennent des clés que le plugin ne définit pas. Le stockage conserve la planification dans le répertoire de données de Cronicle. Une petite vérification Node.js peut lister les clés :

node -e 'const fs=require("fs"),p="/opt/cronicle/data/global/schedule";for(const f of fs.readdirSync(p)){const j=JSON.parse(fs.readFileSync(p+"/"+f));for(const e of j.items||[]){const k=Object.keys(e.params||{});if(k.some(x=>/^(ld_preload|node_options|bash_env|pythonpath|path)$/i.test(x)))console.log(e.id,e.title,k.join(","));}}'

Une correspondance est une piste, pas une preuve. Un plugin peut légitimement définir un paramètre portant l'un de ces noms. Comparez les clés avec le schéma du plugin.

Examinez les journaux des tâches et le journal de Cronicle pour les environnements enfants ou les erreurs qui nomment une bibliothèque ou un require Node.js inattendu. Le lanceur journalise les options de démarrage enfant au niveau de débogage 9. Si la journalisation de débogage est activée, recherchez LD_PRELOAD, NODE_OPTIONS ou le chemin d'un fichier que vous ne reconnaissez pas.

grep -Ei 'LD_PRELOAD|NODE_OPTIONS|BASH_ENV|PYTHONPATH' /opt/cronicle/logs/*.log 2>/dev/null

Le fournisseur ne publie aucun indicateur de compromission. Une tentative échouée peut ne laisser aucune trace, et une tentative réussie peut ne laisser que la sortie de la tâche ou un événement modifié.

Chronologie

  • 3 avril 2026 : l'avis antérieur GHSA-5j3v-cq96-xw6v pour l'injection via la sortie de tâche update_event est publié et corrigé en 0.9.111.
  • 17 juillet 2026 : le commit da0228fd et la version 0.9.124 valident les paramètres d'événement par rapport au schéma du plugin.
  • 2 septembre 2026 : la CVE-2026-85076 est réservée.
  • 4 septembre 2026 : l'avis GHSA-9w42-v8cj-68gq est publié.
  • 5 novembre 2026 : le dossier CVE est publié à 20:28 UTC.
  • 7 novembre 2026 : les versions 0.9.143 et 0.9.144 ajoutent d'autres durcissements des remplacements et de l'API des tâches.

Le correctif est sorti sept semaines avant l'avis et quatre mois avant le dossier CVE.
Figure 1 : De la correction 0.9.124 à l'avis, au dossier CVE et aux versions de durcissement de novembre. Source : versions de Cronicle, avis et dossier CVE, vérifiés le 7 novembre 2026

FAQ

Qui peut exploiter cette faille ?

Un utilisateur Cronicle qui n'est pas administrateur mais peut créer, modifier ou exécuter des événements. L'avis nomme les privilèges run_events, create_events et edit_events. Un visiteur anonyme ne peut pas l'exploiter.

Donne-t-elle un accès root ?

Pas à elle seule. L'environnement injecté s'applique au processus enfant de la tâche, qui s'exécute en tant qu'utilisateur configuré pour le plugin ou l'événement. Une tâche configurée pour s'exécuter en tant que root donne root à l'attaquant. Une tâche qui s'exécute en tant qu'utilisateur non privilégié donne l'accès de cet utilisateur.

La version 0.9.124 suffit-elle ?

Elle corrige la CVE-2026-85076. Les versions 0.9.141 à 0.9.144 ajoutent d'autres durcissements des mêmes chemins de remplacement d'événements et de tâches, dont « Prevent script parameter overrides in run_event » en 0.9.144. Exécutez 0.9.144.

La faille a-t-elle été exploitée dans la nature ?

Aucune exploitation dans la nature n'est signalée au 7 novembre 2026, et la CVE n'est pas dans le catalogue KEV de la CISA. L'avis publie des exemples de requêtes, donc la technique est publique.

Que faire si je ne peux pas mettre à jour tout de suite ?

Retirez les privilèges run_events, create_events et edit_events des rôles non administrateurs. Auditez les événements stockés pour les clés de paramètres que le plugin ne définit pas. La restriction supprime une fonctionnalité pour ces utilisateurs, donc planifiez le changement avec eux.

Comment savoir quel plugin un événement utilise ?

L'enregistrement de l'événement a un champ plugin qui nomme l'identifiant du plugin. La définition du plugin liste ses paramètres. Comparez les clés de paramètres de l'événement avec cette liste. La commande de vérification de « Comment vérifier si j'ai été compromis ? » affiche l'identifiant, le titre et les clés de l'événement.

Références

  • Avis GitHub GHSA-9w42-v8cj-68gq, publié le 4 septembre 2026, avec le code vulnérable, les étapes de reproduction et le correctif : https://github.com/jhuckaby/Cronicle/security/advisories/GHSA-9w42-v8cj-68gq (vérifié le 7 novembre 2026)
  • Dossier CVE-2026-85076, publié le 5 novembre 2026, CVSS v4.0 9.0 et CWE-863/CWE-74 : https://www.cve.org/CVERecord?id=CVE-2026-85076 (vérifié le 7 novembre 2026)
  • Commit Cronicle da0228fd, « Harden event parameter validation and plugin handling », 17 juillet 2026 : https://github.com/jhuckaby/Cronicle/commit/da0228fdea9bc57c09876c00ea0feda70a0ef110 (vérifié le 7 novembre 2026)
  • Version Cronicle 0.9.124, 17 juillet 2026 : https://github.com/jhuckaby/Cronicle/releases/tag/v0.9.124 (vérifié le 7 novembre 2026)
  • Version Cronicle 0.9.144, 7 novembre 2026, avec la note « Prevent script parameter overrides in run_event » : https://github.com/jhuckaby/Cronicle/releases/tag/v0.9.144 (vérifié le 7 novembre 2026)
  • Version Cronicle 0.9.143, 7 novembre 2026, avec la liste blanche de l'API de mise à jour des tâches : https://github.com/jhuckaby/Cronicle/releases/tag/v0.9.143 (vérifié le 7 novembre 2026)
  • Cronicle lib/job.js en v0.9.123, la copie des paramètres vers l'environnement : https://github.com/jhuckaby/Cronicle/blob/v0.9.123/lib/job.js#L537 (vérifié le 7 novembre 2026)
  • Cronicle lib/api.js en v0.9.123, la vérification de type des paramètres : https://github.com/jhuckaby/Cronicle/blob/v0.9.123/lib/api.js#L252 (vérifié le 7 novembre 2026)
  • Cronicle lib/job.js en v0.9.124, la copie des paramètres selon le schéma : https://github.com/jhuckaby/Cronicle/blob/v0.9.124/lib/job.js#L227 (vérifié le 7 novembre 2026)
  • Documentation de ligne de commande de Cronicle, /opt/cronicle/bin/control.sh version et upgrade : https://github.com/jhuckaby/Cronicle/blob/v0.9.123/docs/CommandLine.md (vérifié le 7 novembre 2026)
  • GitHub, jhuckaby/Cronicle, 5 875 étoiles et 509 forks : https://github.com/jhuckaby/Cronicle (vérifié le 7 novembre 2026)
  • Docker Hub, cronicle/cronicle, 132 076 téléchargements : https://hub.docker.com/r/cronicle/cronicle (vérifié le 7 novembre 2026)
  • Flux JSON du catalogue des vulnérabilités exploitées de la CISA, vérifié pour CVE-2026-85076 le 7 novembre 2026 (non listée) : https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (vérifié le 7 novembre 2026)