CVE-2026-41061 is a medium-severity (CVSS 5.4) Cross-site Scripting (XSS) vulnerability in wwbn/avideo. No vendor fix is recorded yet; mitigation options are listed below.
WWBN AVideo Vulnerable to stored XSS via Unanchored Duration Regex in Video Encoder Receiver
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-41061.
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.
How urgent is this, really
CVE-2026-41061 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
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
The isValidDuration() regex at objects/video.php:918 uses /^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}/ without a $ end anchor, allowing arbitrary HTML/JavaScript to be appended after a valid duration prefix. The crafted duration is stored in the database and rendered without HTML escaping via echo Video::getCleanDuration() on trending pages, playlist pages, and video gallery thumbnails, resulting in stored cross-site scripting.
Details
Input entry point: objects/aVideoEncoderReceiveImage.json.php:208
// Line 203-211
if (!empty($_REQUEST['duration'])) {
$video->setDuration($_REQUEST['duration']);
}
Insufficient validation: objects/video.php:918
static function isValidDuration($duration) {
// ...
return preg_match('/^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}/', $duration);
// Missing $ anchor here -----------------------------------^
}
The regex matches 00:00:01 at the start of the string but ignores everything after it. A payload like 00:00:01</time><img src=x onerror=alert(1)><time> passes validation.
No sanitization in output function: objects/video.php:3463-3480
public static function getCleanDuration($duration = "") {
$durationParts = explode(".", $duration);
$duration = $durationParts[0];
$durationParts = explode(':', $duration);
if (count($durationParts) == 1) {
return '0:00:' . static::addZero($durationParts[0]);
} elseif (count($durationParts) == 2) {
return '0:' . static::addZero($durationParts[0]) . ':' . static::addZero($durationParts[1]);
}
return $duration; // Returns full string unmodified for 3+ colon parts
}
With the payload 00:00:01</time><img src=x onerror=alert(1)><time>, exploding by : yields 3+ parts, so the full unsanitized string is returned.
Unescaped output sinks:
view/trending.php:72:
<time class="duration"><?php echo Video::getCleanDuration($value['duration']); ?></time>
view/include/playlist.php:159:
<time class="duration"><?php echo Video::getCleanDuration(@$value['duration']); ?></time>
objects/video.php:7200(gallery thumbnail generation):
$img .= "<time class=\"duration\"...>" . $duration . "</time>";
No Content-Security-Policy headers are set. The application uses raw PHP templates with no auto-escaping framework.
PoC
-
Authenticate as a user with upload permission and obtain a
video_id_hashfor a video (visible in encoder API responses or via the upload flow). -
Send the malicious duration:
curl -X POST "https://target/objects/aVideoEncoderReceiveImage.json.php" \
-d "videos_id=VIDEO_ID" \
-d "video_id_hash=HASH" \
-d 'duration=00:00:01</time><img src=x onerror=alert(document.cookie)><time>'
-
The
isValidDuration()regex matches the00:00:01prefix and allows the full string to be stored. -
Visit the trending page (
/view/trending.php) or any playlist containing the poisoned video. The injected HTML breaks out of the<time>tag and theonerrorhandler executes JavaScript in the victim's browser context.
Impact
- Session hijacking: Attacker can steal session cookies of any user (including administrators) who views a page listing the poisoned video (trending, playlists, search results, channel pages).
- Account takeover: Stolen admin session cookies grant full platform control.
- Phishing: Attacker can inject fake login forms or redirect users to malicious sites.
- Worm potential: Since the XSS fires on commonly-visited listing pages (trending), it can propagate without targeted delivery — any visitor is a victim.
The attack requires only upload-level permissions (low privilege) and impacts all users who view any page rendering the poisoned video's duration (high blast radius).
Recommended Fix
Fix 1 — Anchor the regex (objects/video.php:918):
- return preg_match('/^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}/', $duration);
+ return preg_match('/^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}(\.[0-9]+)?$/', $duration);
Fix 2 — HTML-escape all duration output (defense in depth):
In view/trending.php:72:
- <time class="duration"><?php echo Video::getCleanDuration($value['duration']); ?></time>
+ <time class="duration"><?php echo htmlspecialchars(Video::getCleanDuration($value['duration']), ENT_QUOTES, 'UTF-8'); ?></time>
In view/include/playlist.php:159:
- <time class="duration"><?php echo Video::getCleanDuration(@$value['duration']); ?></time>
+ <time class="duration"><?php echo htmlspecialchars(Video::getCleanDuration(@$value['duration']), ENT_QUOTES, 'UTF-8'); ?></time>
In objects/video.php:7200:
- $img .= "<time class=\"duration\"...>" . $duration . "</time>";
+ $img .= "<time class=\"duration\"...>" . htmlspecialchars($duration, ENT_QUOTES, 'UTF-8') . "</time>";
Both fixes should be applied: the regex fix prevents storage of invalid data, and the output escaping provides defense in depth against any other code path that might store unvalidated durations.
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, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Remediation status
No patched version of wwbn/avideo has shipped for CVE-2026-41061 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 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-41061 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-41061. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-41061 in your dependencies?
O3 Security finds CVE-2026-41061 across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.