TL;DR
We discovered a serious remote code execution (RCE) vulnerability in Ghost CMS, tracked as GHSA-cgc2-rcrh-qr5x. Ghost CMS is one of the most popular Node.js content management systems, powering more than 100,000 active websites and boasting over 100 million installations worldwide.
The RCE allows attackers to execute arbitrary JavaScript code on Ghost instances by crafting malicious themes and convincing administrators to use them. We have not observed an active attack using this RCE yet. The vulnerability has been patched in Ghost version 6.19.1, and we strongly recommend immediate upgrades for all Ghost instances.
The vulnerability stems from Ghost's use of the jsonpath package, which relies on static-eval to interpret JSONPath expressions in Handlebars templates. Despite static-eval being marked by its authors as "NOT suitable for handling arbitrary untrusted user input," Ghost uses it to process attacker-controlled templates in themes. Attackers can craft malicious JSONPath expressions that bypass all the security controls deployed in static-eval, jsonpath, and Ghost to achieve arbitrary code execution.
After reporting the vulnerability, we assisted the maintainers with considering alternatives and prevented insufficient fixes like upgrading the problematic dependency or relying on another insecure package with a similar functionality.
Affected Versions
Introduction
Ghost CMS is one of the most popular Node.js content management systems, with over 50,000 GitHub stars. It powers more than 100,000 active websites, and boasts over 100 million installations worldwide.
Founded in 2013 during the early days of Node.js, Ghost has become a trusted platform for independent journalists, bloggers, and content creators. The Ghost Foundation, which backs the project, generates over $10 million in annual recurring revenue, demonstrating the platform's commercial success and widespread adoption.
Over its twelve-year history, Ghost CMS has been heavily scrutinized by the security community, with numerous vulnerabilities discovered and disclosed. However, while the Ghost codebase itself has received significant security attention, its transitive dependencies have received less scrutiny. This vulnerability demonstrates how a seemingly-innocuous, transitive dependency can introduce critical security flaws into even the most well-audited applications.
The vulnerability
Ghost CMS uses the jsonpath package to interpret JSONPath expressions within Handlebars templates. The jsonpath package, in turn, relies on static-eval to evaluate expressions. Even though static-eval parses expressions into an Abstract Syntax Tree (AST) and selectively interprets them instead of using a code execution primitive like eval(), the package is explicitly marked by its authors as "NOT suitable for handling arbitrary untrusted user input". Moreover, in older versions of static-eval, the dangerous Function() constructor is invoked when processing user-defined functions.
Until version 6.19.1, Ghost CMS locked its dependency to an older version of jsonpath (1.1.1) and static-eval (2.0.2), which directly uses Function() for interpreting user-defined functions. However, jsonpath attempts to prevent function execution via static-eval by passing a scope argument with the name "@", which prevents the compilation of any user-defined functions. This mitigation, while well-intentioned, is insufficient to prevent all forms of code execution.
The vulnerability exists in Ghost's get helper, located at ghost/core/core/frontend/helpers/get.js. When processing Handlebars templates, the helper uses jsonpath.query() to evaluate JSONPath expressions:
function resolvePaths(globals, data, value) {
const regex = /\{\{(.*?)\}\}/g;
value = value.replace(regex, function (match, path) {
let result;
// ... path processing ...
if (path.charAt(0) === '@') {
result = jsonpath.query(globals, path.slice(1));
} else {
result = jsonpath.query(data, path); // Vulnerable call
}
return result.join(',');
});
return value;
}The jsonpath.query() function processes attacker-controlled JSONPath expressions present in third-party templates through static-eval, creating an opportunity for code injection despite the package's warnings against handling untrusted input.
The fundamental issue is that static-eval has a well-documented history of security vulnerabilities, including CVE-2021-23334 and other sandbox escape issues. While the package implements some security controls, the maintainer has explicitly stated that they do not want to offer any security guarantees and have committed to only fixing sandbox escapes if "the fix is simple". This position reflects the inherent difficulty of securely evaluating untrusted code, even with AST-based approaches. Attackers can exploit `static-eval` through careful manipulation of object property access, as discussed below.
The exploit
After identifying the transitive dependency on static-eval, we had to next judge whether this dependency is exploitable and how we can perform UI-level actions that trigger the payload. To identify UI attack vectors, we used Cursor CLI to analyze UI- and API-level actions that could trigger JSONPath interpretation. The jsonpath.query() function is invoked as part of the get helper during Handlebars template interpretation, which occurs whenever a page using the theme is rendered.
The first challenge was bypassing Ghost's theme validation system (gscan), which validates Handlebars templates before they can be activated. gscan uses Handlebars' own parser (Handlebars.parseWithoutProcessing()) to validate template syntax, which could potentially block malicious JSONPath expressions.
We discovered that gscan treats single-level bracket notation like {{@site[...]}} as valid property access and does not deeply validate the JavaScript code inside the brackets. This allows JSONPath filter expressions (which use the [?(...)] syntax) to pass validation when embedded in this format.
The key bypass was using {{@site[?(...)]}} instead of nested property access like {{@site.site[?(...)]}}. When gscan encounters {{@site[?(...)]}}, it:
- Recognizes @site as a valid Handlebars path
- Treats [...] as property/array access notation
- Does not deeply parse or validate the JavaScript expression inside [?(...)]
This validation gap allows malicious JSONPath filter expressions to reach the vulnerable jsonpath.query() code path, where they are evaluated by static-eval.
As mentioned earlier, the locked-version of static-eval uses the Function() constructor, but jsonpath prevents its execution by inserting an invalid parameter name in scope. Thus, we identified an attack vector against static-eval that does not rely on user-defined functions. The exploit leverages JavaScript's prototype chain and function property access to construct a code execution chain. The key insight is that `static-eval` allows property access on objects and functions, enabling attackers to traverse the prototype chain to reach the Function constructor.
The exploit uses the following pattern within a JSONPath filter expression:
({__proto__:"".toString})["constructor"]("return process.mainModule.require('fs').writeFileSync('/tmp/jsonpath-rce-success.txt','RCE_SUCCESS')")()Breaking down this payload:
1. Function Reference via Prototype: ({__proto__:"".toString}) creates an object literal with the __proto__ property set to "".toString, which is a reference to the String.prototype.toString function. This allows us to obtain a function object without directly referencing functions that are blocked via a typeof check in static-eval.
2. Accessing the Function Constructor: ["constructor"] uses bracket notation to access the constructor property of the function object. Since toString is a function, its constructor property is the Function constructor itself. This bypasses static-eval's restrictions because property access on objects is allowed, and static-eval doesn't prevent accessing the constructor property of functions.
3. Code Execution: The Function() constructor is invoked with an attacker-controlled string argument ("return …"), which compiles and returns a new function that writes a file to the disk. The trailing () immediately invokes this function, executing the arbitrary code.
This attack works because static-eval allows: 1) Object literal creation with arbitrary properties, 2) Property access via bracket notation, 3) Accessing the `constructor` property of functions, 4) Invoking functions (including constructors) with string arguments.
While jsonpath attempts to prevent user-defined function expressions in static-eval, it doesn't prevent accessing built-in function constructors through property access patterns, creating a critical security gap.
Putting it all together, a malicious theme can contain Handlebars templates with JSONPath expressions that execute arbitrary JavaScript code on the Ghost CMS backend. The complete exploit payload embedded in a theme template looks like this:
{{!< default}}
<h1>RCE Test</h1>
<p>Check /tmp/jsonpath-rce-success.txt for success</p>
{{#get "posts" filter="tags:{{@site[?( ({__proto__:\"\".toString})[\"constructor\"](\"return process.mainModule.require('fs').writeFileSync('/tmp/jsonpath-rce-success.txt','RCE_SUCCESS')\")() )]}}" limit="1"}}
{{/get}}The attack requires an administrator to upload and activate a malicious theme, but once activated, the vulnerability triggers automatically on every page view, making it highly effective for persistent server compromise.
The fix
Initially, the Ghost team proposed upgrading to the latest version of jsonpath (1.2.1), believing it would address the vulnerability. However, we advised against this approach, as jsonpath and static-eval remain fundamentally flawed building blocks for processing attacker-controlled input. We demonstrated that even the latest jsonpath version remained vulnerable, sharing a bypass payload that achieved code execution:
REDACTEDThis payload exploits prototype pollution and function constructor access to execute arbitrary code, demonstrating that upgrading jsonpath alone would not resolve the underlying security issue.
The Ghost team then proposed migrating to an alternative JSONPath implementation that builds a proper AST instead of using static-eval. However, we identified several denial-of-service (DoS) attack vectors against this alternative package that would allow attackers to hang Ghost CMS instances for extended periods with a single request. While these issues were less severe than the RCE vulnerability in jsonpath, they demonstrated that AST-based code/expression evaluation approaches still require careful security consideration.
After evaluating these options, the Ghost team made the correct decision: removing the dependency on jsonpath or any equivalent package entirely. Instead, they implemented a simpler, custom solution for retrieving data from JSON structures using basic JSONPath-like expressions. This custom implementation:
1. Eliminates code execution risk: By avoiding static-eval or similar code execution mechanisms, the custom solution removes the possibility of arbitrary code execution
2. Reduces attack surface: A purpose-built solution tailored to Ghost's specific needs has a smaller attack surface than a general-purpose library
3. Maintains functionality: The custom implementation provides the necessary JSONPath functionality while avoiding the security pitfalls of third-party packages
This approach demonstrates an important security principle: when a dependency introduces unacceptable security risks, sometimes the best solution is to remove it entirely and implement a minimal, purpose-built alternative.
Mitigation
We recommend taking the following actions:
1. Immediate patching: Upgrade to the latest version of Ghost CMS immediately. The fix removes the vulnerable jsonpath dependency and replaces it with a secure custom implementation.
2. Theme security: Review all installed themes for suspicious code. Be cautious when installing themes from untrusted sources, and prefer themes from Ghost's official marketplace or verified developers.
3. Access control: Restrict theme installation and modification to trusted administrators only. Implement strong authentication (MFA) for administrator accounts.
4. Network segmentation: Isolate Ghost CMS instances from critical systems and databases. Use network segmentation and firewall rules to limit outbound connections.
5. Monitoring: Implement monitoring for unusual server behavior, unexpected file modifications, or suspicious network connections that might indicate compromise.
Takeaways for developers
1. Transitive dependencies are security risks: This vulnerability demonstrates that even well-audited applications can be compromised through transitive dependencies. Software composition analysis (SCA) is not a luxury but a necessity. Organizations must regularly audit their dependency trees and understand the security implications of each package.
2. Heed package warnings: When a package explicitly states it's "NOT suitable for handling arbitrary untrusted user input", believe it. Using such packages for processing untrusted input creates unacceptable security risks, regardless of any mitigations or workarounds.
3. Sometimes removal is the best fix: When a dependency introduces fundamental security flaws, upgrading may not be sufficient. Removing the dependency and implementing a minimal, purpose-built alternative can be the most secure approach, even if it requires more development effort.
Conclusion
This vulnerability in Ghost CMS serves as a critical reminder that software composition analysis is not a luxury but a necessity. A problematic transitive dependency led to remote code execution in one of the most popular and widely-deployed server-side JavaScript applications. Despite Ghost's long history, active security community, and commercial backing, a single vulnerable dependency created a severe security flaw.
We want to thank the Ghost CMS team for being so positive during the entire vulnerability disclosure process and for closely involving us in the security analysis of the proposed fixes.
Detect and block malware



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:









