Grav CMS vulnerability path traversal: 0-day in FormFlash without authentication

Depov

Activist
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
126
Reaction score
116
Deposit
0$
CVSS 8.8 (HIGH), zero privileges, automated operation - according to the CISA-ADP, Technical Impact: total. CVE-2026-42608 in Grav CMS - pathtraversal via the FormFlash component, where the only POST parameter __form-flash-id Turns any page with a form to the entry point. An unautrified attacker creates arbitrary directories and writes YAML files with controlled content directly in the configuration directory. In the Russian-speaking segment, a detailed analysis of the code of this vulnerability has not yet been published - I close the gap: from the vulnerable line of PHP to a working HTTP request, with the context of the previous CVE Grav and the mapping on MITRE ATT&CK.

FormFlash: Vulnerable Grav CMS Component and Attack Surface

Grav - flat-file CMS on PHP with 14 500+ stars on GitHub and over 100 000 Docker image downloads (according to TantoSec). Instead of the Grav database stores everything in YAML files: site configuration, user information, page content, plugin settings - everything lies on the disk. To understand the impact of pathtraversal is fundamental: any file entry into an arbitrary directory is a modification of the application behavior. Not “potential”, but direct.




FormFlash (Grav\Framework\Form\FormFlash) - the internal component responsible for the preservation of these forms between HTTP redistors. Standard scenario: the visitor fills the contact form, the POST - Gran sends redirect (302) to the confirmation page, and FormFlash between two queries resets intermediate data into a temporary directory on a disk. After processing, removes.




Critical detail: FormFlash accepts session identifier directly from the POST-parameter __form-flash-id. It is this parameter that determines the path to the writing directory. The component is available on any page with Gran Forms: contact form, /admin/login, registration, any custom form. Authentication is not needed - public endpoint.




The surface of the attack is determined by the fact that most Grav installations with Administrator plugin contain at least one form. According to TantoSec, ZoomEye finds about 36 000 instance on the request of “Grav Admin Login” – each of them potentially vulnerable.

How FormFlash Builds a Storage Path

Method __construct() FormFlash class gets session_id from the POST-parameter and uses it when building the path. Key fragment (according to GHSA-hmcx-ch82-3fv2):

PHP:

$folder = $config['folder']

?? ($this->sessionId

? 'tmp://forms/' . $this->sessionId

: '');

$this->folder = $folder && $locator->isStream($folder)

? $locator->findResource($folder, true, true)

: $folder;

$this->sessionId - value __form-flash-id from the POST request. It is concaneted directly with the string 'tmp://forms/' without any check on the control symbols of the path. Further findResource() resolves stream-URI into the real path of the file system. If sessionId contains sequences ../, the permitted path goes beyond the borders tmp/forms/ and gets into the arbitrary catalogs of webroot.




The reason is the absence of a call basename() or regex-validation before concatenation. The developers have addressed session_id as with a trusted meaning: “well, a line from POST, it does not get into SQL – what harm?” In flat-file architecture, the answer is different.

Path traversal without authentication: anatomy CVE-2026608

Vulnerability is classified as CWE-22 (Improper Limitation of a Pathname to a Restricted Directory). I'll disassemble CVSS-vector by components - here CVSS 4.0:

• AV:N is a network attack, standard HTTP POST

• AC:L - difficulty low, no special conditions

• AT:N - does not require preliminary actions

• PR:N - privileges are not needed (unauthenticated)

• UI:N - User interaction is not required

• VC:H / VI:H - high impact on privacy and integrity

• VA:N - there is no direct impact on availability (although DoS is possible through exhaustodeion)

• E:p- there is proof-of-concept

CISA SSVC ranked: Exploitation: poc, Automatable: yes, Technical Impact: total. The solution is Track* (follow, prepare patch).

What is recorded on disk

With successful operation, FormFlash creates:

1. Arbitrary directory along the path given through the traversal sequence

2. File index.yamlwithin this directory with data partially controlled by the attacker

Contents index.yaml formed from these forms: field form, id, unique_id and user data from POST parameters form-data[*]. The attacker controls both the recording path and part of the file contents - within the framework of the YAML format. This is already a Configuration Injection.

