CVE-2020-25717: s3:auth: let auth3_generate_session_info_pac() delegate everything...
authorStefan Metzmacher <metze@samba.org>
Mon, 4 Oct 2021 17:42:20 +0000 (19:42 +0200)
committerPavel Filipenský <pfilipensky@samba.org>
Wed, 18 Jan 2023 12:00:51 +0000 (13:00 +0100)
This consolidates the code paths used for NTLMSSP and Kerberos!

I checked what we were already doing for NTLMSSP, which is this:

a) source3/auth/auth_winbind.c calls wbcAuthenticateUserEx()
b) as a domain member we require a valid response from winbindd,
   otherwise we'll return NT_STATUS_NO_LOGON_SERVERS
c) we call make_server_info_wbcAuthUserInfo(), which internally
   calls make_server_info_info3()
d) auth_check_ntlm_password() calls
   smb_pam_accountcheck(unix_username, rhost), where rhost
   is only an ipv4 or ipv6 address (without reverse dns lookup)
e) from auth3_check_password_send/auth3_check_password_recv()
   server_returned_info will be passed to auth3_generate_session_info(),
   triggered by gensec_session_info(), which means we'll call into
   create_local_token() in order to transform auth_serversupplied_info
   into auth_session_info.

For Kerberos gensec_session_info() will call
auth3_generate_session_info_pac() via the gensec_generate_session_info_pac()
helper function. The current logic is this:

a) gensec_generate_session_info_pac() is the function that
   evaluates the 'gensec:require_pac', which defaulted to 'no'
   before.
b) auth3_generate_session_info_pac() called
   wbcAuthenticateUserEx() in order to pass the PAC blob
   to winbindd, but only to prime its cache, e.g. netsamlogon cache
   and others. Most failures were just ignored.
c) If the PAC blob is available, it extracted the PAC_LOGON_INFO
   from it.
d) Then we called the horrible get_user_from_kerberos_info() function:
   - It uses a first part of the tickets principal name (before the @)
     as username and combines that with the 'logon_info->base.logon_domain'
     if the logon_info (PAC) is present.
   - As a fallback without a PAC it's tries to ask winbindd for a mapping
     from realm to netbios domain name.
   - Finally is falls back to using the realm as netbios domain name
   With this information is builds 'userdomain+winbind_separator+useraccount'
   and calls map_username() followed by smb_getpwnam() with create=true,
   Note this is similar to the make_server_info_info3() => check_account()
   => smb_getpwnam() logic under 3.
   - It also calls smb_pam_accountcheck(), but may pass the reverse DNS lookup name
     instead of the ip address as rhost.
   - It does some MAP_TO_GUEST_ON_BAD_UID logic and auto creates the
     guest account.
e) We called create_info3_from_pac_logon_info()
f) make_session_info_krb5() calls gets called and triggers this:
   - If get_user_from_kerberos_info() mapped to guest, it calls
     make_server_info_guest()
   - If create_info3_from_pac_logon_info() created a info3 from logon_info,
     it calls make_server_info_info3()
   - Without a PAC it tries pdb_getsampwnam()/make_server_info_sam() with
     a fallback to make_server_info_pw()
   From there it calls create_local_token()

I tried to change auth3_generate_session_info_pac() to behave similar
to auth_winbind.c together with auth3_generate_session_info() as
a domain member, as we now rely on a PAC:

a) As domain member we require a PAC and always call wbcAuthenticateUserEx()
   and require a valid response!
b) we call make_server_info_wbcAuthUserInfo(), which internally
   calls make_server_info_info3(). Note make_server_info_info3()
   handles MAP_TO_GUEST_ON_BAD_UID and make_server_info_guest()
   internally.
c) Similar to auth_check_ntlm_password() we now call
   smb_pam_accountcheck(unix_username, rhost), where rhost
   is only an ipv4 or ipv6 address (without reverse dns lookup)
