Skip to content

Unsafe Reflection in dashboard widget `data_source` discloses all password hashes

High
bertugfahriozer published GHSA-3jfm-927m-q5rg Jul 26, 2026

Package

composer ci4-cms-erp/ci4ms (Composer)

Affected versions

<= 0.32.0.0

Patched versions

0.33.1.0

Description

Summary

The dashboard-widget feature stores a user-supplied data_source string and later executes it as new $class() followed by $obj->$method(), splitting the value on ::. The field is persisted from the request with no validation, so a low-privileged backend user who can manage widgets controls both the class that is instantiated and the (zero-argument) method that is invoked, and the return value is rendered back to them.

A user whose only relevant grants are the dashboard-widget permissions (and who has no permission on the Users module) can set data_source to \CodeIgniter\Shield\Models\UserIdentityModel::findAll and read every account's login e-mail and bcrypt password hash, including the super-admin. The hashes can be cracked offline to take over any account.

Details

The vulnerable code is resolveDataSource() in WidgetService.php. It splits the stored Data Source on "::", then instantiates the class and calls the method with no allow-list:

{5F3E8911-8A8D-48C7-A36D-357CBD0DBBFE}

The value comes from the widget create/update form. The controller only trims it and never validates it (only slug and title are validated), so the attacker controls both the class and the method.

It is triggered by the read-only endpoint backend/dashboard-widgets/data/{slug}, which returns the result as JSON. As long as the widget slug is not one of the built-in names (total-users, recent-activity, and so on), this endpoint runs the attacker's class and method.

A CodeIgniter model needs no constructor arguments and findAll() takes none, so the value below fits the pattern and returns the whole identities table:

\CodeIgniter\Shield\Models\UserIdentityModel::findAll

Because the endpoint only checks the dashboard-widget read permission, a user with no access to the Users module still reads every hash. Verified on the running app: the test user can read widget data but cannot read the Users module.

PoC

Actor: a non-admin user whose group grants only the dashboard-widget permissions and has no Users-module access.

  1. Log in to the backend as the low-privileged widget user.

  2. Create a widget with:

    • Slug: users-leak
    • Title: ALLUSERS
    • Type: table
    • Data Source: \CodeIgniter\Shield\Models\UserIdentityModel::findAll

    Save.

image
  1. Trigger the widget and read the response:

    GET http://localhost:8080/backend/dashboard-widgets/data/users-leak
    
image

Impact

Any authenticated backend user with the dashboard-widget create/read permissions. No admin/super-admin rights, and crucially no Users-module permission, are required.
Disclosure of every user's login e-mail and bcrypt password hash, including the super-admin. This is a cross-module credential-disclosure issue that enables offline cracking and full account takeover.

Fix

Never derive a class/method to instantiate or call from user input. Resolve data_source through a fixed allow-list of approved providers:

/** Approved custom data providers: data_source value => [Class, method]. */
private const ALLOWED_DATA_SOURCES = [
    // 'sales.daily' => [\App\Widgets\SalesProvider::class, 'daily'],
];

protected function resolveDataSource(string $source): array
{
    if (!isset(self::ALLOWED_DATA_SOURCES[$source])) {
        return ['value' => '', 'label' => 'Unknown source'];
    }
    [$class, $method] = self::ALLOWED_DATA_SOURCES[$source];
    return (array) (new $class())->{$method}();
}

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE ID

CVE-2026-64689

Weaknesses

Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')

The product uses external input with reflection to select which classes or code to use, but it does not sufficiently prevent the input from selecting improper classes or code. Learn more on MITRE.

Credits