Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
5.4
WWBN AVideo stores harmful code in video durations
GHSA-8pv3-29pp-pf8f
Summary
A weakness in WWBN AVideo's video duration handling allows attackers to inject malicious code into the video database. This can lead to unauthorized actions when users view videos with crafted durations. To fix this issue, update WWBN AVideo to properly validate and sanitize video durations to prevent malicious code injection.
What to do
No fix is available yet. Check with your software vendor for updates.
Affected software
| Ecosystem | Vendor | Product | Affected versions |
|---|---|---|---|
| Packagist | wwbn | wwbn/avideo | <= 29.0 |
Original title
WWBN AVideo has Stored XSS via Unanchored Duration Regex in Video Encoder Receiver
Original 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`
```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>
```
2. `view/include/playlist.php:159`:
```php
<time class="duration"><?php echo Video::getCleanDuration(@$value['duration']); ?></time>
```
3. `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>'
```
3. The `isValidDuration()` regex matches the `00:00:01` prefix and allows the full string to be stored.
4. 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.
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>
```
2. `view/include/playlist.php:159`:
```php
<time class="duration"><?php echo Video::getCleanDuration(@$value['duration']); ?></time>
```
3. `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>'
```
3. The `isValidDuration()` regex matches the `00:00:01` prefix and allows the full string to be stored.
4. 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.
osv CVSS3.1
5.4
Vulnerability type
CWE-79
Cross-site Scripting (XSS)
Published: 14 Apr 2026 · Updated: 15 Apr 2026 · First seen: 15 Apr 2026