d) From there it calls create_local_token()

As standalone server (in an MIT realm) we continue
with the already existing code logic, which works without a PAC:
a) we keep smb_getpwnam() with create=true logic as it
   also requires an explicit 'add user script' option.
b) In the following commits we assert that there's
   actually no PAC in this mode, which means we can
   remove unused and confusing code.

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

Signed-off-by: Stefan Metzmacher <metze@samba.org>
[abartlet@samba.org Backported due to change in structure
 initialization with { 0 } to zero ]
[abartlet@samba.org backported to 4.12 due to conflict
 with code not present to reload shared on krb5 login]

source3/auth/auth_generic.c

index 26a38f92b304ad04b288737d16a08f2507905634..3099e8f90578b31dec3af13480b97cb643862a22 100644 (file)
@@ -46,6 +46,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
                                                uint32_t session_info_flags,
                                                struct auth_session_info **session_info)
 {
+       enum server_role server_role = lp_server_role();
        TALLOC_CTX *tmp_ctx;
        struct PAC_LOGON_INFO *logon_info = NULL;
        struct netr_SamInfo3 *info3_copy = NULL;
@@ -54,39 +55,59 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
        char *ntuser;
        char *ntdomain;
        char *username;
-       char *rhost;
+       const char *rhost;
        struct passwd *pw;
        NTSTATUS status;
-       int rc;
 
        tmp_ctx = talloc_new(mem_ctx);
        if (!tmp_ctx) {
                return NT_STATUS_NO_MEMORY;
        }
 
-       if (pac_blob) {
-#ifdef HAVE_KRB5
-               struct wbcAuthUserParams params = {};
+       if (tsocket_address_is_inet(remote_address, "ip")) {
+               rhost = tsocket_address_inet_addr_string(
+                       remote_address, tmp_ctx);
+               if (rhost == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto done;
+               }
+       } else {
+               rhost = "127.0.0.1";
+       }
+
+       if (server_role != ROLE_STANDALONE) {
+               struct wbcAuthUserParams params = { 0 };
                struct wbcAuthUserInfo *info = NULL;
                struct wbcAuthErrorInfo *err = NULL;
+               struct auth_serversupplied_info *server_info = NULL;
+               char *original_user_name = NULL;
+               char *p = NULL;
                wbcErr wbc_err;
 
+               if (pac_blob == NULL) {
+                       /*
+                        * This should already be catched at the main
+                        * gensec layer, but better check twice
+                        */
+                       status = NT_STATUS_INTERNAL_ERROR;
+                       goto done;
+               }
+
                /*
                 * Let winbind decode the PAC.
                 * This will also store the user
                 * data in the netsamlogon cache.
                 *
-                * We need to do this *before* we
-                * call get_user_from_kerberos_info()
-                * as that does a user lookup that
-                * expects info in the netsamlogon cache.
-                *
-                * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=11259
+                * This used to be a cache prime
+                * optimization, but now we delegate
+                * all logic to winbindd, as we require
+                * winbindd as domain member anyway.
                 */
                params.level = WBC_AUTH_USER_LEVEL_PAC;
                params.password.pac.data = pac_blob->data;
                params.password.pac.length = pac_blob->length;
 
+               /* we are contacting the privileged pipe */
                become_root();
                wbc_err = wbcAuthenticateUserEx(&params, &info, &err);
                unbecome_root();
@@ -99,18 +120,90 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
                 */
 
                switch (wbc_err) {
-                       case WBC_ERR_WINBIND_NOT_AVAILABLE:
                        case WBC_ERR_SUCCESS:
                                break;
+                       case WBC_ERR_WINBIND_NOT_AVAILABLE:
+                               status = NT_STATUS_NO_LOGON_SERVERS;
+                               DBG_ERR("winbindd not running - "
+                                       "but required as domain member: %s\n",
+                                       nt_errstr(status));
+                               goto done;
                        case WBC_ERR_AUTH_ERROR:
                                status = NT_STATUS(err->nt_status);
                                wbcFreeMemory(err);
                                goto done;
+                       case WBC_ERR_NO_MEMORY:
+                               status = NT_STATUS_NO_MEMORY;
+                               goto done;
                        default:
                                status = NT_STATUS_LOGON_FAILURE;
                                goto done;
                }
 
+               status = make_server_info_wbcAuthUserInfo(tmp_ctx,
+                                                         info->account_name,
+                                                         info->domain_name,
+                                                         info, &server_info);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10, ("make_server_info_wbcAuthUserInfo failed: %s\n",
+                                  nt_errstr(status)));
+                       goto done;
+               }
+
+               /* We skip doing this step if the caller asked us not to */
+               if (!(server_info->guest)) {
+                       const char *unix_username = server_info->unix_name;
+
+                       /* We might not be root if we are an RPC call */
+                       become_root();
+                       status = smb_pam_accountcheck(unix_username, rhost);
+                       unbecome_root();
+
+                       if (!NT_STATUS_IS_OK(status)) {
+                               DEBUG(3, ("check_ntlm_password:  PAM Account for user [%s] "
+                                         "FAILED with error %s\n",
+                                         unix_username, nt_errstr(status)));
+                               goto done;
+                       }
+
+                       DEBUG(5, ("check_ntlm_password:  PAM Account for user [%s] "
+                                 "succeeded\n", unix_username));
+               }
+
+               DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name));
+
+               p = strchr_m(princ_name, '@');
+               if (!p) {
+                       DEBUG(3, ("[%s] Doesn't look like a valid principal\n",
+                                 princ_name));
+                       status = NT_STATUS_LOGON_FAILURE;
+                       goto done;
+               }
+
+               original_user_name = talloc_strndup(tmp_ctx, princ_name, p - princ_name);
+               if (original_user_name == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto done;
+               }
+
+               status = create_local_token(mem_ctx,
+                                           server_info,
+                                           NULL,
+                                           original_user_name,
+                                           session_info);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10, ("create_local_token failed: %s\n",
+                                  nt_errstr(status)));
+                       goto done;
+               }
+
+               goto session_info_ready;
+       }
+
+       /* This is the standalone legacy code path */
+
+       if (pac_blob != NULL) {
+#ifdef HAVE_KRB5
                status = kerberos_pac_logon_info(tmp_ctx, *pac_blob, NULL, NULL,
                                                 NULL, NULL, 0, &logon_info);
 #else
@@ -121,22 +214,6 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
                }
        }
 
