From 63b9355b70878663d48833492dcea2b8dd524616 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Sun, 16 Mar 2025 12:57:56 +0100 Subject: [PATCH 1/4] support plaintext passwords Shouldn't be encouraged, but sometimes you actually do want ```php $auth_users = array( 'admin' => 'admin@123' ); ``` --- tinyfilemanager.php | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index d1848d9..9be4acf 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -325,13 +325,49 @@ if ($ip_ruleset != 'OFF') { // Checking if the user is logged in or not. If not, it will show the login form. if ($use_auth) { + function tfm_password_verify( + #[\SensitiveParameter] + string $password, + string $hash + ): bool { + $str_starts_with = function (string $haystack, string $needle): bool { + if (PHP_MAJOR_VERSION >= 8) { + return str_starts_with($haystack, $needle); + } + return strlen($needle) <= strlen($haystack) && 0 === substr_compare($haystack, $needle, 0); + }; + $needles = array( + '$1$', // CRYPT_MD5, + '$2a$', // CRYPT_BLOWFISH + '$2x$', // CRYPT_BLOWFISH + '$2y$', // CRYPT_BLOWFISH + '$5$', // CRYPT_SHA256 + '$6$', // CRYPT_SHA512 + ); + if (strlen($hash) >= 26) { // CRYPT_MD5, the weakest supported algo, yield length 26 hash. (DES is not supported.) + foreach ($needles as $needle) { + if ($str_starts_with($hash, $needle)) { + // it's a crypt() hash. + return password_verify($password, $hash); + } + } + } + // plaintext password. + // We still hash both inputs and use hash_equals() to protect against timing-attack-password-extraction. + // hash_equals() is only constant-time when both inputs have the same length. + // md5 is used as a compression function. + $h1 = hash("md5", $password, true); + $h2 = hash("md5", $hash, true); + return hash_equals($h1, $h2); + } + if (isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']])) { // Logged } elseif (isset($_POST['fm_usr'], $_POST['fm_pwd'], $_POST['token'])) { // Logging In sleep(1); if (function_exists('password_verify')) { - if (isset($auth_users[$_POST['fm_usr']]) && isset($_POST['fm_pwd']) && password_verify($_POST['fm_pwd'], $auth_users[$_POST['fm_usr']]) && verifyToken($_POST['token'])) { + if (isset($auth_users[$_POST['fm_usr']]) && isset($_POST['fm_pwd']) && tfm_password_verify($_POST['fm_pwd'], $auth_users[$_POST['fm_usr']]) && verifyToken($_POST['token'])) { $_SESSION[FM_SESSION_ID]['logged'] = $_POST['fm_usr']; fm_set_msg(lng('You are logged in')); fm_redirect(FM_SELF_URL); From 83b4a2f64992d89bbe3505ecd64b4516075bc1a4 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 17 Mar 2025 09:41:33 +0100 Subject: [PATCH 2/4] oops --- tinyfilemanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index 9be4acf..3b3a05c 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -334,7 +334,7 @@ if ($use_auth) { if (PHP_MAJOR_VERSION >= 8) { return str_starts_with($haystack, $needle); } - return strlen($needle) <= strlen($haystack) && 0 === substr_compare($haystack, $needle, 0); + return 0 === substr_compare($haystack, $needle, 0, strlen($needle)); }; $needles = array( '$1$', // CRYPT_MD5, From c649a43d820d69c2f53937a85e100ebc8a312938 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Wed, 19 Mar 2025 21:38:47 +0100 Subject: [PATCH 3/4] simplify Probably in the future a $7$ will be created. --- tinyfilemanager.php | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index 3b3a05c..a43a489 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -330,27 +330,20 @@ if ($use_auth) { string $password, string $hash ): bool { - $str_starts_with = function (string $haystack, string $needle): bool { - if (PHP_MAJOR_VERSION >= 8) { - return str_starts_with($haystack, $needle); - } - return 0 === substr_compare($haystack, $needle, 0, strlen($needle)); - }; - $needles = array( - '$1$', // CRYPT_MD5, - '$2a$', // CRYPT_BLOWFISH - '$2x$', // CRYPT_BLOWFISH - '$2y$', // CRYPT_BLOWFISH - '$5$', // CRYPT_SHA256 - '$6$', // CRYPT_SHA512 - ); - if (strlen($hash) >= 26) { // CRYPT_MD5, the weakest supported algo, yield length 26 hash. (DES is not supported.) - foreach ($needles as $needle) { - if ($str_starts_with($hash, $needle)) { - // it's a crypt() hash. - return password_verify($password, $hash); - } - } + // CRYPT_MD5: $1$ + // CRYPT_BLOWFISH: $2a$, $2x$, $2y$ + // CRYPT_SHA256: $5$ + // CRYPT_SHA512: $6$ + if ( + strlen($hash) >= 26 // CRYPT_MD5, the weakest known algo, yield length 26 hash. + // CRYPT_STD_DES and CRYPT_EXT_DES is not supported: They're insecure 1970's algos that nobody sane has used for decades, and they're difficult to detect. + && $hash[0] === '$' + && (1 === strspn($hash[1], '0123456789')) + && ($hash[2] === '$' || $hash[3] === '$') + && !($hash[2] === '$' && $hash[3] === '$') + ) { + // it's a crypt() hash. + return password_verify($password, $hash); } // plaintext password. // We still hash both inputs and use hash_equals() to protect against timing-attack-password-extraction. @@ -360,6 +353,7 @@ if ($use_auth) { $h2 = hash("md5", $hash, true); return hash_equals($h1, $h2); } + if (isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']])) { // Logged From 399b607987cc01681cfb6750476f34bf6eb5a6d5 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Wed, 19 Mar 2025 21:41:01 +0100 Subject: [PATCH 4/4] hash is also sensitive now when has might be plaintext, it's sensitive --- tinyfilemanager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tinyfilemanager.php b/tinyfilemanager.php index a43a489..4bc4756 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -328,6 +328,7 @@ if ($use_auth) { function tfm_password_verify( #[\SensitiveParameter] string $password, + #[\SensitiveParameter] string $hash ): bool { // CRYPT_MD5: $1$