Grav CMS before 2.0.16 contains an origin validation bypass in the Uri::referrer() and Pages::referrerRoute() methods, which validate the Referer header using an unanchored string prefix match (strstartswith($referrer, $base)) with no trailing delimiter. An attacker who controls a domain that begins with the victim site's origin (e.g. https://example.com.attacker.tld) can send a request with such a Referer to be treated as same-origin, bypassing the Referer-based origin check.
Grav CMS before 2.0.16 contains a timing vulnerability in Utils::verifyNonce() that uses non-constant-time string comparison with the === operator instead of hashequals() for CSRF nonce validation. Attackers can measure response timing differences to recover valid nonce values byte-by-byte through multiple requests, weakening CSRF protection below its intended security margin.
Grav CMS before 2.0.16 fails to filter system, site, and theme configuration arrays in sandboxed Twig renders, allowing content editors to read sensitive configuration values. Attackers with page-content edit access can access raw configuration arrays including secrets like cache credentials by using dot notation in Twig templates, bypassing the configdeniedpaths restrictions.
Grav CMS before 2.0.16 contains a path traversal vulnerability in the mediadirectory() Twig function that fails to validate filesystem paths, allowing authenticated users to enumerate and access files outside intended scope. Attackers with page authoring privileges can supply arbitrary filesystem paths to mediadirectory() and use the allow-listed filepath accessor on Medium objects to read file contents of any file matching configured media extensions that the web server process can access.
Grav CMS before 2.0.16 contains a symlink following vulnerability in Scheduler Job::createLockFile() that allows local attackers to overwrite arbitrary files by pre-creating symlinks at predictable lock file paths in the world-writable temp directory. Attackers can place a symlink at the predictable lock path pointing to any file the web server process can write to, and the next scheduled job run will follow the symlink and overwrite the target file's content with the job ID string.
Grav CMS before 2.0.13 contains a server-side template injection vulnerability in email-action parameters that allows low-privileged page editors to execute arbitrary operating-system commands. Attackers can inject Twig payloads using the unsandboxed find filter in email subject, body, to, or from fields to achieve remote code execution when forms are submitted.
Grav CMS before 2.0.13 contains a remote code execution vulnerability in the Flex Objects plugin settings validation that allows authenticated users to execute arbitrary code by uploading a ZIP file containing PHP code. Attackers can bypass routine name validation by using array notation instead of string notation, call the unZip routine with a malicious archive, and write PHP files to the web root for execution.
Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.
Grav CMS versions 2.0.7 through 2.0.10 fail to validate fully-qualified static method calls (Class::method) in blueprint dynamic-field directives because Blueprint::isSafeDynamicCall() only applies its dangerous-callable denylist to strings that do not contain '::'. An account with only page-editing rights (admin.pages, not super-admin or admin.pagestwig) can plant a directive in a page's form-field frontmatter that invokes an arbitrary public static PHP method with attacker-controlled arguments. Using built-in gadget methods this allows reading of any server-readable file (disclosed to anonymous visitors of the crafted page) and arbitrary creation/copying of files and directories under the web-server account. Fixed in 2.0.11.
Grav is a file-based Web platform. Prior to 2.0.0, an authenticated admin.super user can crash Grav or fill the disk by uploading a specially crafted ZIP archive through the Direct Install tool because Installer::unZip calls ZipArchive::extractTo without limits on uncompressed size, entry count, or directory depth. This issue is fixed in version 2.0.0.
Grav CMS before 2.0.0-beta.2 contains multiple code-execution vulnerabilities. Three unsafe unserialize() calls - in Scheduler\JobQueue, Framework\Cache\Adapter\FileCache, and Session - deserialize untrusted data without restricting allowed classes, enabling PHP object injection and, via a gadget chain, arbitrary code execution where an attacker controls the serialized input. Additionally, InstallCommand's git clone operation passes the branch, url, and path parameters into a shell command without escaping, allowing OS command injection via plugin/theme installation (which requires admin access). A Twig security blocklist bypass (server-side template injection) is also present. The issues are fixed in 2.0.0-beta.2.
A vulnerability was found in Grav CMS up to 1.7.49.5/2.0.0-beta.1. Affected by this vulnerability is the function FileCache::doGet of the file system/src/Grav/Framework/Cache/Adapter/FileCache.php of the component Cache Value Handler. The manipulation results in deserialization. The attack may be launched remotely. The attack requires a high level of complexity. The exploitation appears to be difficult. The exploit has been made public and could be used. Upgrading to version 2.0.0-beta.2 addresses this issue. The patch is identified as c66dfeb5f. The affected component should be upgraded.
Grav CMS v1.7.x and before is vulnerable to XML External Entity (XXE) through the SVG file upload functionality in the admin panel and File Manager plugin.
Grav CMS 1.6.30 with Admin Plugin 1.9.18 contains a persistent cross-site scripting vulnerability that allows authenticated attackers to inject malicious scripts through the page title field. Attackers can create a new page with a malicious script in the title, which will be executed when the page is viewed in the admin panel or on the site.
Summary
An IDOR (Insecure Direct Object Reference) vulnerability in the Grav CMS Admin Panel allows low-privilege users to access sensitive information from other accounts. Although direct account takeover is not possible, admin email addresses and other metadata can be exposed, increasing the risk of phishing, credential stuffing, and social engineering.
---
Details
Endpoint: /admin/accounts/users/{username} Tested Version: Grav Admin 1.7.48 Affected Accounts: Authenticated users with 0 privileges (non-privileged accounts)
Description: Requesting another user’s account details (e.g., /admin/accounts/users/admin) as a low-privilege user returns an HTTP 403 Forbidden response. However, sensitive information such as the admin’s email address is still present in the response source, specifically in the <title> tag.
system/src/Grav/Common/Flex/Types/Users/UserCollection.php <img width="700" height="327" alt="Screenshot 2025-08-24 021027" src="https://github.com/user-attachments/assets/7e69ae49-d8fc-442f-b00c-9efaec706b2e" />
system/blueprints/flex/user-accounts.yaml <img width="700" height="300" alt="Screenshot 2025-08-24 020521" src="https://github.com/user-attachments/assets/756631c8-d60b-4b84-a08a-2a9c2f81b41f" />
This is a classic IDOR vulnerability, where object references (usernames) are not properly protected from unauthorized enumeration.
---
PoC
1. Log in as a non-privileged user (0-privilege account). 2. Access another user’s endpoint, for example:
GET /admin/accounts/users/admin 3. Observe the HTTP 403 Forbidden response. 4. Inspect the page source; sensitive data such as the admin email can be seen in the <title> tag.
PoC Video:
https://drive.google.com/file/d/1lYqwqSkN5sPNmHvXGOk6R1mdIgVt71H/view
---
Impact
Type: Information Disclosure via IDOR Who is impacted: Low-privilege authenticated users can enumerate other accounts and extract sensitive metadata (admin emails). Risk: Exposed information can be used for targeted phishing, credential stuffing, brute-force attacks, or social engineering campaigns. Severity Justification: Only a low-privilege account is required, and sensitive metadata is leaked. Arbitrary code execution is not possible, but the information exposure is moderate risk.
---
Disclosure & CVE Request
We request a CVE ID for this vulnerability once validated. Please credit the discovery to:
Elvin Nuruyev Kanan Farzalili
Summary A path traversal vulnerability has been identified in Grav CMS, versions 1.7.49.5 , allowing authenticated attackers with administrative privileges to read arbitrary files on the underlying server filesystem. This vulnerability arises due to insufficient input sanitization in the backup tool, where user-supplied paths are not properly restricted, enabling access to files outside the intended webroot directory. The impact of this vulnerability depends on the privileges of the user account running the application.
PoC To accurately demonstrate the maximum potential impact of this vulnerability, the testing environment was configured in a specific way:
- Elevated Privileges: The application was run locally with the highest possible system privileges, operating under the root user account. - Objective: This configuration was chosen to unequivocally show that the path traversal vulnerability is not just a theoretical issue but can lead to a complete compromise of the underlying host when combined with poor operational practices. The ability to read any file on the system is the ultimate test of the flaw's severity.
Proof of Concept Goal: Under these conditions, the subsequent PoC will exploit the vulnerability to read the SSH private key of the root user (/root/.ssh/idrsa). The successful exfiltration of this key represents a worst-case scenario, as it would provide an attacker with persistent, undetectable, and complete administrative access to the host server. This highlights the critical intersection of an application-layer vulnerability and a infrastructure-level misconfiguration.
1- LOGIN AS ADMIN AND GO TO : http://127.0.0.1/admin/tools/backups 2- Change 'Root Folder' to backup directory /../../../../../../../root/.ssh/
<img width="1902" height="492" alt="Screenshot 2025-09-11 161519" src="https://github.com/user-attachments/assets/23a60dc3-7758-4e24-b910-e66a1dd1f5e2" />
3- CLICK : 'SAVE' 4- CLICK : 'Backup Now'
<img width="1916" height="512" alt="Screenshot 2025-09-11 154151" src="https://github.com/user-attachments/assets/88a63ff2-777e-467e-857b-0644ef698499" />
5- Extract Backup :
<img width="704" height="101" alt="Screenshot 2025-09-11 160114" src="https://github.com/user-attachments/assets/b91ce4db-9843-4280-b8f0-32c73aa12d4d" /> <img width="567" height="101" alt="Screenshot 2025-09-11 160135" src="https://github.com/user-attachments/assets/155ce7d8-c2fc-4b54-b054-f7c7550bec82" />
Summary
Grav CMS is vulnerable to a Server-Side Template Injection (SSTI) that allows any authenticated user with editor permissions to execute arbitrary code on the remote server, bypassing the existing security sandbox.
Details
Grav CMS uses a custom sandbox to protect the powerful Twig methods such as registerUndefinedFilterCallback(). These methods are designed to prevent SSTI attacks by denying the execution of dangerous PHP functions (e.g., exec(), passthru(), system(), etc.) within Twig template directives.
The current defense mechanism relies on a blacklist of prohibited functions (PHP, Twig), checked through the isDangerousFunction() method in the file system/src/Grav/Common/Twig.php:
php $this->twig->registerUndefinedFilterCallback(function (string $name) use ($config) { $allowed = $config->get('system.twig.safefilters'); if (isarray($allowed) && inarray($name, $allowed, true) && functionexists($name)) { return new TwigFilter($name, $name); } if ($config->get('system.twig.undefinedfilters')) { if (functionexists($name)) { if (!Utils::isDangerousFunction($name)) { usererror("PHP function {$name}() used as Twig filter. This is deprecated in Grav 1.7. Please add it to system configuration: system.twig.safefilters", EUSERDEPRECATED);
return new TwigFilter($name, $name); }
/ @var Debugger $debugger / $debugger = $this->grav['debugger']; $debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig filter. If you really want to use it, please add it to system configuration: system.twig.safefilters")); }
return new TwigFilter($name, static function () {}); }
return false; });
In this code, the isDangerousFunction() check is bypassed if the filter defined in the $name variable is considered safe. Only an administrator can mark a function as safe by adding it to the system.twig.safefilters configuration properties (whitelists that are empty by default) in the system/config/system.yaml file.
Notably, the Twig class is defined within the system/src/Grav/Common/Twig.php file, and the Twig object (and environment) is instantiated there:
php / Class Twig @package Grav\Common\Twig / class Twig { / @var Environment / public $twig; / @var array / public $twigvars = []; / @var array / public $twigpaths; / @var string / public $template;
// Constructor public function construct(Grav $grav) { $this->grav = $grav; $this->twigpaths = []; }
// Twig initialization method public function init() { if (null === $this->twig) { / @var Config $config / $config = $this->grav['config']; / @var UniformResourceLocator $locator / $locator = $this->grav['locator']; / @var Language $language / $language = $this->grav['language'];
$activelanguage = $language->getActive(); ... } } }
Since the security sandbox does not fully protect the Twig object, it is possible to interact with it (e.g., call methods, read/write attributes) through maliciously crafted Twig template directives injected into a web page. This allows an authenticated editor to add arbitrary functions to the Twig attribute system.twig.safefilters, effectively bypassing the Grav CMS sandbox.
Proof of Concept (PoC) An authenticated user with permission to edit a page (with Twig processing enabled) in the Grav CMS admin console can inject malicious template directives to execute arbitrary OS commands on the remote web server.
For example, to exploit the vulnerability and execute the prohibited system('id') command, bypassing the sandbox, an editor could create/edit a web page with the following template directives:
twig {% set arr = {'1':'system', '2':'exec'} %} {{ vardump(grav.twig.twigvars['config'].set('system.twig.safefilters', arr)) }} {{ 'id'|system }} {{ 'whoami'|exec }}
Once the page is saved, it can be accessed by unauthenticated users, triggering the execution of the system('id') command on the server hosting the vulnerable Grav CMS.
Impact The vulnerability allows remote code execution on the underlying server, which could lead to full server compromise.
Summary A user with admin panel access and permissions to create or edit pages in Grav CMS can enable Twig processing in the page frontmatter. By injecting malicious Twig expressions, the user can escalate their privileges to admin or execute arbitrary system commands via the scheduler API. This results in both Privilege Escalation (PE) and Remote Code Execution (RCE) vulnerabilities.
Details Grav CMS allows Twig to be executed in page templates if enabled in admin panel (process: twig: true). A user with publisher/editor privileges, that can create or edit pages and enable twig processing, can thereby inject arbitrary code that will execute in the context of the page render.
This enables exploitation of Grav internal APIs such as: - grav.user.update() and grav.user.save() for escalating the current user to super admin or admin - grav.scheduler.addCommand(), grav.scheduler.save() and grav.scheduler.run() for code execution
The Twig sandbox is not enforced in this context, allowing full access to any backend PHP object and method in the system/src/Grav/Common directory.
PoC Preconditions: - You must have access to a non-admin user with permission to create/edit pages (admin.pages access) - For Privilege Escalation, you also have to be logged in to the site with the same user as the admin panel.
Steps to reproduce Privilege Escalation: 1. Login into the non-admin page (default at cms-url/login). 2. Login to the admin panel, create or edit a page and set the Twig processing to true (Advanced -> Process: Twig: true). 3. Inject the following payload into the page content to escalate privileges: {% set = grav.user.update({ 'access': { 'admin': { 'login': true, 'super': true } } }, {}) %} {% set = grav.user.save() %} 4. Visit the edited/created page url. The logged in user is now admin. (Note: For the changes to show, you need to log out of the admin panel and relogin).
Steps to reproduce Remote Code Execution: 1. Login to the admin panel, create or edit a page and set the Twig processing to true (Advanced -> Process: Twig: true). 2. Inject the following payload into the page content to execute commands: {% set = grav.scheduler.addCommand('curl', ['http://localhost:8000']) %} {% set = grav.scheduler.save() %} {% set = grav.scheduler.run() %} 3. Visit the page to trigger the execution. The system will issue a curl request.
Impact This vulnerability allows: - Privilege Escalation from any user with page editing capabilities to full admin (super) access. - Remote Code Execution, as the attacker can run system arbitrary commands via the scheduler API.
It affects any Grav CMS installation where users with lower privileges are allowed to create or edit pages and Twig processing is not globally disabled.