-       rc = get_remote_hostname(remote_address,
-                                &rhost,
-                                tmp_ctx);
-       if (rc < 0) {
-               status = NT_STATUS_NO_MEMORY;
-               goto done;
-       }
-       if (strequal(rhost, "UNKNOWN")) {
-               rhost = tsocket_address_inet_addr_string(remote_address,
-                                                        tmp_ctx);
-               if (rhost == NULL) {
-                       status = NT_STATUS_NO_MEMORY;
-                       goto done;
-               }
-       }
-
        status = get_user_from_kerberos_info(tmp_ctx, rhost,
                                             princ_name, logon_info,
                                             &is_mapped, &is_guest,
@@ -170,6 +247,8 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
                goto done;
        }
 
+session_info_ready:
+
        /* setup the string used by %U */
        set_current_user_info((*session_info)->unix_info->sanitized_username,
                              (*session_info)->unix_info->unix_name,
@@ -179,7 +258,9 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
        lp_load_with_shares(get_dyn_CONFIGFILE());
 
        DEBUG(5, (__location__ "OK: user: %s domain: %s client: %s\n",
-                 ntuser, ntdomain, rhost));
+                 (*session_info)->info->account_name,
+                 (*session_info)->info->domain_name,
+                 rhost));
 
        status = NT_STATUS_OK;