From dc7ac4d0a5595797b95905c098fdac2d42dbdc84 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 14 Feb 2025 20:57:07 +0100 Subject: [PATCH] s3:rpc_server/netlogon: let _netr_NTLMv2_RESPONSE_verify() generate trust_forest_domain_info array MS-NRPC 3.5.4.5.1.1 Pass-through domain name validation, requires to pass information about the trust topology to NTLMv2_RESPONSE_verify_netlogon_creds()... Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme --- source3/rpc_server/netlogon/srv_netlog_nt.c | 225 ++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c index ab661931b2c..aadea689501 100644 --- a/source3/rpc_server/netlogon/srv_netlog_nt.c +++ b/source3/rpc_server/netlogon/srv_netlog_nt.c @@ -52,10 +52,14 @@ #include "lib/util/util_str_escape.h" #include "source3/lib/substitute.h" #include "librpc/rpc/server/netlogon/schannel_util.h" +#include "libcli/lsarpc/util_lsarpc.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_RPC_SRV +static NTSTATUS fill_forest_trust_array(TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation *info); + /************************************************************************* _netr_LogonControl *************************************************************************/ @@ -1649,6 +1653,227 @@ static NTSTATUS _netr_NTLMv2_RESPONSE_verify( size_t num_trusts = 0; struct trust_forest_domain_info *trusts = NULL; + if (creds->secure_channel_type == SEC_CHAN_DNS_DOMAIN || + creds->secure_channel_type == SEC_CHAN_DOMAIN) + { + struct trust_forest_domain_info *remote_d = NULL; + struct pdb_trusted_domain **dex = NULL; + struct trustdom_info **dlg = NULL; + uint32_t num_domains = 0; + char *creds_trust = NULL; + char *p = NULL; + size_t ti; + + creds_trust = talloc_strdup(frame, creds->account_name); + if (creds_trust == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + p = strrchr(creds_trust, '$'); + if (p != NULL) { + *p = '\0'; + } + + if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) { + status = pdb_enum_trusted_domains(frame, + &num_domains, + &dex); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } else { + status = pdb_enum_trusteddoms(frame, + &num_domains, + &dlg); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + trusts = talloc_zero_array(frame, + struct trust_forest_domain_info, + num_domains + 1); + if (trusts == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + trusts[0].tdo = talloc_zero(trusts, + struct lsa_TrustDomainInfoInfoEx); + if (trusts[0].tdo == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + /* + * information about our own forest/domain + */ + + if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) { + struct lsa_ForestTrustInformation *fti = NULL; + const struct lsa_ForestTrustDomainInfo *ftd = NULL; + + fti = talloc_zero(trusts, + struct lsa_ForestTrustInformation); + if (fti == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = fill_forest_trust_array(fti, fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + SMB_ASSERT(fti->count == 2); + SMB_ASSERT(fti->entries[1] != NULL); + ftd = &fti->entries[1]->forest_trust_data.domain_info; + + trusts[0].tdo->sid = ftd->domain_sid; + trusts[0].tdo->domain_name.string = + ftd->dns_domain_name.string; + trusts[0].tdo->netbios_name.string = + ftd->netbios_domain_name.string; + + status = trust_forest_info_lsa_1to2(trusts, + fti, + &trusts[0].fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } else { + trusts[0].tdo->sid = get_global_sam_sid(); + trusts[0].tdo->netbios_name.string = workgroup; + } + + trusts[0].is_local_forest = true; + + for (ti = 0; ti < num_domains; ti++) { + struct trust_forest_domain_info *d = + &trusts[ti+1]; + struct ForestTrustInfo *fti = NULL; + enum ndr_err_code ndr_err; + size_t fi; + + d->tdo = talloc_zero(trusts, + struct lsa_TrustDomainInfoInfoEx); + if (d->tdo == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) { + d->tdo->sid = &dex[ti]->security_identifier; + d->tdo->domain_name.string = dex[ti]->domain_name; + d->tdo->netbios_name.string = dex[ti]->netbios_name; + d->tdo->trust_type = dex[ti]->trust_type; + d->tdo->trust_direction = dex[ti]->trust_direction; + d->tdo->trust_attributes = dex[ti]->trust_attributes; + } else { + d->tdo->sid = &dlg[ti]->sid; + d->tdo->netbios_name.string = dlg[ti]->name; + } + + if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) { + continue; + } + + if (dex[ti]->trust_forest_trust_info.length == 0) { + continue; + } + + fti = talloc_zero(trusts, + struct ForestTrustInfo); + if (fti == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + ndr_err = ndr_pull_struct_blob_all( + &dex[ti]->trust_forest_trust_info, + fti, fti, + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + + status = trust_forest_info_to_lsa2(trusts, + fti, + &d->fti); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + /* + * We do not have a forest trust scanner + * backgroup task, so we need + * to change the LSA_FOREST_TRUST_DOMAIN_INFO + * records into + * LSA_FOREST_TRUST_SCANNER_INFO + * records. + * + * They are both of type + * struct lsa_ForestTrustDomainInfo + * so it's easy to do that. + */ + for (fi = 0; fi < d->fti->count; fi++) { + struct lsa_ForestTrustRecord2 *r = + d->fti->entries[fi]; + + if (r == NULL) { + continue; + } + + if (r->type != LSA_FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + r->type = LSA_FOREST_TRUST_SCANNER_INFO; + } + } + + num_trusts = num_domains + 1; + for (ti = 0; ti < num_trusts; ti++) { + struct trust_forest_domain_info *d = + &trusts[ti]; + + if (strequal(d->tdo->netbios_name.string, creds_trust)) { + if (remote_d != NULL) { + /* + * We should only find one + */ + TALLOC_FREE(frame); + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + if (d->is_local_forest) { + /* + * Something is completely broken + */ + TALLOC_FREE(frame); + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + d->is_checked_trust = true; + remote_d = d; + } + } + + if (remote_d == NULL) { + /* + * We should at least find the tdo + * of the remote domain + */ + TALLOC_FREE(frame); + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + } + status = NTLMv2_RESPONSE_verify_netlogon_creds( user_info->client.account_name, user_info->client.domain_name, -- 2.47.2