GitHub Security Advisory: GHSA-gwfr-jfjf-92vv
Package: getgrav/grav (COMPOSER)
Severity: LOW (CVSS 5.0)
CWE: CWE-20: Improper Input Validation, CWE-502: Deserialization of Untrusted Data
Vulnerable versions: < 2.0.0-beta.2
Patched in: 2.0.0-beta.2

Summary: Grav has Insecure Deserialization in File Cache

Details:
# Insecure Deserialization in File Cache

- **Severity:** High 
- **CWE:** CWE-502
- **Location:** `system/src/Grav/Framework/Cache/Adapter/FileCache.php`
- **Sink:** `unserialize($value, ['allowed_classes' => true])`

## Affected version(s)

- **Affected:** `>= 1.7.44` and `<= 1.7.49.5` (verified in current codebase and changelog-covered releases).
- **Fixed:** No upstream fix identified in the reviewed branch at the time of analysis.
- **Notes:** Earlier `1.7.x` releases may also be affected, but were not fully back-traced in this review.

## Notes
`allowed_classes => true` allows object instantiation and does not constrain classes.

## PoC (Primitive Demonstration)

### Preconditions
- Local PHP runtime.
- Goal is to validate the deserialization primitive used in cache retrieval.

### Steps
```bash
php -r '
class CacheWakeup { public function __wakeup(){ file_put_contents("/tmp/grav_filecache_poc.txt", "wakeup"); } }

$payload = serialize(new CacheWakeup());
unserialize($payload, ["allowed_classes" => true]);

echo file_exists("/tmp/grav_filecache_poc.txt") ? "FILECACHE_UNSERIALIZE_TRIGGERED\n" : "FILECACHE_UNSERIALIZE_NOT_TRIGGERED\n";
'
```

### Expected Result
- Output contains: `FILECACHE_UNSERIALIZE_TRIGGERED`.

### Interpretation
This reproduces the same unsafe primitive used by `FileCache::doGet()`:
`unserialize($value, ['allowed_classes' => true])`.
If cache files are attacker-tampered, object magic methods may execute.

## Exploit Preconditions
- Cache file poisoning/tampering capability.

## Recommendation
- Avoid object deserialization in cache payloads.
- Use non-object formats and integrity protection for cache files.



---

## Maintainer note — fix applied (2026-04-24)

Fixed in Grav core on the `2.0` branch: commit [`c66dfeb5f`](https://github.com/getgrav/grav/commit/c66dfeb5f) — will ship in **2.0.0-beta.2**.

**What changed:** `Framework\Cache\Adapter\FileCache` now HMAC-signs every cache payload with `Security::getNonceKey()` on write, and verifies the HMAC on read. Tampered, forged, or pre-upgrade files are treated as cache misses and unlinked instead of being unserialized. The on-disk format is now versioned:

```
v2
<expires>
<key>
<hmac-hex>
<serialized>
```

Existing caches rebuild transparently on first read. Note that `Framework\Cache\Adapter\FileCache` isn't wired into Grav's main cache path — Symfony's `FilesystemAdapter` is — but the class is reachable by plugin and downstream consumers, so the hardening applies defensively.

**Files:**
- [`system/src/Grav/Framework/Cache/Adapter/FileCache.php`](https://github.com/getgrav/grav/blob/2.0/system/src/Grav/Framework/Cache/Adapter/FileCache.php).
- [`tests/unit/Grav/Common/Security/FileCacheSecurityTest.php`](https://github.com/getgrav/grav/blob/2.0/tests/unit/Grav/Common/Security/FileCacheSecurityTest.php) — round-trip, tampered-payload rejection, wrong-key forgery rejection, pre-v2 file rebuild, key-field mismatch.