From 4784cb826f7cfd01471c29cfb51bdf6d34d6d643 Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Tue, 9 Sep 2025 12:45:24 -0300 Subject: [PATCH] ipa-kdb: enforce PAC presence on TGT for TGS-REQ MS-KILE's PA-PAC-REQUEST sequence allows the Kerberos client to request a TGT without a PAC. At the moment, there is no way to configure the MIT KDC to reject such request. This commit enforces the presence of the PAC when processing TGTs provided by TGS-REQ. It ensures the server principal of the TGT is the same as the one in PAC_CLIENT_INFO (i.e. enforces client principal canonicalization) with integrity check. Only one exception is applied: this check is skipped for local TGTs on domain where the MS-PAC generator is not initialized (i.e. domains where SID generation was not executed yet). Signed-off-by: Julien Rische --- daemons/ipa-kdb/ipa_kdb.h | 9 +++ daemons/ipa-kdb/ipa_kdb_common.c | 18 ++++++ daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 2 +- daemons/ipa-kdb/ipa_kdb_mspac.c | 87 ++++++++++++++++++++++++++++ daemons/ipa-kdb/ipa_kdb_principals.c | 21 +------ 5 files changed, 116 insertions(+), 21 deletions(-) diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 85cabe142..7bad8c85f 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -434,6 +434,14 @@ ipadb_check_for_bronze_bit_attack(krb5_context context, # endif #endif +/* Check the ticket provided in a TGS-REQ. In some situations, the ticket is + * expected to contain a PAC. If it is not the case, or if the function is + * enable to decode an authorization-data element, it fails. + * Any failure should result in the TGS-REQ to be rejected. */ +krb5_error_code ipadb_enforce_pac(krb5_context kcontext, + const krb5_ticket *ticket, + const char **status); + /* DELEGATION CHECKS */ krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, @@ -472,3 +480,4 @@ int ipadb_string_to_sid(const char *str, struct dom_sid *sid); void alloc_sid(struct dom_sid **sid); void free_sid(struct dom_sid **sid); bool dom_sid_check(const struct dom_sid *sid1, const struct dom_sid *sid2, bool exact_check); +bool ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ); diff --git a/daemons/ipa-kdb/ipa_kdb_common.c b/daemons/ipa-kdb/ipa_kdb_common.c index 42e0856d0..eb0b0d129 100644 --- a/daemons/ipa-kdb/ipa_kdb_common.c +++ b/daemons/ipa-kdb/ipa_kdb_common.c @@ -704,3 +704,21 @@ krb5_error_code ipadb_multibase_search(struct ipadb_context *ipactx, return ipadb_simple_ldap_to_kerr(ret); } +bool +ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ) +{ + krb5_data *primary; + size_t l_tgs_name; + + if (2 != krb5_princ_size(kcontext, princ)) + return false; + + primary = krb5_princ_component(kcontext, princ, 0); + + l_tgs_name = strlen(KRB5_TGS_NAME); + + if (l_tgs_name != primary->length) + return false; + + return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name); +} diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c index d6d618d1d..a92a9a0ad 100644 --- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c +++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c @@ -207,7 +207,7 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata, *lifetime_out = 0; *renew_lifetime_out = 0; - return 0; + return ipadb_enforce_pac(context, ticket, status); } krb5_error_code kdcpolicy_ipakdb_initvt(krb5_context context, diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c index 0964d112a..c4085fca5 100644 --- a/daemons/ipa-kdb/ipa_kdb_mspac.c +++ b/daemons/ipa-kdb/ipa_kdb_mspac.c @@ -3344,6 +3344,93 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext, return KRB5_KDB_NOENTRY; } +static krb5_error_code +check_for_pac(krb5_context kcontext, krb5_authdata **authdata, bool *pac_present) +{ + krb5_error_code kerr = ENOENT; + size_t i, j; + krb5_authdata **ifrel = NULL; + + for (i = 0; authdata && authdata[i]; ++i) { + if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT) { + continue; + } + + kerr = krb5_decode_authdata_container(kcontext, + KRB5_AUTHDATA_IF_RELEVANT, + authdata[i], &ifrel); + if (kerr) { + goto end; + } + + for (j = 0; ifrel[j]; ++j) { + if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC) { + break; + } + } + if (ifrel[j]) { + break; + } + + krb5_free_authdata(kcontext, ifrel); + ifrel = NULL; + } + + *pac_present = ifrel; + kerr = 0; + +end: + krb5_free_authdata(kcontext, ifrel); + return kerr; +} + +krb5_error_code +ipadb_enforce_pac(krb5_context kcontext, const krb5_ticket *ticket, + const char **status) +{ + struct ipadb_context *ipactx; + bool pac_present; + krb5_error_code kerr; + + /* Filter TGTs only */ + if (!ipadb_is_tgs_princ(kcontext, ticket->server)) { + kerr = 0; + goto end; + } + + /* Get IPA context */ + ipactx = ipadb_get_context(kcontext); + if (!ipactx) { + kerr = KRB5_KDB_DBNOTINITED; + goto end; + } + + /* If local TGT but PAC generator not initialized, skip PAC enforcement */ + if (krb5_realm_compare(kcontext, ipactx->local_tgs, ticket->server) && + !ipactx->mspac) + { + krb5_klog_syslog(LOG_WARNING, "MS-PAC not available. This makes " + "FreeIPA vulnerable to privilege escalation exploit " + "(CVE-2025-7493). Please generate SIDs to enable PAC " + "support."); + kerr = 0; + goto end; + } + + /* Search for the PAC, fail if it cannot be found */ + kerr = check_for_pac(kcontext, ticket->enc_part2->authorization_data, + &pac_present); + if (kerr) { + *status = "PAC_ENFORCEMENT_CANNOT_DECODE_TGT_AUTHDATA"; + } else if (!pac_present) { + kerr = ENOENT; + *status = "PAC_ENFORCEMENT_TGT_WITHOUT_PAC"; + } + +end: + return kerr; +} + #if KRB5_KDB_DAL_MAJOR_VERSION <= 8 # ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT krb5_error_code diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c index 6ee274053..11e084739 100644 --- a/daemons/ipa-kdb/ipa_kdb_principals.c +++ b/daemons/ipa-kdb/ipa_kdb_principals.c @@ -183,25 +183,6 @@ done: return ret; } -static bool -is_tgs_princ(krb5_context kcontext, krb5_const_principal princ) -{ - krb5_data *primary; - size_t l_tgs_name; - - if (2 != krb5_princ_size(kcontext, princ)) - return false; - - primary = krb5_princ_component(kcontext, princ, 0); - - l_tgs_name = strlen(KRB5_TGS_NAME); - - if (l_tgs_name != primary->length) - return false; - - return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name); -} - static krb5_error_code ipadb_set_tl_data(krb5_db_entry *entry, krb5_int16 type, krb5_ui_2 length, @@ -1882,7 +1863,7 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext, #if KRB5_KDB_DAL_MAJOR_VERSION <= 8 /* If TGS principal, some virtual attributes may be added */ - if (is_tgs_princ(kcontext, (*entry)->princ)) { + if (ipadb_is_tgs_princ(kcontext, (*entry)->princ)) { kerr = krb5_dbe_set_string(kcontext, *entry, KRB5_KDB_SK_OPTIONAL_AD_SIGNEDPATH, "true"); -- 2.51.0