GitHub Security Advisory: GHSA-wxjw-phj6-g75w
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 8.8)
CWE: CWE-434: Unrestricted Upload of File with Dangerous Type
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload

Details:
## Summary

The `ImageGallery::saveFile()` method validates uploaded file content using `finfo` MIME type detection but derives the saved filename extension from the user-supplied original filename without an allowlist check. An attacker can upload a polyglot file (valid JPEG magic bytes followed by PHP code) with a `.php` extension. The MIME check passes, but the file is saved as an executable `.php` file in a web-accessible directory, achieving Remote Code Execution.

## Details

The vulnerability exists in `plugin/ImageGallery/ImageGallery.php` in the `saveFile()` method:

```php
// plugin/ImageGallery/ImageGallery.php:80-108
static function saveFile($file, $videos_id)
{
    $allowedMimeTypes = ['image/jpeg', 'image/webp', 'image/gif', 'image/png', 'video/mp4'];
    $directory = self::getImageDir($videos_id);

    // MIME check on file CONTENT — bypassable with polyglot
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $fileType = $finfo->file($file['tmp_name']);

    if (in_array($fileType, $allowedMimeTypes)) {
        // Extension from attacker-controlled filename — NO allowlist
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        do {
            $newFilename = uniqid() . '.' . $extension;
            $newFilePath = $directory . $newFilename;
        } while (file_exists($newFilePath));

        move_uploaded_file($file['tmp_name'], $newFilePath);
        // ...
    }
}
```

**Root cause:** Line 93 extracts the extension from the user-supplied `$file['name']` and uses it directly in the saved filename. There is no check against an allowlist of safe extensions (e.g., `jpg`, `png`, `gif`, `webp`, `mp4`).

**Why the MIME check is insufficient:** PHP's `finfo` with `FILEINFO_MIME_TYPE` inspects file content magic bytes. A file starting with JPEG magic bytes (`\xff\xd8\xff\xe0`) is identified as `image/jpeg` regardless of trailing content. Appending PHP code after the JPEG header creates a polyglot that passes the MIME check but executes as PHP when requested via the web server.

**Why no server-level protection exists:** The root `.htaccess` at line 73 blocks dangerous extensions but uses the pattern `php[a-z0-9]+` — which matches `.php5`, `.phtml`, `.phar`, etc., but intentionally does **not** match plain `.php` (since the application itself requires PHP execution). There is no `.htaccess` in the `videos/` directory to disable PHP execution in the upload target.

**Upload path:** Files are saved to `videos/{videoFilename}/ImageGallery/{uniqid}.php` — directly accessible via the web server.

The upload endpoint at `plugin/ImageGallery/upload.json.php` requires:
1. The ImageGallery plugin to be enabled (line 6-8)
2. An authenticated user (line 10-12)
3. The user must have manage permission on the video (line 18-20) — video owner or admin

The response at line 27 calls `listFiles()` which returns the full URL of each uploaded file, giving the attacker the exact path to their webshell.

## PoC

**Prerequisites:** Au