Grav CMS catalogues in the access zone

Since Grav stores everything in the file system, traversal opens access to critical directories:
1779445241246.png

Recording in user/config/ - the most greasy vector: Grav downloads the configuration of YAML-fibles on each request. Injection settings into the subdirectory of the plugin can change its behavior - from the inclusion of debugging mode to the modification of routes. According to the OWASP Top 10 classification, it is at the same time A01:2021 (Broken Access Control - no authentication check for a critical recording operation) and A03:2021 (Injection - traversal as an injection into the file system path).

Grav CMS exploit: vulnerability playback step

Adjustments to the environment

• OS: any with Docker support (Linux, macOS, Windows with WSL2)

• RAM: minimum 512 MB for Grav container

• Grav CMS: v1.7.49.5 is the last stable release at the time of detection of the vulnerability (vulnerable). Recommended official Docker-image

• Tools: Burp Suite, curlor any HTTP client with the ability to manually form POST-body

• Mandatory condition: having at least one page with Gran Form (standard installation with the Administrator plugin contains /admin/login)

• Network: HTTP access to the target installer

Attack Chain: From Path Transical to Unuthenticated RCE in Grav

CVE-2026-42608 itself gives the creation of directories and recording of YAML-files. There is no direct execution of the code. But in the context of the complete chain of attack on the Grav CMS, this vulnerability becomes a link that seriously reduces the entry threshold. Exploit Public-Facing Application (T1190, Initial Access by MITRE ATT&CK).

Three vectors of attack development

Configuration Injection. Grav dynamically loads the configuration from user/config/. Recording index.yaml in the subdirectory of the plugin or theme can change the settings of routing, processing templates, authorization. In a flat-file architecture, this is the equivalent of SQL injection in the configuration table - only through the file system. It sounds exotic, but works just as reliably.




Cross-User Session Corruption. Controlling session_id, the attacker rewrites the time data of the forms of other users - breaks the isolation of sessions. If the administrator fills out the form in the control panel, the attacker can replace its intermediate data.




Denial of Service via inode exhaustion. Each POST-request creates a new directory. On file systems with an inodes limit (ext4 with default settings), the mass sending of requests will exhaust inodes - the server will stop creating files. For shared-hostings, this is especially painful.

Historical chain CVE in Grav CMS

Gran CMS has a documented story of path traversal and related vulnerabilities. Together, they form a chain from unauthentiated access to full RCE (according to the TantoSec study):




CVE-2024-27921 (CVSS 8.8 HIGH, CWE-22) - pathtrasal when downloading files in versions up to 1.7.45. An authenticated user with low privileges can replace or create files with extensions .json, .zip, .css, .gif. EPSS: 0.0879 - is included in the Top 10% probability of operation (92nd Percentage), which hints at real use. CISA-ADP: Automatetable: no, Technical Impact: total.




