From c44f1e096a19a7d356da5969295393247e61874f Mon Sep 17 00:00:00 2001 From: vinz Date: Fri, 11 Jul 2025 16:08:22 +0000 Subject: [PATCH] chpasswd: Check hash before write when using -e Add is_valid_hash to prevent adding a bad hash in /etc/shadow (and so prevent user to be lock) when using chpasswd -e # before echo 'vinz:test123' | chpasswd -e grep vinz /etc/shadow vinz:test123:20280:0:99999:7::: # now echo 'vinz:test123' | sudo ./chpasswd -e chpasswd: (line 1, user vinz) invalid password hash chpasswd: error detected, changes ignored --- lib/Makefile.am | 2 ++ lib/chkhash.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/chkhash.h | 13 +++++++++ src/chpasswd.c | 16 +++++++++++ 4 files changed, 101 insertions(+) create mode 100644 lib/chkhash.c create mode 100644 lib/chkhash.h diff --git a/lib/Makefile.am b/lib/Makefile.am index 21653c5be..6c68cfa1e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -75,6 +75,8 @@ libshadow_la_SOURCES = \ cast.h \ chkname.c \ chkname.h \ + chkhash.c \ + chkhash.h \ chowndir.c \ chowntty.c \ cleanup.c \ diff --git a/lib/chkhash.c b/lib/chkhash.c new file mode 100644 index 000000000..668705007 --- /dev/null +++ b/lib/chkhash.c @@ -0,0 +1,70 @@ +#include "config.h" + +#include "chkhash.h" + +#include +#include +#include +#include + + +/* + * match_regex - return true if match, false if not + */ +bool +match_regex(const char *pattern, const char *string) +{ + regex_t regex; + int result; + + if (regcomp(®ex, pattern, REG_EXTENDED) != 0) + return false; + + result = regexec(®ex, string, 0, NULL, 0); + regfree(®ex); + + return result == 0; +} + + +/* + * is_valid_hash - check if the given string is a valid password hash + * + * Returns true if the string appears to be a valid hash, false otherwise. + * + * regex from: https://man.archlinux.org/man/crypt.5.en + */ +bool +is_valid_hash(const char *hash) +{ + // Minimum hash length + if (strlen(hash) < 13) + return false; + + // Yescrypt: $y$ + algorithm parameters + $ + salt + $ + 43-char (minimum) hash + if (match_regex("^\\$y\\$[./A-Za-z0-9]+\\$[./A-Za-z0-9]{1,86}\\$[./A-Za-z0-9]{43}$", hash)) + return true; + + // Bcrypt: $2[abxy]$ + 2-digit cost + $ + 53-char hash + if (match_regex("^\\$2[abxy]\\$[0-9]{2}\\$[./A-Za-z0-9]{53}$", hash)) + return true; + + // SHA-512: $6$ + salt + $ + 86-char hash + if (match_regex("^\\$6\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\\n]{1,16}\\$[./A-Za-z0-9]{86}$", hash)) + return true; + + // SHA-256: $5$ + salt + $ + 43-char hash + if (match_regex("^\\$5\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\\n]{1,16}\\$[./A-Za-z0-9]{43}$", hash)) + return true; + + // MD5: $1$ + salt + $ + 22-char hash + if (match_regex("^\\$1\\$[^$:\\n]{1,8}\\$[./A-Za-z0-9]{22}$", hash)) + return true; + + // DES: exactly 13 characters from [A-Za-z0-9./] + if (match_regex("^[./A-Za-z0-9]{13}$", hash)) + return true; + + // Not a valid hash + return false; +} diff --git a/lib/chkhash.h b/lib/chkhash.h new file mode 100644 index 000000000..d986199a8 --- /dev/null +++ b/lib/chkhash.h @@ -0,0 +1,13 @@ +#ifndef SHADOW_INCLUDE_CHKHASH_H +#define SHADOW_INCLUDE_CHKHASH_H + + +#include "config.h" + +#include + + +bool is_valid_hash(const char *hash); + + +#endif diff --git a/src/chpasswd.c b/src/chpasswd.c index f04daac4c..6f8423632 100644 --- a/src/chpasswd.c +++ b/src/chpasswd.c @@ -22,6 +22,7 @@ #include "pam_defs.h" #endif /* USE_PAM */ #include "atoi/str2i.h" +#include "chkhash.h" #include "defines.h" #include "nscd.h" #include "sssd.h" @@ -554,6 +555,21 @@ int main (int argc, char **argv) } else #endif /* USE_PAM */ { + + /* + * Prevent adding a non valid hash to /etc/shadow and + * potentialy lock account + */ + + if (eflg) { + if (!is_valid_hash(newpwd)) { + fprintf (stderr, + _("%s: (line %jd, user %s) invalid password hash\n"), + Prog, line, name); + errors = true; + continue; + } + } const struct spwd *sp; struct spwd newsp; const struct passwd *pw; -- 2.47.2