GHSA-WFQ5-QGQP-HVHV

Vulnerability from github – Published: 2026-03-17 20:05 – Updated: 2026-03-20 21:22
VLAI?
Summary
Unauthenticated Reflected XSS via innerHTML in AVideo
Details

Summary

AVideo contains a reflected XSS vulnerability that allows unauthenticated attackers to execute arbitrary JavaScript in a victim's browser. User input from a URL parameter flows through PHP's json_encode() into a JavaScript function that renders it via innerHTML, bypassing encoding and achieving full script execution.

Root Cause

The vulnerability is caused by two issues working together:

1. Source: Unescaped user input passed to JavaScript (videoNotFound.php)

File: view/videoNotFound.php line 49

if (!empty($_REQUEST['404ErrorMsg'])) {
    echo 'avideoAlertInfo(' . json_encode($_REQUEST['404ErrorMsg']) . ');';
}

PHP's json_encode() with default flags only escapes quotes ("\") and backslashes. It does NOT escape HTML special characters (<, >, /). The resulting string contains raw HTML tags that are passed directly to JavaScript.

2. Sink: innerHTML renders HTML tags as executable DOM (script.js)

File: view/js/script.js

function avideoAlertInfo(msg) {            // line ~1891
    avideoAlert("", msg, 'info');           // calls ↓
}

function avideoAlert(title, msg, type) {   // line ~1270
    avideoAlertHTMLText(title, msg, type);  // calls ↓
}

function avideoAlertHTMLText(title, msg, type) {  // line ~1451
    var span = document.createElement("span");
    span.innerHTML = msg;                  // line 1464 — XSS SINK
    swal({ content: span });
}

innerHTML parses the string as HTML. Any <img>, <svg>, or other HTML tags with event handlers are instantiated as real DOM elements, triggering JavaScript execution.

Data Flow

URL parameter (?404ErrorMsg=PAYLOAD)
    → $_REQUEST['404ErrorMsg']
    → json_encode()          ← does NOT escape < > /
    → avideoAlertInfo()
    → avideoAlert()
    → avideoAlertHTMLText()
    → span.innerHTML = msg   ← renders HTML tags, executes JS

Proof of Concept

https://localhost/view/videoNotFound.php?404ErrorMsg=<img src=x onerror=alert(document.domain)>

image

The page renders:

avideoAlertInfo("<img src=x onerror=alert(document.domain)>");

Which flows to span.innerHTML = "<img src=x onerror=alert(document.domain)>". The browser creates an <img> element, src=x fails to load, onerror fires alert(document.domain).

Affected Code

File Line Issue
view/videoNotFound.php 49 json_encode() does not escape < > for HTML context
view/js/script.js 1464 span.innerHTML = msg renders user input as HTML
view/js/script.js 1282 span.innerHTML = msg in avideoAlertWithCookie()
view/js/script.js 1335 span.innerHTML = __(msg,true) in avideoConfirm()
view/js/script.js 1358 span.innerHTML = msg in avideoAlertOnceForceConfirm()

The innerHTML sink exists in 4 functions. Any future code that passes user input to avideoAlertInfo(), avideoAlertWarning(), avideoAlertDanger(), or avideoAlertSuccess() will create additional XSS vectors.

Remediation

Fix 1: Escape HTML in PHP (source fix)

// view/videoNotFound.php line 49
// BEFORE (vulnerable):
echo 'avideoAlertInfo(' . json_encode($_REQUEST['404ErrorMsg']) . ');';

// AFTER (fixed):
echo 'avideoAlertInfo(' . json_encode($_REQUEST['404ErrorMsg'], JSON_HEX_TAG | JSON_HEX_AMP) . ');';

JSON_HEX_TAG converts <\u003C and >\u003E, preventing HTML injection.

Fix 2: Use textContent instead of innerHTML (sink fix, recommended)

// view/js/script.js - all alert functions
// BEFORE (vulnerable):
span.innerHTML = msg;

// AFTER (fixed):
span.textContent = msg;

textContent treats the string as plain text — HTML tags are displayed literally, never parsed or executed.

Fix 3: Add Content-Security-Policy header (defense in depth)

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

