Skip to content

Authentication bypass when using older password hashes

Moderate
netniV published GHSA-37x7-mfjv-mm7m May 12, 2024

Package

Cacti (PHP)

Affected versions

<= 1.2.26

Patched versions

1.2.27

Description

Vulnerability Details

Authentication

Cacti calls compat_password_hash when users set their password.

/**
 * compat_password_hash - if the secure function exists, hash using that.
 *   If that does not exist, hash older md5 function instead
 *
 * @param  (string) $password - password to hash
 * @param  (string) $algo     - algorithm to use (PASSWORD_DEFAULT)
 *
 * @return (bool)   true if password hash matches, false otherwise
 */
function compat_password_hash($password, $algo, $options = array()) {
        if (function_exists('password_hash')) {
                // Check if options array has anything, only pass when required
                return (cacti_sizeof($options) > 0) ?
                        password_hash($password, $algo, $options) :
                        password_hash($password, $algo);
        }

        return md5($password);
}

compat_password_hash use password_hash if there is it, else use md5.

When verifying password, it calls compat_password_verify.

/**
 * compat_password_verify - if the secure function exists, verify against that
 *   first.  If that checks fails or does not exist, check against older md5
 *   version
 *
 * @param  (string) $password - password to verify
 * @param  (string) $hash     - current password hash
 *
 * @return (bool)   true if password hash matches, false otherwise
 */
function compat_password_verify($password, $hash) {
		if (function_exists('password_verify')) {
                if (password_verify($password, $hash)) {
                        return true;
                }
		}

        $md5 = md5($password);

        return ($md5 == $hash);
}

In compat_password_verify, password_verify is called if there is it, else use md5.

password_verify and password_hash are spported on PHP < 5.5.0, following PHP manual:

(PHP 5 >= 5.5.0, PHP 7, PHP 8)
password_hash — Creates a password hash

(PHP 5 >= 5.5.0, PHP 7, PHP 8)
password_verify — Verifies that a password matches a hash

So md5 is used for authentication in Cacti running on PHP < 5.5.0.

Type Juggling

The vulnerability is in compat_password_verify.

/**
 * compat_password_verify - if the secure function exists, verify against that
 *   first.  If that checks fails or does not exist, check against older md5
 *   version
 *
 * @param  (string) $password - password to verify
 * @param  (string) $hash     - current password hash
 *
 * @return (bool)   true if password hash matches, false otherwise
 */
function compat_password_verify($password, $hash) {
		if (function_exists('password_verify')) {
                if (password_verify($password, $hash)) {
                        return true;
                }
		}

        $md5 = md5($password);

        return ($md5 == $hash);
}

Md5-hashed user input is compared with correct password in database by $md5 == $hash. It is a loose comparison, not ===. It is a type juggling vulnerability.

Proof of Concept

There are passwords of admin and guest in database, which are each md5("helloAXTKLSjy") and md5("RSnakehiQ1fICqo6LA"), and the both start with "0e", which PHP recognize as 0 when loosely comparing.

> select * from user_auth;
+----+----------+----------------------------------+-------+---------------+---------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
| id | username | password                         | realm | full_name     | email_address | must_change_password | password_change | show_tree | show_list | show_preview | graph_settings | login_opts | policy_graphs | policy_trees | policy_hosts | policy_graph_templates | enabled | lastchange | lastlogin | password_history | locked | failed_attempts | lastfail | reset_perms |
+----+----------+----------------------------------+-------+---------------+---------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
|  1 | admin    | 0e052539892259114859640052326948 |     0 | Administrator |               |                      | on              | on        | on        | on           | on             |          2 |             1 |            1 |            1 |                      1 | on      |         -1 |        -1 | -1               |        |               0 |        0 |    65181430 |
|  3 | guest    | 0e453499463434434754387288377524 |     0 | Guest Account |               |                      | on              | on        | on        | on           |                |          1 |             1 |            1 |            1 |                      1 | on      |         -1 |        -1 | -1               |        |               0 |        0 |  1458272138 |
+----+----------+----------------------------------+-------+---------------+---------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
2 rows in set (0.000 sec)

Their correct passwords are "helloAXTKLSjy" and "RSnakehiQ1fICqo6LA", but authentication is bypassed by entering "240610708".

Some md5 magic hashes here:

240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361
IHKFRNS:0e256160682445802696926137988570
GZECLQZ:0e537612333747236407713628225676
GGHMVOE:0e362766013028313274586933780773

Severity

Moderate

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
High
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None

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:H/PR:L/UI:N/S:U/C:L/I:L/A:N

CVE ID

CVE-2024-34340

Weaknesses

Credits