CVE-2024-34082 (CVSS 8.5 HIGH, CWE-269 - Improp Privilege Management) - reading arbitrary server files via Twig syntax in versions up to 1.7.46. User with page editing rights reads user/accounts/*.yamlwhere password hashes, 2FA secrets and reset tokens lie. CISA-ADP: Technical Impact: total.




Chain of operation, described by TantoSec: Password Reset Poisoning (through Host header on /admin/forgot) → Administant account capture → CVE-2024-34082 (reading of account data through STI) → CVE-2024-27921 (loading files through path traversal) → Scheduler abuse to run the code on the server.




CVE-2026-42608 adds a new vector to the beginning of this chain: it works completely without authentication and does not require SMTP (unlike Password Reset Poisoning). Where the mail server is not configured, it is CVE-2026-42608 that gives an alternative entry point for modifying the file system.

Mapping on MITRE ATT&CK
1779445259789.png

The chain runs from Initial Access (T1190) through Discovery and Collection to Persistence and Execution - six ATT&CK tactics with one set of vulnerabilities in one CMS.

Patral contrasts in Grav CMS and detectability

When the operation of CVE-2026-42608 is not working

Restriction of the rights of the web server. If PHP/Apache/Nginx is running with minimal privileges, and user/config/ and user/accounts/ owned by root with permission 0750 - the recording will not pass. In practice, most installations via Docker image use www-data as the owner of the entire webroot, and there are no restrictions. Hardened-installation on bare metal with the separation of privileges - an exception, not a rule.




Lack of forms. Grav as a static generator without an Admin plugin and without custom forms does not provide the attack surface. Configuration is atypical - according to TantoSec, most production installations use Administrator plugin.




Reverse proxy with URL-normalization. Nginx with merge_slashes on or Cloudflare with WAF may normalize or reject requests with ../ in the body. But the parameter __form-flash-id transferred to POST body (not in the URL), and not all WAF inspect POST parameters with the same depth. It's gonna be lucky.

Approaches to the Detect

At the WAF level. Monitoring of POST-parameters on sequence ../ and their encoded variants (..%2f, %2e%2e/) in the parameter __form-flash-id. ModSecureity CRS rule 930100 covers the base case, but for Gran it is more effective than a targeted rule on a specific parameter - less false positive from legitimate forms.




At the file system level. Set up auditd or inotify to monitoring file creation in user/config/, user/accounts/, user/pages/. Appearance index.yaml in unexpected subdirections - a straight IoC. In the normal operation, Grav forms are written only in tmp/forms/<session_uuid>/.




At the level of the logs. POST-queries with __form-flash-idcontaining symbols /, \ or .. - anomaly. In the staffing, this parameter contains the session UUID (strictly alphanumeric line). Simple grep on access logs with regular expression on traversal-patterns in POST data reveals attempts at operation.

Patch and Grav CMS Security: How CVE-2026608 Closed

Correction used in the branch of Grav 2.0 - comit d904efc33, included in the release of 2.0-beta.2 (according to the advisory GHSA-mcx-ch82-3fv2, the date of the fix is 2026-04-24).




The essence of the patch: FormFlash::construct() Now Validating session_id, unique_id and id through a strict allowlist - regular expression [A-Za-z0-9,_-]{1,64}. Values that have not been validated are thrown into an empty line. With an empty identifier methods save(), delete() and getTmpDir() become no-op: POST-request with form-flash-id=../../user/config/proof_dir It just doesn't create anything on the disk. One regex and no vulnerability.




The patch is accompanied by 32 unit tests in the file FormFlashSecurityTest.php, covering the original PoC and its variations (double coding, alternative separators, edge cases of length). Here the developers are good - closed not only the hole, but also all the obvious bypasses.

Protective checklist

1. Update Grav to 2.0.0-beta.2 or later. For a 1.7.x patch not ported - if the update is not possible, apply workaround through WAF

2. File system rights: set user/config/and user/accounts/in chmod 0750 with owner other than the web server process (where the architecture allows)

3. WAF-rule: block POST requests with ../, ..%2f, %2e%2e/in the parameter __form-flash-id

4. Monitoring: auditd/intoify to create files in user/config/Out of standard administrative activity

5. Surface minimization: if forms on public pages are not used - disable the Form plugin or limit it to authenticated users through route configuration

In CVE-2026-42608 I am occupied by the technical side (it is trivial - one challenge basename() or regex solves the problem), and the steady pattern that I see when auditing the flat-file CMS PHP code. Developers systematically treat session identifiers as a secure value: “Cooking/POST string, it does not get into SQL – what is the harm of it?” In the flat-file architecture, the answer is different: any user input involved in the file system path design is a direct vector for path traversal. Grav is not unique: the same pattern is found in other CMS with file storage, just Grav has a vulnerable code on unauthentiated endpoint with shapes on every time.




EPSS for CVE-2026-42608 is still 0.0012 (30th percentile) - formally low probability of mass use. But CISA-APD labels vulnerability as Automated: yes - therefore, the mass scanning via Shodan/ZoomEye on the signature "Grav Admin Login" with the automatic sending of the PoC request to each of the 36 000 constants is not a question "if", but "when". Until the EPSS has caught up with reality, the patch window is still open
 
Top Bottom