]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
chpasswd: Check hash before write when using -e
authorvinz <mmpx09@protonmail.com>
Fri, 11 Jul 2025 16:08:22 +0000 (16:08 +0000)
committerAlejandro Colomar <foss+github@alejandro-colomar.es>
Sun, 20 Jul 2025 19:57:14 +0000 (21:57 +0200)
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
lib/chkhash.c [new file with mode: 0644]
lib/chkhash.h [new file with mode: 0644]
src/chpasswd.c

index 21653c5be7cede43294100d3f019780315891fd3..6c68cfa1eb56be64ace1533f84777dd2d5b8948c 100644 (file)
@@ -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 (file)
index 0000000..6687050
--- /dev/null
@@ -0,0 +1,70 @@
+#include "config.h"
+
+#include "chkhash.h"
+
+#include <regex.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+
+/*
+ * 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(&regex, pattern, REG_EXTENDED) != 0)
+               return false;
+
+       result = regexec(&regex, string, 0, NULL, 0);
+       regfree(&regex);
+
+       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 (file)
index 0000000..d986199
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef SHADOW_INCLUDE_CHKHASH_H
+#define SHADOW_INCLUDE_CHKHASH_H
+
+
+#include "config.h"
+
+#include <stdbool.h>
+
+
+bool is_valid_hash(const char *hash);
+
+
+#endif
index f04daac4cd0f0c555ecc9ce035003bdb17c13823..6f8423632cbc060b2e420aed9b74261eee05ffcf 100644 (file)
@@ -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;