PT-2026-37291 · Packagist · Wwbn Avideo
Published
2026-05-05
·
Updated
2026-05-05
·
CVE-2026-43875
CVSS v3.1
6.8
Medium
| Vector | AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N |
Summary
plugin/MobileManager/oauth2.php completes an OAuth login by sending an HTTP 302 Location: oauth2Success.php?user=<email>&pass=<HASH> where <HASH> is the victim's stored password hash (md5(hash("whirlpool", sha1(password)))) read directly from the users table. AVideo's own login endpoint (objects/login.json.php) accepts an encodedPass=1 flag that bypasses hashing and performs a direct string comparison between the supplied value and the stored hash. Anyone who captures the redirect URL — via server logs, referrer leakage, or browser history — therefore obtains a credential equivalent to the plaintext password and can fully take over the account, including admin accounts.Details
Sink: hash inlined in a GET redirect
plugin/MobileManager/oauth2.php:98-102:$pass = rand();
$users id = User::createUserIfNotExists($user, $pass, $name, $email, $photoURL);
$adapter->disconnect();
$userObject = new User($users id);
header("Location: oauth2Success.php?user=" . $userObject->getUser() . "&pass=" . $userObject->getPassword());
$userObject->getPassword() returns the raw database column (objects/user.php:159-162):public function getPassword()
{
return strip tags($this->password);
}
The returned value is the stored password hash for the account (existing or freshly-created). It is transported to the browser as a query-string parameter in the
Location: header, so it is written to:- Web-server access logs (
combined/mainlog formats record the full request line including query string). - Upstream proxy / CDN / WAF logs.
- Any error monitoring / APM that captures request URLs (Sentry, Datadog, New Relic defaults).
- The victim's browser history (persistent local artifact).
- The
Refererheader on subsequent navigation from the renderedoauth2Success.phppage if the page or its assets load any external origin and the browser'sReferrer-Policyis not strict.
Hash equals plaintext for login
objects/login.json.php:182-209:if (!empty($ GET['user'])) {
$ POST['user'] = $ GET['user'];
}
if (!empty($ GET['pass'])) {
$ POST['pass'] = $ GET['pass'];
}
if (!empty($ GET['encodedPass'])) {
$ POST['encodedPass'] = $ GET['encodedPass'];
}
...
$user = new User(0, $ POST['user'], $ POST['pass']);
...
$resp = $user->login(false, @$ POST['encodedPass']);
objects/user.php:1272-1279 passes $encodedPass to find():if (strtolower($encodedPass) === 'false') {
$encodedPass = false;
}
...
$user = $this->find($this->user, $this->password, true, $encodedPass);
objects/user.php:1785-1794:if ($pass !== false) {
if (!encryptPasswordVerify($pass, $result['password'], $encodedPass)) {
...
return false;
}
}
objects/functions.php:2312-2331:function encryptPasswordVerify(#[SensitiveParameter] $password, $hash, $encodedPass = false)
{
global $advancedCustom, $global;
if (!$encodedPass || $encodedPass === 'false') {
$passwordSalted = encryptPassword($password);
$passwordUnSalted = encryptPassword($password, true);
} else {
$passwordSalted = $password; // <- direct use, no hashing
$passwordUnSalted = $password;
}
$isValid = $passwordSalted === $hash || $passwordUnSalted === $hash;
...
}
When
encodedPass is truthy, the supplied value is compared as-is against the stored hash. The captured redirect parameter pass=<HASH> is therefore a valid login credential when replayed with encodedPass=1.Compounding factors
- The redirect is a raw
Location:(GET), not a POST — the secret is placed in a URL which is by definition non-confidential transport. - No CSRF token, no
stateparameter tied to the session, and no single-use token is used on/plugin/MobileManager/oauth2.php. login.json.phpdoes not require a CSRF token or captcha on the first attempt (checkLoginAttempts()atobjects/user.php:1282only rate-limits after failures, and the attacker succeeds on the first try).- By contrast, the non-plugin flow in
objects/login.json.php:144-145already sets session state server-side ($userObject->login(true)), demonstrating the project already has a safer pattern available.
PoC
Prerequisites:
MobileManager plugin enabled and at least one supported login provider (e.g. LoginGoogle) configured with valid keys — both are common production settings for this product.- Victim initiates the mobile OAuth flow:
GET /plugin/MobileManager/oauth2.php?type=Google
- After the victim authorizes at the provider, the server sends:
HTTP/1.1 302 Found
Location: oauth2Success.php?user=victim%40example.com&pass=9d7ab4...stored-hash...
This request-line — including the password hash — is written to the web server's access log (default
combined format) and to any upstream proxy/CDN log. It also appears in the victim's browser history.-
Attacker obtains
<HASH>from any of those channels. -
Attacker logs in as the victim without knowing the plaintext password:
curl -i -c cookies.txt
'https://target.example.com/objects/login.json.php?user=victim@example.com&pass=<HASH>&encodedPass=1'
Expected response:
200 OK with JSON containing id, user, PHPSESSID, isAdmin, email, and a Set-Cookie: PHPSESSID=... that grants full account access. The attacker can now browse, upload, modify the victim's channel, or — if the victim is an admin — access /mvideos and all admin endpoints.Impact
- Full account takeover of any user who has ever logged in through the MobileManager OAuth endpoint.
- If the victim is an administrator, the attacker gains administrative control of the AVideo instance (user management, plugin config, site-wide content).
- The exposed hash works indefinitely: it remains valid for as long as the victim does not change their password, so a one-time log/history/referrer capture yields a persistent credential.
- Passes silently — from the application's perspective, the attacker is just a legitimate login with
encodedPass=1(a flag the product itself uses for mobile-app "remember me" flows).
Recommended Fix
- Never place the password hash (or any credential-equivalent material) in a URL. In
plugin/MobileManager/oauth2.php, mirror whatobjects/login.json.php:143-146already does for the web flow — establish the session server-side and redirect to a URL with no credentials:
$userObject = new User(0, $user, $pass);
$userObject->login(true); // server-side session
header("Location: oauth2Success.php");
-
Additionally, remove or hard-restrict the
encodedPassbranch inobjects/functions.php:2319-2329. If a "hash-equivalent" credential must exist for the mobile app, replace it with a short-lived, single-use, server-issued bearer token bound to the session, rather than the persistent database hash. -
Add a
stateparameter and CSRF protection on/plugin/MobileManager/oauth2.phpso the redirect cannot be initiated from a third-party origin. -
For defense-in-depth, strip query strings containing
pass=from access-log formats and ensureoauth2Success.phpsetsReferrer-Policy: no-referrerwhile it is being deprecated.
Fix
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Wwbn Avideo