GitHub Security Advisory: GHSA-g3hj-mf85-679g
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 5.4)
CWE: CWE-862: Missing Authorization
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: AVideo: IDOR in uploadPoster.php Allows Any Authenticated User to Overwrite Scheduled Live Stream Posters and Trigger False Socket Notifications

Details:
## Summary

The `plugin/Live/uploadPoster.php` endpoint allows any authenticated user to overwrite the poster image for any scheduled live stream by supplying an arbitrary `live_schedule_id`. The endpoint only checks `User::isLogged()` but never verifies that the authenticated user owns the targeted schedule. After overwriting the poster, the endpoint broadcasts a `socketLiveOFFCallback` notification containing the victim's broadcast key and user ID to all connected WebSocket clients.

## Details

The vulnerable endpoint at `plugin/Live/uploadPoster.php` accepts a `live_schedule_id` from `$_REQUEST` and uses it to determine poster file paths and trigger socket notifications without ownership validation.

**Entry point — attacker-controlled input (line 11-12):**
```php
$live_servers_id = intval($_REQUEST['live_servers_id']);
$live_schedule_id = intval($_REQUEST['live_schedule_id']);
```

**Insufficient auth check (line 14-17):**
```php
if (!User::isLogged()) {
    $obj->msg = 'You cant edit this file';
    die(json_encode($obj));
}
```

This only verifies the user is logged in. There is no check that `User::getId()` matches the schedule owner's `users_id`.

**Poster path resolved by ID alone (line 40-42):**
```php
$paths = Live_schedule::getPosterPaths($live_schedule_id, 0);
$obj->file = str_replace($global['systemRootPath'], '', $paths['path']);
$obj->fileThumbs = str_replace($global['systemRootPath'], '', $paths['path_thumbs']);
```

`getPosterPaths()` is a static method that constructs file paths purely from the numeric ID with no authorization.

**Attacker's file overwrites victim's poster (line 48):**
```php
if (!move_uploaded_file($_FILES['file_data']['tmp_name'], $tmpDestination)) {
```

**Broadcast to all WebSocket clients (line 67-73):**
```php
if (!empty($live_schedule_id)) {
    $ls = new Live_schedule($live_schedule_id);
    $array = setLiveKey($ls->getKey(), $ls->getLive_servers_id());
    $array['users_id'] = $ls->getUsers_id();
    $array['stats'] = getStatsNotifications(true);
    Live::notifySocketStats("socketLiveOFFCallback", $array);
}
```

The `Live_schedule` constructor (inherited from `ObjectYPT`) loads data by ID with no auth checks. `Live::notifySocketStats()` calls `sendSocketMessageToAll()` which broadcasts to every connected WebSocket client.

**Notably, the parallel endpoints DO have ownership checks:**
- `plugin/Live/view/Live_schedule/uploadPoster.php` (line 18-21) checks `$row->getUsers_id() != User::getId()`
- `plugin/Live/uploadPoster.json.php` (line 24-27) checks `User::isAdmin() || $row->getUsers_id() == User::getId()`

This proves the missing check in `uploadPoster.php` is an oversight, not by-design.

## PoC

```bash
# Step 1: Log in as a low-privilege user to get a session cookie
curl -c cookies.txt -X POST 'https://target.com/objects/login.json.php' \
  -d 'user=attacker@example.com&pass=attackerpassword'

# Step 2: Overwrite the poster for live_schedule_id=1 (owned by a different user)
curl -b cookies.txt \