GitHub Advisoryadvisory · Packagist

GHSA-r7fx-8g49-7hhr Grav CMS vulnerable to stored XSS via Markdown media attribute() action

MEDIUM Published 2026-05-05 Modified 2026-05-13 Official page ↗
Aliases
CVE-2026-42841
Ecosystem
Packagist
Packages
getgrav/grav
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N
Weaknesses
CWE-79
Reviewed
2026-05-05

Affected packages

• Packagist getgrav/grav: < 2.0.0-beta.2 → fixed in 2.0.0-beta.2

Description

Summary An authenticated user with page editing permissions can inject an executable JavaScript event-handler attribute into rendered image HTML through Grav's Markdown media action syntax. The issue is caused by Markdown image query parameters being converted into callable media actions. The public `attribute()` media method can be reached this way, allowing an editor to set an arbitrary HTML attribute name and value on the generated image element. For example, this Markdown: ```markdown ) ``` is rendered as an image tag containing an executable `onload` handler: ```html ``` This results in stored XSS when another user views the affected page. In a multi-user Grav installation, a lower-privileged page editor could use this to target administrators or reviewers who preview or view editor-controlled content. Tested versions: • Grav CMS: 1.7.49.5 • Admin Plugin: 1.10.49.1 Suggested classification: • CWE-79: Improper Neutralization of Input During Web Page Generation • Stored Cross-Site Scripting • Suggested CVSS v4.0 score if page editing is considered high privilege: 6.9 Medium • Suggested CVSS v4.0 vector: `CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:P/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N` • Suggested CVSS v3.1 score if page editing is considered high privilege: 6.9 Medium • Suggested CVSS v3.1 vector: `CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:L/A:N` Details The issue appears to come from this source-to-sink flow: 1. `ParsedownGravTrait::inlineImage()` processes Markdown images. 2. `Excerpts::processImageExcerpt()` resolves the referenced media object. 3. `Excerpts::processMediaActions()` parses the image URL query string into media actions. 4. `call_user_func_array()` invokes the requested action method on the media object. 5. `MediaObjectTrait::attribute()` stores the attacker-controlled attribute name and value. 6. The media object returns a Parsedown element containing the injected attribute. 7. Parsedown renders the attribute name into the final HTML. Relevant code paths: ```text system/src/Grav/Common/Markdown/ParsedownGravTrait.php system/src/Grav/Common/Page/Markdown/Excerpts.php system/src/Grav/Common/Media/Traits/MediaObjectTrait.php system/src/Grav/Common/Page/Medium/StaticImageMedium.php system/src/Grav/Common/Page/Medium/ImageMedium.php vendor/erusev/parsedown/Parsedown.php ``` In `system/src/Grav/Common/Markdown/ParsedownGravTrait.php`, Markdown image excerpts are passed into Grav-specific media handling: ```php if (isset($excerpt['element']['attributes']['src'])) { $excerpt = $this->excerpts->processImageExcerpt($excerpt); } ``` In `system/src/Grav/Common/Page/Markdown/Excerpts.php`, query string parameters are converted into media action calls. The query parameter name becomes the method name: ```php $carry[] = ['method' => $parts[0], 'params' => $value]; ``` The requested method is later invoked dynamically: ```php $medium = call_user_func_array([$medium, $action['method']], $args); ``` For the payload: ```text attribute=onload,alert(document.domain) ``` the method is `attribute`, and the arguments are `onload` and `alert(document.domain)`. In `system/src/Grav/Common/Media/Traits/MediaObjectTrait.php`, `attribute()` stores the caller-controlled attribute name directly: ```php public function attribute($attribute = null, $value = '') { if (!empty($attribute)) { $this->attributes[$attribute] = $value; } return $this; } ``` The image media classes then return the collected attributes as attributes for an `img` element. In `system/src/Grav/Common/Page/Medium/StaticImageMedium.php`: ```php return ['name' => 'img', 'attributes' => $attributes]; ``` The non-static image path in `system/src/Grav/Common/Page/Medium/ImageMedium.php` also returns image attributes in the same way. Finally, in `vendor/erusev/parsedown/Parsedown.php`, the attribute value is escaped, but the attribute name is rendered as-is: ```php $markup .= ' '.$name.'="'.self::escape($value).'"'; ``` As a result, the attacker-controlled attribute name `onload` is emitted into the final HTML and executes as a browser event handler. The Admin Plugin's save-time XSS detection does not appear to block this because the stored content is Markdown media syntax, not raw HTML: ```markdown ) ``` The dangerous HTML is generated later during Markdown/media rendering. PoC I reproduced this on a standard Grav CMS installation with the Admin Plugin enabled. Configuration and prerequisites: • Grav CMS 1.7.49.5 • Admin Plugin 1.10.49.1 • Markdown processing enabled for pages • A user account with permission to create or edit pages • A page media file available in the edited page folder, for example `market-overview.gif` Steps to reproduce: 1. Install Grav CMS with the Admin Plugin. 2. Log in to the Admin panel as a user who can create or edit pages. 3. Create a normal content page or edit an existing one. 4. Add or reference a page media file named `market-overview.gif`. 5. Insert the following Markdown into the page body: ```markdown ) ``` 6. Save the page. 7. Open the rendered frontend page in a browser. 8. The JavaScript payload executes when the image loads. 9. Inspect the generated DOM. The rendered image element contains the injected `onload` attribute. Expected result: The Markdown media action should not be able to generate executable HTML attributes. The payload should be rejected, sanitized, or rendered without the dangerous event-handler attribute. Actual result: The payload is accepted and rendered as an executable image event handler: ```html ``` Screenshots: • the stored Markdown payload in the page editor • the JavaScript alert executing on the frontend page • browser DevTools showing the injected `onload` attribute in the rendered DOM Impact This is a stored cross-site scripting vulnerability. An authenticated user with page editing permissions can store a malicious Markdown image reference. When the affected page is rendered, the payload executes in the browser of any user who views that page. In multi-user Grav installations, this may allow a lower-privileged editor to target administrators, reviewers, or other privileged users who preview or view editor-controlled content. Depending on the victim's privileges and deployed plugins, successful exploitation may allow JavaScript execution in the site origin, access to same-origin page data available to the victim, and same-origin actions performed as the victim. CVSS 4.0 rationale: • `AV:N`: the issue is exploitable through the web application. • `AC:L`: no special race condition or complex setup is required after page editing access is obtained. • `AT:P`: exploitation requires the malicious Markdown/media reference to be stored in page content and later rendered to a victim. • `PR:H`: the attacker needs page editing capability. • `UI:P`: a victim must view the affected page. The demonstrated `onload` payload executes on passive page rendering, without requiring a click or form submission by the victim. • `VC:H/VI:L/VA:N`: confidentiality impact can be high when t…

Related

Alias · CVE (1)

Weakness · CWE (1)

  • CWE-79 Improper Neutralization of Input During Web Page Generation weakness

References