Medium

WWBN AVideo has Stored XSS via Unanchored Duration Regex in Video Encoder Receiver

  • Disclosed: 14.04.2026
  • Updated: 11.10.2026 10:22

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

php
// Line 203-211
if (!empty($_REQUEST['duration'])) {
    $video->setDuration($_REQUEST['duration']);
}

Insufficient validation: objects/video.php:918

php
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

php
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:

  1. view/trending.php:72:
php
<time class="duration"><?php echo Video::getCleanDuration($value['duration']); ?></time>
  1. view/include/playlist.php:159:
php
<time class="duration"><?php echo Video::getCleanDuration(@$value['duration']); ?></time>
  1. objects/video.php:7200 (gallery thumbnail generation):
php
$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

  1. Authenticate as a user with upload permission and obtain a video_id_hash for a video (visible in encoder API responses or via the upload flow).

  2. Send the malicious duration:

bash
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>'
  1. The isValidDuration() regex matches the 00:00:01 prefix and allows the full string to be stored.

  2. Visit the trending page (/view/trending.php) or any playlist containing the poisoned video. The injected HTML breaks out of the <time> tag and the onerror handler 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):

php
- 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:

php
- <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:

php
- <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:

php
- $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 software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Composer package wwbn/avideo <= 29.0 no fix

References

CVSS score 5.4/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
Weakness (CWE)
CWE-79
Platform
Composer
Package
wwbn/avideo
Disclosed
14.04.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-8pv3-29pp-pf8f - CC BY 4.0

Vulnerability history: wwbn/avideo

All published vulnerabilities of this package (Composer package), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (160) in the archive
Total entries
160
First → latest
04.2022 06.2026
Without a fix
143
Exploited in attacks
0

Severity breakdown

  • Critical 14
  • High 61
  • Medium 83
  • Low 2

Disclosures per year

  1. 2022: 2
  2. 2023: 5
  3. 2024: 5
  4. 2025: 0
  5. 2026: 148
Severity:
  1. High · 9.8 CVE-2026-41304 <= 29.0 no fix
  2. WWBN AVideo has Stored XSS via Unanchored Duration Regex in Video Encoder Receiver this entry
    Medium · 5.4 CVE-2026-41061 <= 29.0 no fix
Show more (20) Showing 20 of 76

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.