GitHub Security Advisory: GHSA-g39v-qrj6-jxrh
Package: wwbn/avideo (COMPOSER)
Severity: MODERATE (CVSS 4.3)
CWE: CWE-639: Authorization Bypass Through User-Controlled Key
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: AVideo: IDOR in AI Plugin Allows Stealing Other Users' AI-Generated Metadata and Transcriptions

Details:
## Summary

The AI plugin's `save.json.php` endpoint loads AI response objects using an attacker-controlled `$_REQUEST['id']` parameter without validating that the AI response belongs to the specified video. An authenticated user with AI permissions can reference any AI response ID — including those generated for other users' private videos — and apply the stolen AI-generated content (titles, descriptions, keywords, summaries, or full transcriptions) to their own video, effectively exfiltrating the information.

## Details

In `plugin/AI/save.json.php`, the authorization flow checks that the user can edit the *target video* (`Video::canEdit($videos_id)` at line 23), but loads the AI response object from a completely separate, user-controlled parameter:

**Line 29 — metatags path (no ownership check):**
```php
if(!empty($_REQUEST['ai_metatags_responses_id'])){
    $ai = new Ai_metatags_responses($_REQUEST['id']);  // Loads ANY response by ID
    
    if (empty($ai->getcompletion_tokens())) {
        forbiddenPage('AI Response not found');
    }
}
```

**Line 146 — transcription path (no ownership check):**
```php
case 'text':
    if(!empty($_REQUEST['ai_transcribe_responses_id'])){
        $ait = new Ai_transcribe_responses($_REQUEST['id']);  // Loads ANY response by ID
        $value = $ait->getVtt();
```

The `ObjectYPT` base class constructor performs a simple database lookup with no authorization:
```php
public function __construct($id = "", $refreshCache = false) {
    if (!empty($id)) {
        $this->load($id, $refreshCache);  // SELECT * WHERE id = ? — no permission check
    }
}
```

The loaded data is then applied to the attacker's video — titles via `$video->setTitle()` (line 49-51), descriptions via `$video->setDescription()` (lines 91-92, 100-101), and transcriptions via `file_put_contents()` (line 156).

In contrast, `plugin/AI/delete.json.php` correctly validates ownership by traversing to the parent `Ai_responses` record:

```php
// delete.json.php lines 42-44 — CORRECT ownership check
$ai = new Ai_responses($aitr->getAi_responses_id());
if ($ai->getVideos_id() == $videos_id) {
    $obj->ai_transcribe_responses_id = $aitr->delete();
```

This proves the developers intended ownership validation but omitted it in the save endpoint.

## PoC

**Prerequisites:** Two user accounts (attacker and victim), both with `canUseAI` permission. The victim has generated AI metadata or transcription for a private video.

**Step 1: Attacker enumerates AI response IDs to steal metadata**

AI response IDs are sequential integers. The attacker supplies their own `videos_id` (which they can edit) but references a victim's AI response `id`:

```bash
# Attacker owns video ID 5, victim's AI metatags response is ID 42
curl -b "attacker_cookies" \
  "https://target.example/plugin/AI/save.json.php" \
  -d "videos_id=5&ai_metatags_responses_id=1&id=42&label=videoTitles&index=0"
```

**Expected result:** The victim's AI-generated title (from their private video