]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
CVE-2026-4408: s3:samr-server: deny, mask and/or single quote username to 'check...
authorStefan Metzmacher <metze@samba.org>
Wed, 18 Mar 2026 11:24:47 +0000 (12:24 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 26 May 2026 12:51:32 +0000 (12:51 +0000)
We pass this on to the check password script, prevent remote command
execution.

We now try to autodetect if we could implicitly use '%u' for the
replacement and fallback to a fixed fallback username.

Admins should make use of SAMBA_CPS_ACCOUNT_NAME
instead of passing '%u' to 'check password script'

BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034

Pair-Programmed-With: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
source3/rpc_server/samr/srv_samr_chgpasswd.c

index 41fe5bcc71ede23e020fb42a1268fed4af835e4b..3ab050f66600fed97bd1636ac30bee8838603340 100644 (file)
@@ -54,6 +54,7 @@
 #include "passdb.h"
 #include "auth.h"
 #include "lib/util/sys_rw.h"
+#include "lib/util/util_str_escape.h"
 #include "librpc/rpc/dcerpc_samr.h"
 
 #include "lib/crypto/gnutls_helpers.h"
@@ -1008,27 +1009,118 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext)
 /***********************************************************
 ************************************************************/
 
+static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx,
+                                                  const char *orig_cmd,
+                                                  const char *username,
+                                                  char **cmd_out)
+{
+       const char *fallback_username = "__CVE-2026-4408_FallbackUsername__";
+       const char *inv = NULL;
+       char *cmd = NULL;
+       bool modified = false;
+       bool masked = false;
+       bool mixed_fallback = false;
+
+       *cmd_out = NULL;
+
+       if (username == NULL) {
+               return NT_STATUS_INVALID_USER_PRINCIPAL_NAME;
+       }
+
+       /*
+        * This catches invalid characters in account names
+        * which might be problematic passing to a shell script.
+        */
+       inv = strstr_for_invalid_account_characters(username);
+       if (inv != NULL) {
+               char *le_username = log_escape(tosctx, username);
+
+               DBG_WARNING("username '%s' has invalid or dangerous characters\n",
+                           le_username);
+
+               TALLOC_FREE(le_username);
+
+               return NT_STATUS_INVALID_USER_PRINCIPAL_NAME;
+       }
+
+       /*
+        * This masks the remaining unsafe characters which
+        * are not already caught by strstr_for_invalid_account_characters()
+        * with '_'.
+        *
+        * Then it replaces %u with an single quoted
+        * and/or shell escaped version of the masked username.
+        */
+       cmd = talloc_string_sub_unsafe(tosctx,
+                                      orig_cmd,
+                                      'u',
+                                      username,
+                                      STRING_SUB_UNSAFE_CHARACTERS,
+                                      '_',
+                                      fallback_username,
+                                      &modified,
+                                      &masked,
+                                      &mixed_fallback);
+       if (cmd == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /*
+        * Now warn about unexpected values
+        */
+
+       if (mixed_fallback) {
+               D_WARNING("CVE-2026-4408: "
+                         "strange quoting in 'check password script', "
+                         "falling back to replace %%u with %s, "
+                         "use testparm to fix the configuration\n",
+                         fallback_username);
+               D_WARNING("CVE-2026-4408: "
+                         "You should use '%%u', or SAMBA_CPS_ACCOUNT_NAME "
+                         "inside of 'check password script'.\n");
+       } else if (masked) {
+               char *le_username = log_escape(tosctx, username);
+
+               D_WARNING("CVE-2026-4408: "
+                         "replaced %%u with masked value instead of: %s\n",
+                         le_username);
+               D_WARNING("CVE-2026-4408: "
+                         "You should use SAMBA_CPS_ACCOUNT_NAME inside "
+                         "'check password script' instead of %%u.\n");
+
+               TALLOC_FREE(le_username);
+       }
+
+       *cmd_out = cmd;
+       return NT_STATUS_OK;
+}
+
+
 NTSTATUS check_password_complexity(const char *username,
                                   const char *fullname,
                                   const char *password,
                                   enum samPwdChangeReason *samr_reject_reason)
 {
+       int check_ret;
+       NTSTATUS status;
        TALLOC_CTX *tosctx = talloc_tos();
        const struct loadparm_substitution *lp_sub =
                loadparm_s3_global_substitution();
-       int check_ret;
-       char *cmd;
+       const char *orig_cmd = NULL;
+       char *cmd = NULL;
 
-       /* Use external script to check password complexity */
-       if ((lp_check_password_script(tosctx, lp_sub) == NULL)
-           || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){
+       orig_cmd = lp_check_password_script(tosctx, lp_sub);
+       if (orig_cmd == NULL || orig_cmd[0] == '\0') {
                return NT_STATUS_OK;
        }
 
-       cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u",
-                               username);
-       if (!cmd) {
-               return NT_STATUS_PASSWORD_RESTRICTION;
+       /* note we don't use 'fullname' or 'password' here */
+       status = check_password_complexity_internal(tosctx,
+                                                   orig_cmd,
+                                                   username,
+                                                   &cmd);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
        check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1);