Impact

  • Session hijacking — steal PHPSESSID cookie (not HttpOnly by default)
  • Account takeover — use stolen session to change password or email
  • Phishing — inject a realistic login form inside the SweetAlert modal
  • Worm propagation — inject self-spreading payloads via comments/messages
  • Admin compromise — send crafted link to admin, steal session, gain full control
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "wwbn/avideo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "25.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33035"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-17T20:05:23Z",
    "nvd_published_at": "2026-03-20T05:16:16Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nAVideo contains a reflected XSS vulnerability that allows unauthenticated attackers to execute arbitrary JavaScript in a victim\u0027s browser. User input from a URL parameter flows through PHP\u0027s `json_encode()` into a JavaScript function that renders it via `innerHTML`, bypassing encoding and achieving full script execution.\n\n\n## Root Cause\nThe vulnerability is caused by two issues working together:\n\n### 1. Source: Unescaped user input passed to JavaScript (videoNotFound.php)\n\n**File:** `view/videoNotFound.php` line 49\n\n```php\nif (!empty($_REQUEST[\u0027404ErrorMsg\u0027])) {\n    echo \u0027avideoAlertInfo(\u0027 . json_encode($_REQUEST[\u0027404ErrorMsg\u0027]) . \u0027);\u0027;\n}\n```\n\nPHP\u0027s `json_encode()` with default flags only escapes quotes (`\"` \u2192 `\\\"`) and backslashes. It does **NOT** escape HTML special characters (`\u003c`, `\u003e`, `/`). The resulting string contains raw HTML tags that are passed directly to JavaScript.\n\n### 2. Sink: innerHTML renders HTML tags as executable DOM (script.js)\n\n**File:** `view/js/script.js`\n\n```javascript\nfunction avideoAlertInfo(msg) {            // line ~1891\n    avideoAlert(\"\", msg, \u0027info\u0027);           // calls \u2193\n}\n\nfunction avideoAlert(title, msg, type) {   // line ~1270\n    avideoAlertHTMLText(title, msg, type);  // calls \u2193\n}\n\nfunction avideoAlertHTMLText(title, msg, type) {  // line ~1451\n    var span = document.createElement(\"span\");\n    span.innerHTML = msg;                  // line 1464 \u2014 XSS SINK\n    swal({ content: span });\n}\n```\n\n`innerHTML` parses the string as HTML. Any `\u003cimg\u003e`, `\u003csvg\u003e`, or other HTML tags with event handlers are instantiated as real DOM elements, triggering JavaScript execution.\n\n### Data Flow\n\n```\nURL parameter (?404ErrorMsg=PAYLOAD)\n    \u2192 $_REQUEST[\u0027404ErrorMsg\u0027]\n    \u2192 json_encode()          \u2190 does NOT escape \u003c \u003e /\n    \u2192 avideoAlertInfo()\n    \u2192 avideoAlert()\n    \u2192 avideoAlertHTMLText()\n    \u2192 span.innerHTML = msg   \u2190 renders HTML tags, executes JS\n```\n\n---\n\n## Proof of Concept\n```\nhttps://localhost/view/videoNotFound.php?404ErrorMsg=\u003cimg src=x onerror=alert(document.domain)\u003e\n```\n\u003cimg width=\"1918\" height=\"1035\" alt=\"image\" src=\"https://github.com/user-attachments/assets/20077ce2-5b49-4bd3-a7df-ab48be786cc1\" /\u003e\n\nThe page renders:\n```javascript\navideoAlertInfo(\"\u003cimg src=x onerror=alert(document.domain)\u003e\");\n```\n\nWhich flows to `span.innerHTML = \"\u003cimg src=x onerror=alert(document.domain)\u003e\"`. The browser creates an `\u003cimg\u003e` element, `src=x` fails to load, `onerror` fires `alert(document.domain)`.\n\n## Affected Code\n\n| File | Line | Issue |\n|------|------|-------|\n| `view/videoNotFound.php` | 49 | `json_encode()` does not escape `\u003c` `\u003e` for HTML context |\n| `view/js/script.js` | 1464 | `span.innerHTML = msg` renders user input as HTML |\n| `view/js/script.js` | 1282 | `span.innerHTML = msg` in `avideoAlertWithCookie()` |\n| `view/js/script.js` | 1335 | `span.innerHTML = __(msg,true)` in `avideoConfirm()` |\n| `view/js/script.js` | 1358 | `span.innerHTML = msg` in `avideoAlertOnceForceConfirm()` |\n\nThe `innerHTML` sink exists in 4 functions. Any future code that passes user input to `avideoAlertInfo()`, `avideoAlertWarning()`, `avideoAlertDanger()`, or `avideoAlertSuccess()` will create additional XSS vectors.\n\n\n## Remediation\n\n### Fix 1: Escape HTML in PHP (source fix)\n\n```php\n// view/videoNotFound.php line 49\n// BEFORE (vulnerable):\necho \u0027avideoAlertInfo(\u0027 . json_encode($_REQUEST[\u0027404ErrorMsg\u0027]) . \u0027);\u0027;\n\n// AFTER (fixed):\necho \u0027avideoAlertInfo(\u0027 . json_encode($_REQUEST[\u0027404ErrorMsg\u0027], JSON_HEX_TAG | JSON_HEX_AMP) . \u0027);\u0027;\n```\n\n`JSON_HEX_TAG` converts `\u003c` \u2192 `\\u003C` and `\u003e` \u2192 `\\u003E`, preventing HTML injection.\n\n### Fix 2: Use textContent instead of innerHTML (sink fix, recommended)\n\n```javascript\n// view/js/script.js - all alert functions\n// BEFORE (vulnerable):\nspan.innerHTML = msg;\n\n// AFTER (fixed):\nspan.textContent = msg;\n```\n\n`textContent` treats the string as plain text \u2014 HTML tags are displayed literally, never parsed or executed.\n\n### Fix 3: Add Content-Security-Policy header (defense in depth)\n\n```\nContent-Security-Policy: default-src \u0027self\u0027; script-src \u0027self\u0027; style-src \u0027self\u0027 \u0027unsafe-inline\u0027\n```\n\n## Impact\n\n- **Session hijacking** \u2014 steal `PHPSESSID` cookie (not HttpOnly by default)\n- **Account takeover** \u2014 use stolen session to change password or email\n- **Phishing** \u2014 inject a realistic login form inside the SweetAlert modal\n- **Worm propagation** \u2014 inject self-spreading payloads via comments/messages\n- **Admin compromise** \u2014 send crafted link to admin, steal session, gain full control",
  "id": "GHSA-wfq5-qgqp-hvhv",
  "modified": "2026-03-20T21:22:24Z",
  "published": "2026-03-17T20:05:23Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-wfq5-qgqp-hvhv"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33035"
    },
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/cca6196f4072cb9acc39b1030fb8fb1702b4f69b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/WWBN/AVideo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Unauthenticated Reflected XSS via innerHTML in AVideo"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…