From 67a9d537e3c981e88fbddb49007e86b534b36b56 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 10 Jul 2025 11:44:36 -0400 Subject: [PATCH 1/4] Enforce uniqueness across krbprincipalname and krbcanonicalname This relies on a fix in 389-ds that extends the uniqueness plugin to be able to compare attributes with different matching syntax. This will prevent privilege escalation attacks if one of the attributes is not set on an entry if it is set elsewhere. Signed-off-by: Rob Crittenden --- install/share/unique-attributes.ldif | 28 +++++----------------------- install/updates/10-uniqueness.update | 27 +++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/install/share/unique-attributes.ldif b/install/share/unique-attributes.ldif index 60f2c3470..b28d981b5 100644 --- a/install/share/unique-attributes.ldif +++ b/install/share/unique-attributes.ldif @@ -1,34 +1,16 @@ -dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config +dn: cn=kerberos name uniqueness,cn=plugins,cn=config changetype: add objectClass: top objectClass: nsSlapdPlugin objectClass: extensibleObject -cn: krbPrincipalName uniqueness +cn: kerberos name uniqueness nsslapd-pluginPath: libattr-unique-plugin nsslapd-pluginInitfunc: NSUniqueAttr_Init nsslapd-pluginType: preoperation nsslapd-pluginEnabled: on -uniqueness-attribute-name: krbPrincipalName -nsslapd-plugin-depends-on-type: database -nsslapd-pluginId: NSUniqueAttr -nsslapd-pluginVersion: 1.1.0 -nsslapd-pluginVendor: Fedora Project -nsslapd-pluginDescription: Enforce unique attribute values -uniqueness-subtrees: $SUFFIX -uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX -uniqueness-across-all-subtrees: on - -dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config -changetype: add -objectClass: top -objectClass: nsSlapdPlugin -objectClass: extensibleObject -cn: krbCanonicalName uniqueness -nsslapd-pluginPath: libattr-unique-plugin -nsslapd-pluginInitfunc: NSUniqueAttr_Init -nsslapd-pluginType: preoperation -nsslapd-pluginEnabled: on -uniqueness-attribute-name: krbCanonicalName +uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch: +uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch: +uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch: nsslapd-plugin-depends-on-type: database nsslapd-pluginId: NSUniqueAttr nsslapd-pluginVersion: 1.1.0 diff --git a/install/updates/10-uniqueness.update b/install/updates/10-uniqueness.update index 699de3b4d..d8fc8cae3 100644 --- a/install/updates/10-uniqueness.update +++ b/install/updates/10-uniqueness.update @@ -80,13 +80,32 @@ add:uniqueness-subtree-entries-oc: posixAccount # krbPrincipalName uniqueness scopes Active/Delete containers dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config -add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX -add:uniqueness-across-all-subtrees: on +deleteentry: cn=krbPrincipalName uniqueness,cn=plugins,cn=config # krbCanonicalName uniqueness scopes Active/Delete containers dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config -add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX -add:uniqueness-across-all-subtrees: on +deleteentry: dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config + +dn: cn=kerberos name uniqueness,cn=plugins,cn=config +default:objectClass: top +default:objectClass: nsSlapdPlugin +default:objectClass: extensibleObject +default:cn: kerberos name uniqueness +default:nsslapd-pluginPath: libattr-unique-plugin +default:nsslapd-pluginInitfunc: NSUniqueAttr_Init +default:nsslapd-pluginType: preoperation +default:nsslapd-pluginEnabled: on +default:uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch: +default:uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch: +default:uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch: +default:nsslapd-plugin-depends-on-type: database +default:nsslapd-pluginId: NSUniqueAttr +default:nsslapd-pluginVersion: 1.1.0 +default:nsslapd-pluginVendor: Fedora Project +default:nsslapd-pluginDescription: Enforce unique attribute values +default:uniqueness-subtrees: $SUFFIX +default:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX +default:uniqueness-across-all-subtrees: on # ipaUniqueID uniqueness scopes Active/Delete containers dn: cn=ipaUniqueID uniqueness,cn=plugins,cn=config From bb24703a18f9f74d4ae258abf05f32a90f25e70e Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Fri, 18 Jul 2025 10:26:37 +0200 Subject: [PATCH 2/4] 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 58a0339fc..8fa050995 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -431,6 +431,14 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext, const char *test_realm, size_t size, char **trusted_realm); +/* 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, @@ -475,3 +483,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 ae7742a32..fc603cbe2 100644 --- a/daemons/ipa-kdb/ipa_kdb_common.c +++ b/daemons/ipa-kdb/ipa_kdb_common.c @@ -767,3 +767,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 2802221c7..aa8de3eda 100644 --- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c +++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c @@ -195,7 +195,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 9723103d8..9a7aedd84 100644 --- a/daemons/ipa-kdb/ipa_kdb_mspac.c +++ b/daemons/ipa-kdb/ipa_kdb_mspac.c @@ -3346,3 +3346,90 @@ 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; +} diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c index 14603e528..0a91975d2 100644 --- a/daemons/ipa-kdb/ipa_kdb_principals.c +++ b/daemons/ipa-kdb/ipa_kdb_principals.c @@ -188,25 +188,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 cmp_local_tgs_princ(krb5_context kcontext, const char *local_realm, krb5_const_principal princ, bool *result) @@ -1986,7 +1967,7 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext, return kerr; /* 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 = cmp_local_tgs_princ(kcontext, ipactx->realm, (*entry)->princ, &is_local_tgs_princ); if (kerr) From 20c03a3e89c920ae8c4f9b1f18fd515f661fc622 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Thu, 28 Aug 2025 15:31:39 +0200 Subject: [PATCH 3/4] ipatests: extend test for unique krbcanonicalname Add a test ensuring that root@REALM cannot be added as krbcanonicalname Add a test for PAC enforcement: try to access a service using a TGT obtained without PAC. Should fail as PAC is now enforced. Signed-off-by: Florence Blanc-Renaud --- ipatests/test_integration/test_commands.py | 44 ++++++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py index 421e69043..328eb0c78 100644 --- a/ipatests/test_integration/test_commands.py +++ b/ipatests/test_integration/test_commands.py @@ -1891,7 +1891,7 @@ class TestIPACommandWithoutReplica(IntegrationTest): hostname = master.hostname realm = master.domain.realm principal = f'test/{hostname}@{realm}' - entry_ldif = textwrap.dedent(""" + entry_ldif_template = textwrap.dedent(""" dn: krbprincipalname={principal},cn=services,cn=accounts,{base_dn} changetype: add ipakrbprincipalalias: test/{hostname}@{realm} @@ -1902,13 +1902,15 @@ class TestIPACommandWithoutReplica(IntegrationTest): objectclass: krbprincipal objectclass: krbprincipalaux objectclass: top - krbcanonicalname: admin@{realm} + krbcanonicalname: {user}@{realm} managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn} - """).format( + """) + entry_ldif = entry_ldif_template.format( base_dn=base_dn, hostname=hostname, principal=principal, - realm=realm) + realm=realm, + user='admin') tasks.kdestroy_all(master) master.run_command( ['kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}']) @@ -1921,6 +1923,40 @@ class TestIPACommandWithoutReplica(IntegrationTest): raiseonerr=False) assert "entry with the same attribute value" in result.stderr_text + # Now try with root@realm instead of admin@realm + entry_ldif = entry_ldif_template.format( + base_dn=base_dn, + hostname=hostname, + principal=principal, + realm=realm, + user='root') + args = [ + 'ldapmodify', + '-Y', + 'GSSAPI' + ] + result = master.run_command(args, stdin_text=entry_ldif, + raiseonerr=False) + assert "entry with the same attribute value" in result.stderr_text + tasks.kdestroy_all(master) + + def test_no_request_pac(self): + # Try to use a TGT obtained without PAC + # Should fail as the presence of the PAC when processing TGTs + # provided by TGS-REQ is now enforced. + hostname = self.master.hostname + realm = self.master.domain.realm + self.master.run_command([ + 'kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}', + '--no-request-pac' + ]) + result = self.master.run_command( + ['kvno', f'ldap/{hostname}@{realm}'], + raiseonerr=False + ) + assert result.returncode == 1 + assert "PAC_ENFORCEMENT_TGT_WITHOUT_PAC" in result.stderr_text + class TestIPAautomount(IntegrationTest): @classmethod From 96ea6f94c898f4458edcbb18de21338095dd5664 Mon Sep 17 00:00:00 2001 From: Antonio Torres Date: Tue, 30 Sep 2025 10:22:09 +0200 Subject: [PATCH 4/4] Become IPA 4.12.5 Signed-off-by: Antonio Torres --- VERSION.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.m4 b/VERSION.m4 index 8aa7f65c6..b6ab9e457 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -21,7 +21,7 @@ ######################################################## define(IPA_VERSION_MAJOR, 4) define(IPA_VERSION_MINOR, 12) -define(IPA_VERSION_RELEASE, 4) +define(IPA_VERSION_RELEASE, 5) ######################################################## # For 'pre' releases the version will be #