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_hash
when users set their password.compat_password_hash
usepassword_hash
if there is it, else usemd5
.When verifying password, it calls
compat_password_verify
.In
compat_password_verify
,password_verify
is called if there is it, else usemd5
.password_verify
andpassword_hash
are spported on PHP < 5.5.0, following PHP manual:So
md5
is 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: