From 4a95661686544bbe3a242e99e6afbd63d5fcc19a Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Mon, 11 Apr 2022 13:50:35 +0300 Subject: [PATCH] ipa-sam: retrieve trusted domain account credential from the TDO itself When NRPC netr_ServerAuthenticate3 call is performed, a trusted AD DC would use trusted domain account to authenticate to Samba. This means that Samba would do internally samr_QueryUserInfo2 request with level 16 (UserControlInformation), coming to PDB module via pdb_getsampwsid() call. For normal user or workstation accounts we expect to have Kerberos keys available and may be able to extract NTLM hash data from them. However, trusted domain account is not a normal Kebreros principal. It stores TDO credential in a different way. Since we never processed it through the pdb_getsampwsid() call, it was not possible to retrieve the NTLM hash for TDO account at all, hence netr_ServerAuthenticate3 call was failing. NTLM hash is used internally in Samba. An external communication with AD DC will use an AES-based session key that is derived from the TDO credential. The credential itself can be treated as a plaintext here. Fix it by adding a recognition of the trusted domain object account and retrieve the NTLM hash from the correct attribute of the TDO. Fixes: https://pagure.io/freeipa/issue/9134 Signed-off-by: Alexander Bokovoy Reviewed-By: Florence Blanc-Renaud --- daemons/ipa-sam/ipa_sam.c | 157 ++++++++++++++++++++++++++++---------- 1 file changed, 117 insertions(+), 40 deletions(-) diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c index 27350ccb2..aed39e90d 100644 --- a/daemons/ipa-sam/ipa_sam.c +++ b/daemons/ipa-sam/ipa_sam.c @@ -2728,7 +2728,7 @@ static NTSTATUS get_trust_pwd(TALLOC_CTX *mem_ctx, const DATA_BLOB *auth_blob, if (iopw.count != 0 && iopw.current.count != 0 && iopw.current.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) { if (pwd != NULL) { - if (!convert_string_talloc(tmp_ctx, CH_UTF16, CH_UNIX, + if (!convert_string_talloc(tmp_ctx, CH_UTF16LE, CH_UNIX, iopw.current.array[0].AuthInfo.clear.password, iopw.current.array[0].AuthInfo.clear.size, &trustpw, &converted_size)) { @@ -3581,12 +3581,14 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state, bool ret = false; bool retval = false; bool machine_account = false; + bool trusted_domain = false; int status; int len = 0; int idx = 0; size_t conv_size = 0; DATA_BLOB nthash; struct dom_sid *group_sid; + uint32_t acct_flags = 0; TALLOC_CTX *tmp_ctx = talloc_init("init_sam_from_ldap"); if (!tmp_ctx) { @@ -3613,17 +3615,15 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state, } len = ldap_count_values_len(usernames); - if (len > 1) { - /* Extract machine account as a user name if exists. - * If not, extract the first returned value */ - for (int i=0; i < len; i++) { - if (usernames[i] != NULL && - usernames[i]->bv_len > 0 && - usernames[i]->bv_val[usernames[i]->bv_len-1] == '$') { - idx = i; - machine_account = true; - break; - } + /* Extract machine account as a user name if exists. + * If not, extract the first returned value */ + for (int i=0; i < len; i++) { + if (usernames[i] != NULL && + usernames[i]->bv_len > 0 && + usernames[i]->bv_val[usernames[i]->bv_len-1] == '$') { + idx = i; + machine_account = true; + break; } } @@ -3644,6 +3644,31 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state, DEBUG(2, ("init_sam_from_ldap: Entry found for user: %s\n", username)); + if (machine_account) { + /* it may also be a TDO account */ + struct berval **oclasses = NULL; + oclasses = ldap_get_values_len(priv2ld(ipasam_state), entry, + LDAP_ATTRIBUTE_OBJECTCLASS); + int c = 0; + if (oclasses == NULL) { + DEBUG(1, ("Cannot find any objectclasses.\n")); + goto fn_exit; + } + + for (c = 0; oclasses[c] != NULL; c++) { + if (strncasecmp(LDAP_OBJ_TRUSTED_DOMAIN, oclasses[c]->bv_val, + oclasses[c]->bv_len) == 0) { + break; + } + } + + if (oclasses[c] != NULL) { + trusted_domain = true; + } + + ldap_value_free_len(oclasses); + } + nt_username = talloc_strdup(tmp_ctx, username); if (!nt_username) { goto fn_exit; @@ -3712,41 +3737,93 @@ static bool init_sam_from_ldap(struct ipasam_private *ipasam_state, } - /* Force machine accounts to be workstation trust type */ - pdb_set_acct_ctrl(sampass, machine_account ? ACB_WSTRUST : ACB_NORMAL, - PDB_SET); + /* set correct account type */ + acct_flags = trusted_domain ? ACB_DOMTRUST : + (machine_account ? ACB_WSTRUST : ACB_NORMAL); + pdb_set_acct_ctrl(sampass, acct_flags, PDB_SET); - retval = smbldap_talloc_single_blob(tmp_ctx, - priv2ld(ipasam_state), - entry, LDAP_ATTRIBUTE_NTHASH, - &nthash); - if (!retval) { - /* NT Hash is not in place. Attempt to retrieve it from - * the RC4-HMAC key if that exists in Kerberos credentials. - * IPA 389-ds plugin allows to ask for it by setting - * ipaNTHash to MagicRegen value. - * */ - temp = smbldap_talloc_dn(tmp_ctx, priv2ld(ipasam_state), entry); - if (temp) { - retval = ipasam_nthash_regen(ipasam_state, - tmp_ctx, temp); - if (retval) { - retval = ipasam_nthash_retrieve(ipasam_state, - tmp_ctx, temp, &nthash); + if (trusted_domain) { + DATA_BLOB trust_auth_incoming = {0}; + char *pass = NULL; + NTSTATUS xstatus; + + retval = false; + if (smbldap_talloc_single_blob(tmp_ctx, + priv2ld(ipasam_state), entry, + LDAP_ATTRIBUTE_TRUST_AUTH_INCOMING, + &trust_auth_incoming)) { + xstatus = get_trust_pwd(tmp_ctx, &trust_auth_incoming, + &pass, NULL); + if (NT_STATUS_IS_OK(xstatus)) { + uint8_t key[NT_HASH_LEN]; + volatile char *p = (void *) pass; + size_t plen = strlen(pass); + + if (E_md4hash(pass, key)) { + volatile char *q = (void *) key; + size_t qlen = NT_HASH_LEN; + + nthash.data = talloc_memdup(tmp_ctx, key, NT_HASH_LEN); + nthash.length = NT_HASH_LEN; + + retval = nthash.data != NULL; + + while (qlen--) { + *q++ = '\0'; + } + } + while (plen--) { + *p++ = '\0'; + } + } + } else { + DEBUG(9, ("Failed to get incoming auth info.\n")); + } + } else { + retval = smbldap_talloc_single_blob(tmp_ctx, + priv2ld(ipasam_state), + entry, LDAP_ATTRIBUTE_NTHASH, + &nthash); + if (!retval) { + /* NT Hash is not in place. Attempt to retrieve it from + * the RC4-HMAC key if that exists in Kerberos credentials. + * IPA 389-ds plugin allows to ask for it by setting + * ipaNTHash to MagicRegen value. + * */ + temp = smbldap_talloc_dn(tmp_ctx, priv2ld(ipasam_state), entry); + if (temp) { + retval = ipasam_nthash_regen(ipasam_state, + tmp_ctx, temp); + if (retval) { + retval = ipasam_nthash_retrieve(ipasam_state, + tmp_ctx, temp, &nthash); + } } } } - if (!retval) { - DEBUG(5, ("Failed to read NT hash form LDAP response.\n")); - } + if (retval) { + if (nthash.length != NT_HASH_LEN && nthash.length != 0) { + DEBUG(5, ("NT hash from LDAP has the wrong size. " + "Perhaps password was not re-set?\n")); + } else { + volatile char *q = (void *) nthash.data; + size_t qlen = nthash.length; - if (nthash.length != NT_HASH_LEN && nthash.length != 0) { - DEBUG(5, ("NT hash from LDAP has the wrong size. Perhaps password was not re-set?\n")); - } else { - if (!pdb_set_nt_passwd(sampass, nthash.data, PDB_SET)) { - DEBUG(5, ("Failed to set NT hash.\n")); + if (!pdb_set_nt_passwd(sampass, nthash.data, PDB_SET)) { + DEBUG(5, ("Failed to set NT hash.\n")); + } + + if (q != NULL) { + /* nthash.length might be 0 on this path, + * we want to skip in this case */ + while (nthash.length && qlen--) { + *q++ = '\0'; + } + } } + } else { + DEBUG(5, ("Failed to read NT hash form LDAP response.\n")); } /* FIXME: */ if (!pdb_set_pass_last_set_time(sampass, (time_t) 1, PDB_SET)) {