GitHub Advisoryadvisory · Packagist

GHSA-qxvm-r42f-5p8j AVideo's Meet plugin: `uploadRecordedVideo.json.php` derives `users_id` from the uploaded filename and calls passwordless `User->login()`, allowing any caller with the Meet shared secret to obtain a session as arbitrary users including admin

HIGH Published 2026-05-15 Modified 2026-05-15 Official page ↗
Ecosystem
Packagist
Packages
WWBN/AVideo
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-1390, CWE-287, CWE-639
Reviewed
2026-05-15

Affected packages

• Packagist WWBN/AVideo: <= 29.0 (no fix yet)

Description

Summary Type: Authorization-bypass via user-controlled identifier. The Meet plugin's recorded-video upload endpoint (`plugin/Meet/uploadRecordedVideo.json.php`) authenticates the caller using a single shared `Authorization: Bearer ` against `$objM->secret`. Once that check passes, the endpoint reads the *target user identifier* from the uploaded file's `name` field, instantiates a `User` object with that ID, and calls `$userObject->login(true, true)` — the no-password / encoded-password login path — committing a session for that user and emitting `Set-Cookie` headers to the caller. There is no check that the caller actually owns the requested `users_id`. File: `plugin/Meet/uploadRecordedVideo.json.php`, lines 56-65; secondary in `objects/user.php` `User::login()` (no-password branch at lines 1276-1310). Root cause: the upload handler's identity model is "service-to-service" (a Meet/Jitsi recorder posts a finished recording back to AVideo with the shared secret) but the `users_id` to credit the upload to is parsed from the FILENAME the same caller controls — `$users_id = explode('-', $_FILES['upl']['name'])[0];`. There is no signed claim, no separate proof-of-identity, no allowlist. The subsequent `$userObject->login(true, true)` call invokes the no-password login path which sets `$_SESSION['user']`, calls `setUserCookie(...)`, and `_session_regenerate_id()` — exactly the operations a normal login performs. The response carries the new `PHPSESSID` back to the caller, who can then reuse it on every subsequent request to act as the targeted user. The Meet shared secret is `md5($global['systemRootPath'] . $global['salt'] . "meet")` (`Meet.php:73`), so any attacker who can read `videos/configuration.php` (e.g., via a path-traversal CVE such as `GHSA-83xq-8jxj-4rxm` or `GHSA-4wmm-6qxj-fpj4` that the project has already addressed in this surface area) can compute the Meet secret deterministically and pivot to full account takeover. Affected Code File: `plugin/Meet/uploadRecordedVideo.json.php`, lines 33-73. ```php if (empty($token)) { forbiddenPage('Token not found'); } $objM = AVideoPlugin::getObjectDataIfEnabled("Meet"); if (empty($objM)) { forbiddenPage('Plugin disabled'); } if ($objM->secret != $token) { // login(true, true); // find($this->user, false, true); // setLastLogin($_SESSION['user']['id']); // ... self::setUserCookie($rememberme, $user['id'], $user['user'], $passhash, $expires); AVideoPlugin::onUserSignIn($_SESSION['user']['id']); $_SESSION['loginAttempts'] = 0; _session_regenerate_id(); // login(true, true)` runs, the server has no way to distinguish a legitimate Meet integration from an attacker who happens to know the same secret. The decision to expose this as a session (cookie + `_session_regenerate_id`) rather than as a one-shot in-process credit makes the impact larger than it needs to be: even if the Meet integration only needed to *credit* the recording to a user, the implementation gives the caller a fully-authenticated session as that user. Exploit Chain 1. Attacker obtains the Meet shared secret. Two plausible paths: • Path A (computational): the secret is `md5($global['systemRootPath'] . $global['salt'] . "meet")` (`plugin/Meet/Meet.php:73`). Both inputs sit in `videos/configuration.php`. AVideo's history of LFI/path-traversal CVEs in this surface (e.g., the `import.json.php` and `listFiles.json.php` advisories already accepted on this program) means the salt is a realistic disclosure target. • Path B (timing oracle): `plugin/Meet/checkToken.json.php` line 26 does `if ($objM->secret === $_GET['secret'])` with no constant-time comparison and a clear yes/no response body. PHP's `===` for strings short-circuits on first byte mismatch, so an attacker on the same network segment can recover the 32-hex secret byte-by-byte over the network with timing analysis. Slower than path A but doesn't depend on a separate vulnerability. 2. Attacker prepares an HTTP POST to `/plugin/Meet/uploadRecordedVideo.json.php`: • `Authorization: Bearer ` • Multipart body with one file field named `upl`. The filename is set to `1-anything.mp4` (where `1` is the `users_id` of the admin or any target user — the format is `-`). The file body itself can be anything that survives the surrounding aVideoQueueEncoder pipeline (an empty file is enough to reach the login call before the encoder rejects). 3. Server flow: • Line 33: token present, ok. • Line 46: `$objM->secret != $token` → false (matches), passes. • Line 51: `$_FILES['upl']` present, ok. • Line 56: `$users_id = explode('-', '1-anything.mp4')[0]` → `'1'`. • Line 59-60: `$userObject = new User(1); $userObject->login(true, true);` — passwordless login as user 1 (admin). `$_SESSION['user']` is set, `setUserCookie` runs, `_session_regenerate_id` issues a new session ID, and the response carries `Set-Cookie: PHPSESSID=; ...`. • Subsequent code runs the encoder pipeline as admin — but the attacker's primary goal was already achieved when the session was established. 4. Attacker captures the `Set-Cookie: PHPSESSID=...` header from the response and uses that cookie on all subsequent requests. Server treats them as user 1 (admin) — full UI access, all admin endpoints, all video management, plugin configuration, user impersonation, etc. 5. Final state: admin account takeover. The original Meet recorder's flow (legitimate uploads with `users_id` = the user who scheduled the meeting) is indistinguishable on the wire from the attack flow (`users_id` = whoever the attacker wants to be). Security Impact Severity: sec-high. End state is full account takeover of any user (including admin), reachable from a single HTTP POST once the secret is known. The shared-secret precondition raises AC to High but does not eliminate it as a credible threat — the secret is computable from any leak of `videos/configuration.php`, and AVideo's CVE history in that surface area is non-trivial. Attacker capability: session hijack as any `users_id` the attacker cares to name. The attacker chooses the target by setting the filename's leading digits before the first `-`. No bound on which user IDs are reachable; admin (`1` on a default install) is the obvious target. Once the session is captured, the atta…

Related

Weakness · CWE (3)

  • CWE-1390 Weak Authentication weakness
  • CWE-287 Improper Authentication weakness
  • CWE-639 Authorization Bypass Through User-Controlled Key weakness

References