diff --git a/0027-kdb-fix-vulnerability-in-GCD-rules-handling.patch b/0027-kdb-fix-vulnerability-in-GCD-rules-handling.patch new file mode 100644 index 0000000..028fc04 --- /dev/null +++ b/0027-kdb-fix-vulnerability-in-GCD-rules-handling.patch @@ -0,0 +1,341 @@ +From 0a48726e104282fb40d8f471ebb306bc9134cb0c Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Tue, 19 Mar 2024 12:24:40 +0100 +Subject: [PATCH] kdb: fix vulnerability in GCD rules handling + +The initial implementation of MS-SFU by MIT Kerberos was missing some +a condition for granting the "forwardable" flag on S4U2Self tickets. +Fixing this mistake required to add a special case for the +check_allowed_to_delegate() function: if the target service argument is +NULL, then it means the KDC is probing for general constrained +delegation rules, not actually checking a specific S4U2Proxy request. + +In commit e86807b5, the behavior of ipadb_match_acl() was modified to +match the changes from upstream MIT Kerberos a441fbe3. However, a +mistake resulted in this mechanism to apply in cases where target +service argument is set AND unset. This results in S4U2Proxy requests to +be accepted regardless of the fact there is a matching service +delegation rule or not. + +This vulnerability does not affect services having RBCD (resource-based +constrained delegation) rules. + +This fixes CVE-2024-2698 + +Signed-off-by: Julien Rische +--- + daemons/ipa-kdb/README.s4u2proxy.txt | 19 ++- + daemons/ipa-kdb/ipa_kdb_delegation.c | 191 +++++++++++++++------------ + 2 files changed, 118 insertions(+), 92 deletions(-) + +diff --git a/daemons/ipa-kdb/README.s4u2proxy.txt b/daemons/ipa-kdb/README.s4u2proxy.txt +index 254fcc4d1..ab34aff36 100644 +--- a/daemons/ipa-kdb/README.s4u2proxy.txt ++++ b/daemons/ipa-kdb/README.s4u2proxy.txt +@@ -12,9 +12,7 @@ much more easily managed. + + The grouping mechanism has been built so that lookup is highly optimized + and is basically reduced to a single search that uses the derefernce +-control. Speed is very important in this case because KDC operations +-time out very quickly and unless we add a caching layer in ipa-kdb we +-must keep the number of searches down to avoid client timeouts. ++control. + + The grouping mechanism is very simple a groupOfPrincipals object is + introduced, this Auxiliary class have a single optional attribute called +@@ -112,8 +110,7 @@ kinit -kt /etc/httpd/conf/ipa.keytab HTTP/ipaserver.example.com + kvno -U admin HTTP/ipaserver.example.com + + # Perform S4U2Proxy +-kvno -k /etc/httpd/conf/ipa.keytab -U admin -P HTTP/ipaserver.example.com +-ldap/ipaserver.example.com ++kvno -U admin -P ldap/ipaserver.example.com + + + If this works it means you successfully impersonated the admin user with +@@ -125,6 +122,18 @@ modprinc -ok_to_auth_as_delegate HTTP/ipaserver.example.com + Simo. + + ++If IPA is compiled with krb5 1.20 and newer (KDB DAL >= 9), then the ++behavior of S4U2Self changes: S4U2Self TGS-REQs produce forwardable ++tickets for all requesters, except if the requester principal is set as ++the proxy (impersonating service) in at least one `servicedelegation` ++rule. In this case, even if the rule has no target, the KDC will ++response to S4U2Self requests with a non-forwardable ticket. Hence, ++granting the `ok_to_auth_as_delegate` permission to the proxy service ++remains the only way for this service to obtain the evidence ticket ++required for general constrained delegation requests if this ticket is ++not provided by the client. ++ ++ + [1] + Note that here I use the term proxy in a different way than it is used in + the krb interfaces. It may seem a bit confusing but I think people will +diff --git a/daemons/ipa-kdb/ipa_kdb_delegation.c b/daemons/ipa-kdb/ipa_kdb_delegation.c +index de82174ad..3581f3c79 100644 +--- a/daemons/ipa-kdb/ipa_kdb_delegation.c ++++ b/daemons/ipa-kdb/ipa_kdb_delegation.c +@@ -91,120 +91,110 @@ static bool ipadb_match_member(char *princ, LDAPDerefRes *dres) + return false; + } + +-static krb5_error_code ipadb_match_acl(krb5_context kcontext, +- LDAPMessage *results, +- krb5_const_principal client, +- krb5_const_principal target) ++#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 ++static krb5_error_code ++ipadb_has_acl(krb5_context kcontext, LDAPMessage *ldap_acl, bool *res) + { + struct ipadb_context *ipactx; +- krb5_error_code kerr; +- LDAPMessage *lentry; +- LDAPDerefRes *deref_results; +- LDAPDerefRes *dres; +- char *client_princ = NULL; +- char *target_princ = NULL; +- bool client_missing; +- bool client_found; +- bool target_found; +- bool is_constraint_delegation = false; +- size_t nrules = 0; +- int ret; ++ bool in_res = false; ++ krb5_error_code kerr = 0; + + ipactx = ipadb_get_context(kcontext); +- if (!ipactx) { ++ if (!ipactx) + return KRB5_KDB_DBNOTINITED; +- } + +- if ((client != NULL) && (target != NULL)) { +- kerr = krb5_unparse_name(kcontext, client, &client_princ); +- if (kerr != 0) { +- goto done; +- } +- kerr = krb5_unparse_name(kcontext, target, &target_princ); +- if (kerr != 0) { +- goto done; +- } +- } else { +- is_constraint_delegation = true; ++ switch (ldap_count_entries(ipactx->lcontext, ldap_acl)) { ++ case 0: ++ break; ++ case -1: ++ kerr = EINVAL; ++ goto end; ++ default: ++ in_res = true; ++ goto end; + } + +- lentry = ldap_first_entry(ipactx->lcontext, results); +- if (!lentry) { +- kerr = ENOENT; +- goto done; +- } ++end: ++ if (res) ++ *res = in_res; ++ ++ return kerr; ++} ++#endif ++ ++static krb5_error_code ++ipadb_match_acl(krb5_context kcontext, LDAPMessage *ldap_acl, ++ krb5_const_principal client, krb5_const_principal target) ++{ ++ struct ipadb_context *ipactx; ++ LDAPMessage *rule; ++ LDAPDerefRes *acis, *aci; ++ char *client_princ = NULL, *target_princ= NULL; ++ bool client_missing, client_found, target_found; ++ int lerr; ++ krb5_error_code kerr; ++ ++ ipactx = ipadb_get_context(kcontext); ++ if (!ipactx) ++ return KRB5_KDB_DBNOTINITED; ++ ++ kerr = krb5_unparse_name(kcontext, client, &client_princ); ++ if (kerr) ++ goto end; ++ ++ kerr = krb5_unparse_name(kcontext, target, &target_princ); ++ if (kerr) ++ goto end; + + /* the default is that we fail */ +- kerr = ENOENT; ++ kerr = KRB5KDC_ERR_BADOPTION; + +- while (lentry) { ++ for (rule = ldap_first_entry(ipactx->lcontext, ldap_acl); ++ rule; ++ rule = ldap_next_entry(ipactx->lcontext, rule)) ++ { + /* both client and target must be found in the same ACI */ + client_missing = true; + client_found = false; + target_found = false; + +- ret = ipadb_ldap_deref_results(ipactx->lcontext, lentry, +- &deref_results); +- switch (ret) { ++ lerr = ipadb_ldap_deref_results(ipactx->lcontext, rule, &acis); ++ switch (lerr) { + case 0: +- for (dres = deref_results; dres; dres = dres->next) { +- nrules++; +- if (is_constraint_delegation) { +- /* +- Microsoft revised the S4U2Proxy rules for forwardable +- tickets. All S4U2Proxy operations require forwardable +- evidence tickets, but S4U2Self should issue a +- forwardable ticket if the requesting service has no +- ok-to-auth-as-delegate bit but also no constrained +- delegation privileges for traditional S4U2Proxy. +- Implement these rules, extending the +- check_allowed_to_delegate() DAL method so that the KDC +- can ask if a principal has any delegation privileges. +- +- Since target principal is NULL and client principal is +- NULL in this case, we simply calculate number of rules associated +- with the server principal to decide whether to deny forwardable bit +- */ +- continue; +- } +- if (client_found == false && +- strcasecmp(dres->derefAttr, "ipaAllowToImpersonate") == 0) { ++ for (aci = acis; aci; aci = aci->next) { ++ if (!client_found && ++ 0 == strcasecmp(aci->derefAttr, "ipaAllowToImpersonate")) ++ { + /* NOTE: client_missing is used to signal that the + * attribute was completely missing. This signals that + * ANY client is allowed to be impersonated. + * This logic is valid only for clients, not for targets */ + client_missing = false; +- client_found = ipadb_match_member(client_princ, dres); ++ client_found = ipadb_match_member(client_princ, aci); + } +- if (target_found == false && +- strcasecmp(dres->derefAttr, "ipaAllowedTarget") == 0) { +- target_found = ipadb_match_member(target_princ, dres); ++ if (!target_found && ++ 0 == strcasecmp(aci->derefAttr, "ipaAllowedTarget")) ++ { ++ target_found = ipadb_match_member(target_princ, aci); + } + } + +- ldap_derefresponse_free(deref_results); ++ ldap_derefresponse_free(acis); + break; + case ENOENT: + break; + default: +- kerr = ret; +- goto done; ++ kerr = lerr; ++ goto end; + } + +- if ((client_found == true || client_missing == true) && +- target_found == true) { ++ if ((client_found || client_missing) && target_found) { + kerr = 0; +- goto done; ++ goto end; + } +- +- lentry = ldap_next_entry(ipactx->lcontext, lentry); +- } +- +- if (nrules > 0) { +- kerr = 0; + } + +-done: ++end: + krb5_free_unparsed_name(kcontext, client_princ); + krb5_free_unparsed_name(kcontext, target_princ); + return kerr; +@@ -223,7 +213,7 @@ krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, + char *srv_principal = NULL; + krb5_db_entry *proxy_entry = NULL; + struct ipadb_e_data *ied_server, *ied_proxy; +- LDAPMessage *res = NULL; ++ LDAPMessage *ldap_gcd_acl = NULL; + + if (proxy != NULL) { + /* Handle the case where server == proxy, this is allowed in S4U */ +@@ -261,26 +251,53 @@ krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, + goto done; + } + +- kerr = ipadb_get_delegation_acl(kcontext, srv_principal, &res); ++ /* Load general constrained delegation rules */ ++ kerr = ipadb_get_delegation_acl(kcontext, srv_principal, &ldap_gcd_acl); + if (kerr) { + goto done; + } + +- kerr = ipadb_match_acl(kcontext, res, client, proxy); +- if (kerr) { +- goto done; ++#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 ++ /* ++ * Microsoft revised the S4U2Proxy rules for forwardable tickets. All ++ * S4U2Proxy operations require forwardable evidence tickets, but ++ * S4U2Self should issue a forwardable ticket if the requesting service ++ * has no ok-to-auth-as-delegate bit but also no constrained delegation ++ * privileges for traditional S4U2Proxy. Implement these rules, ++ * extending the check_allowed_to_delegate() DAL method so that the KDC ++ * can ask if a principal has any delegation privileges. ++ * ++ * If target service principal is NULL, and the impersonating service has ++ * at least one GCD rule, then succeed. ++ */ ++ if (!proxy) { ++ bool has_gcd_rules; ++ ++ kerr = ipadb_has_acl(kcontext, ldap_gcd_acl, &has_gcd_rules); ++ if (!kerr) ++ kerr = has_gcd_rules ? 0 : KRB5KDC_ERR_BADOPTION; ++ } else if (client) { ++#else ++ if (client && proxy) { ++#endif ++ kerr = ipadb_match_acl(kcontext, ldap_gcd_acl, client, proxy); ++ } else { ++ /* client and/or proxy is missing */ ++ kerr = KRB5KDC_ERR_BADOPTION; + } ++ if (kerr) ++ goto done; + + done: + if (kerr) { +-#if KRB5_KDB_DAL_MAJOR_VERSION < 9 +- kerr = KRB5KDC_ERR_POLICY; +-#else ++#if KRB5_KDB_DAL_MAJOR_VERSION >= 9 + kerr = KRB5KDC_ERR_BADOPTION; ++#else ++ kerr = KRB5KDC_ERR_POLICY; + #endif + } + ipadb_free_principal(kcontext, proxy_entry); + krb5_free_unparsed_name(kcontext, srv_principal); +- ldap_msgfree(res); ++ ldap_msgfree(ldap_gcd_acl); + return kerr; + } +-- +2.44.0 + diff --git a/0028-kdb-apply-combinatorial-logic-for-ticket-flags.patch b/0028-kdb-apply-combinatorial-logic-for-ticket-flags.patch new file mode 100644 index 0000000..7e0a72c --- /dev/null +++ b/0028-kdb-apply-combinatorial-logic-for-ticket-flags.patch @@ -0,0 +1,548 @@ +From 70d23b8f6bbcabe2eb621ffa5009866b43e5570a Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Mon, 25 Mar 2024 18:25:52 +0200 +Subject: [PATCH] kdb: apply combinatorial logic for ticket flags + +The initial design for ticket flags was implementing this logic: +* If a ticket policy is defined for the principal entry, use flags from + this policy if they are set. Otherwise, use default ticket flags. +* If no ticket policy is defined for the principal entry, but there is a + global one, use flags from the global ticket policy if they are set. + Otherwise, use default ticket flags. +* If no policy (principal nor global) is defined, use default ticket + flags. + +However, this logic was broken by a1165ffb which introduced creation of +a principal-level ticket policy in case the ticket flag set is modified. +This was typically the case for the -allow_tix flag, which was set +virtually by the KDB driver when a user was locked until they initialize +their password on first kinit pre-authentication. + +This was causing multiple issues, which are mitigated by the new +approach: + +Now flags from each level are combined together. There flags like ++requires_preauth which are set systematically by the KDB diver, as +well as -allow_tix which is set based on the value of "nsAccountLock". +This commit also adds the implicit -allow_svr ticket flag for user +principals to protect users against Kerberoast-type attacks. None of +these flags are stored in the LDAP database, they are hard-coded in the +KDB driver. + +In addition to these "virtual" ticket flags, flags from both global and +principal ticket policies are applied (if these policies exist). + +Principal ticket policies are not supported for hosts and services, but +this is only an HTTP API limitation. The "krbTicketPolicyAux" object +class is supported for all account types. This is required for ticket +flags like +ok_to_auth_as_delegate. Such flags can be set using "ipa +host-mod" and "ipa serivce-mod", or using kadmin's "modprinc". + +It is possible to ignore flags from the global ticket policy or default +flags like -allow_svr for a user principal by setting the +"final_user_tkt_flags" string attribute to "true" in kadmin. In this +case, any ticket flag can be configured in the principal ticket policy, +except requires_preauth and allow_tix. + +This fixes CVE-2024-3183 + +Signed-off-by: Julien Rische +--- + daemons/ipa-kdb/ipa_kdb.h | 43 ++++ + daemons/ipa-kdb/ipa_kdb_principals.c | 306 ++++++++++++++++++++++----- + util/ipa_krb5.c | 18 ++ + util/ipa_krb5.h | 4 + + 4 files changed, 319 insertions(+), 52 deletions(-) + +diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h +index 7baf4697f..85cabe142 100644 +--- a/daemons/ipa-kdb/ipa_kdb.h ++++ b/daemons/ipa-kdb/ipa_kdb.h +@@ -94,6 +94,34 @@ + #define IPA_KRB_AUTHZ_DATA_ATTR "ipaKrbAuthzData" + #define IPA_USER_AUTH_TYPE "ipaUserAuthType" + ++/* Virtual managed ticket flags like "-allow_tix", are always controlled by the ++ * "nsAccountLock" attribute, such flags should never be set in the database. ++ * The following expression combine all of them, and is used to filter them ++ * out. */ ++#define IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL (KRB5_KDB_DISALLOW_ALL_TIX) ++ ++/* Virtual static ticket flags are hard-coded in the KDB driver. */ ++/* Virtual static mandatory flags are set systematically and implicitly for all ++ * principals. They are filtered out from database ticket flags updates. ++ * (However, "KRB5_KDB_REQUIRES_PRE_AUTH" can still be unset by the ++ * "KDC:Disable Default Preauth for SPNs" global setting) */ ++#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY (KRB5_KDB_REQUIRES_PRE_AUTH) ++/* Virtual static default ticket flags are implicitly set for user and non-user ++ * (SPN) principals, and not stored in the database. ++ * (Except if the "IPA_KDB_STRATTR_FINAL_TKTFLAGS" string attribute is "true" ++ * the principal) */ ++/* Virtual static default user ticket flags are set for users only. The ++ * "-allow_svr" flag is set to protect them from CVE-2024-3183. */ ++#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER (KRB5_KDB_DISALLOW_SVR) ++#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN (0) ++ ++/* If this string attribute is set to "true", then only the virtual managed and ++ * virtual static mandatory ticket flags are applied and filtered out from ++ * database read and write operations for the concerned user principal. ++ * Configurable principal ticket flags are applied, but not the configurable ++ * global ticket policy flags. */ ++#define IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS "final_user_tkt_flags" ++ + struct ipadb_mspac; + struct dom_sid; + +@@ -178,6 +206,21 @@ struct ipadb_e_data { + struct dom_sid *sid; + }; + ++inline static krb5_error_code ++ipadb_get_edata(krb5_db_entry *entry, struct ipadb_e_data **ied) ++{ ++ struct ipadb_e_data *in_ied; ++ ++ in_ied = (struct ipadb_e_data *)entry->e_data; ++ if (!in_ied || in_ied->magic != IPA_E_DATA_MAGIC) ++ return EINVAL; ++ ++ if (ied) ++ *ied = in_ied; ++ ++ return 0; ++} ++ + struct ipadb_context *ipadb_get_context(krb5_context kcontext); + int ipadb_get_connection(struct ipadb_context *ipactx); + +diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c +index 07cc87746..cb18861bb 100644 +--- a/daemons/ipa-kdb/ipa_kdb_principals.c ++++ b/daemons/ipa-kdb/ipa_kdb_principals.c +@@ -706,9 +706,10 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, + "krbTicketFlags", &result); + if (ret == 0) { + entry->attributes = result; +- } else { +- *polmask |= TKTFLAGS_BIT; + } ++ /* Since principal, global policy, and virtual ticket flags are combined, ++ * they must always be resolved. */ ++ *polmask |= TKTFLAGS_BIT; + + ret = ipadb_ldap_attr_to_int(lcontext, lentry, + "krbMaxTicketLife", &result); +@@ -1251,23 +1252,150 @@ done: + return ret; + } + +-static krb5_flags maybe_require_preauth(struct ipadb_context *ipactx, +- krb5_db_entry *entry) ++static krb5_error_code ++are_final_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry, ++ bool *final_tktflags) + { +- const struct ipadb_global_config *config; ++ krb5_error_code kerr; + struct ipadb_e_data *ied; ++ char *str = NULL; ++ bool in_final_tktflags = false; + +- config = ipadb_get_global_config(ipactx); +- if (config && config->disable_preauth_for_spns) { +- ied = (struct ipadb_e_data *)entry->e_data; +- if (ied && ied->ipa_user != true) { +- /* not a user, assume SPN */ +- return 0; +- } ++ kerr = ipadb_get_edata(entry, &ied); ++ if (kerr) ++ goto end; ++ ++ if (!ied->ipa_user) { ++ kerr = 0; ++ goto end; ++ } ++ ++ kerr = krb5_dbe_get_string(ipactx->kcontext, entry, ++ IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS, &str); ++ if (kerr) ++ goto end; ++ ++ in_final_tktflags = str && ipa_krb5_parse_bool(str); ++ ++end: ++ if (final_tktflags) ++ *final_tktflags = in_final_tktflags; ++ ++ krb5_dbe_free_string(ipactx->kcontext, str); ++ return kerr; ++} ++ ++static krb5_error_code ++add_virtual_static_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry, ++ krb5_flags *tktflags) ++{ ++ krb5_error_code kerr; ++ krb5_flags vsflg; ++ bool final_tktflags; ++ const struct ipadb_global_config *gcfg; ++ struct ipadb_e_data *ied; ++ ++ vsflg = IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY; ++ ++ kerr = ipadb_get_edata(entry, &ied); ++ if (kerr) ++ goto end; ++ ++ kerr = are_final_tktflags(ipactx, entry, &final_tktflags); ++ if (kerr) ++ goto end; ++ ++ /* In practice, principal ticket flags cannot be final for SPNs. */ ++ if (!final_tktflags) ++ vsflg |= ied->ipa_user ? IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER ++ : IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN; ++ ++ if (!ied->ipa_user) { ++ gcfg = ipadb_get_global_config(ipactx); ++ if (gcfg && gcfg->disable_preauth_for_spns) ++ vsflg &= ~KRB5_KDB_REQUIRES_PRE_AUTH; + } + +- /* By default require preauth for all principals */ +- return KRB5_KDB_REQUIRES_PRE_AUTH; ++ if (tktflags) ++ *tktflags |= vsflg; ++ ++end: ++ return kerr; ++} ++ ++static krb5_error_code ++get_virtual_static_tktflags_mask(struct ipadb_context *ipactx, ++ krb5_db_entry *entry, krb5_flags *mask) ++{ ++ krb5_error_code kerr; ++ krb5_flags flags = IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL; ++ ++ kerr = add_virtual_static_tktflags(ipactx, entry, &flags); ++ if (kerr) ++ goto end; ++ ++ if (mask) ++ *mask = ~flags; ++ ++ kerr = 0; ++ ++end: ++ return kerr; ++} ++ ++/* Add ticket flags from the global ticket policy if it exists, otherwise ++ * succeed. If the global ticket policy is set, the "exists" parameter is set to ++ * true. */ ++static krb5_error_code ++add_global_ticket_policy_flags(struct ipadb_context *ipactx, ++ bool *gtpol_exists, krb5_flags *tktflags) ++{ ++ krb5_error_code kerr; ++ char *policy_dn; ++ char *tktflags_attr[] = { "krbticketflags", NULL }; ++ LDAPMessage *res = NULL, *first; ++ int ec, ldap_tktflags; ++ bool in_gtpol_exists = false; ++ ++ ec = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", ipactx->realm, ++ ipactx->base); ++ if (-1 == ec) { ++ kerr = ENOMEM; ++ goto end; ++ } ++ ++ kerr = ipadb_simple_search(ipactx, policy_dn, LDAP_SCOPE_BASE, ++ "(objectclass=krbticketpolicyaux)", ++ tktflags_attr, &res); ++ if (kerr) { ++ if (KRB5_KDB_NOENTRY == kerr) ++ kerr = 0; ++ goto end; ++ } ++ ++ first = ldap_first_entry(ipactx->lcontext, res); ++ if (!first) { ++ kerr = 0; ++ goto end; ++ } ++ ++ in_gtpol_exists = true; ++ ++ ec = ipadb_ldap_attr_to_int(ipactx->lcontext, first, "krbticketflags", ++ &ldap_tktflags); ++ if (0 == ec && tktflags) { ++ *tktflags |= (krb5_flags)ldap_tktflags; ++ } ++ ++ kerr = 0; ++ ++end: ++ if (gtpol_exists) ++ *gtpol_exists = in_gtpol_exists; ++ ++ ldap_msgfree(res); ++ free(policy_dn); ++ return kerr; + } + + static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, +@@ -1280,6 +1408,7 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, + char *policy_dn = NULL; + LDAPMessage *res = NULL; + LDAPMessage *first; ++ bool final_tktflags, has_local_tktpolicy = true; + int result; + int ret; + +@@ -1288,12 +1417,18 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, + return KRB5_KDB_DBNOTINITED; + } + ++ kerr = are_final_tktflags(ipactx, entry, &final_tktflags); ++ if (kerr) ++ goto done; ++ + ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry, + "krbticketpolicyreference", &policy_dn); + switch (ret) { + case 0: + break; + case ENOENT: ++ /* If no principal ticket policy, fallback to the global one. */ ++ has_local_tktpolicy = false; + ret = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", + ipactx->realm, ipactx->base); + if (ret == -1) { +@@ -1337,12 +1472,13 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, + } + } + if (polmask & TKTFLAGS_BIT) { +- ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first, +- "krbticketflags", &result); +- if (ret == 0) { +- entry->attributes |= result; +- } else { +- entry->attributes |= maybe_require_preauth(ipactx, entry); ++ /* If global ticket policy is being applied, set flags only if ++ * user principal ticket flags are not final. */ ++ if (has_local_tktpolicy || !final_tktflags) { ++ ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first, ++ "krbticketflags", &result); ++ if (ret == 0) ++ entry->attributes |= result; + } + } + +@@ -1366,13 +1502,27 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, + if (polmask & MAXRENEWABLEAGE_BIT) { + entry->max_renewable_life = 604800; + } +- if (polmask & TKTFLAGS_BIT) { +- entry->attributes |= maybe_require_preauth(ipactx, entry); +- } + + kerr = 0; + } + ++ if (polmask & TKTFLAGS_BIT) { ++ /* If the principal ticket flags were applied, then flags from the ++ * global ticket policy has to be applied atop of them if user principal ++ * ticket flags are not final. */ ++ if (has_local_tktpolicy && !final_tktflags) { ++ kerr = add_global_ticket_policy_flags(ipactx, NULL, ++ &entry->attributes); ++ if (kerr) ++ goto done; ++ } ++ ++ /* Virtual static ticket flags are set regardless of database content */ ++ kerr = add_virtual_static_tktflags(ipactx, entry, &entry->attributes); ++ if (kerr) ++ goto done; ++ } ++ + done: + ldap_msgfree(res); + free(policy_dn); +@@ -2275,6 +2425,85 @@ static krb5_error_code ipadb_get_ldap_mod_auth_ind(krb5_context kcontext, + return ret; + } + ++static krb5_error_code ++update_tktflags(krb5_context kcontext, struct ipadb_mods *imods, ++ krb5_db_entry *entry, int mod_op) ++{ ++ krb5_error_code kerr; ++ struct ipadb_context *ipactx; ++ struct ipadb_e_data *ied; ++ bool final_tktflags; ++ krb5_flags tktflags_mask; ++ int tktflags; ++ ++ kerr = ipadb_get_edata(entry, &ied); ++ if (kerr) ++ goto end; ++ ++ ipactx = ipadb_get_context(kcontext); ++ if (!ipactx) { ++ kerr = KRB5_KDB_DBNOTINITED; ++ goto end; ++ } ++ ++ kerr = get_virtual_static_tktflags_mask(ipactx, entry, &tktflags_mask); ++ if (kerr) ++ goto end; ++ ++ kerr = are_final_tktflags(ipactx, entry, &final_tktflags); ++ if (kerr) ++ goto end; ++ ++ /* Flags from the global ticket policy are filtered out only if the user ++ * principal flags are not final. */ ++ if (!final_tktflags) { ++ krb5_flags gtpol_tktflags = 0; ++ ++ kerr = add_global_ticket_policy_flags(ipactx, NULL, >pol_tktflags); ++ if (kerr) ++ goto end; ++ ++ tktflags_mask &= ~gtpol_tktflags; ++ } ++ ++ tktflags = (int)(entry->attributes & tktflags_mask); ++ ++ if (LDAP_MOD_REPLACE == mod_op && !ied->has_tktpolaux) { ++ if (0 == tktflags) { ++ /* No point initializing principal ticket policy if there are no ++ * flags left after filtering out virtual and global ticket policy ++ * ones. */ ++ kerr = 0; ++ goto end; ++ } ++ ++ /* if the object does not have the krbTicketPolicyAux class ++ * we need to add it or this will fail, only for modifications. ++ * We always add this objectclass by default when doing an add ++ * from scratch. */ ++ kerr = ipadb_get_ldap_mod_str(imods, "objectclass", ++ "krbTicketPolicyAux", LDAP_MOD_ADD); ++ if (kerr) ++ goto end; ++ } ++ ++ kerr = ipadb_get_ldap_mod_int(imods, "krbTicketFlags", tktflags, mod_op); ++ if (kerr) ++ goto end; ++ ++ /* If there are no custom ticket flags set in the principal, remove the ++ * "krbTicketFlags" attribute. */ ++ if (0 == tktflags) { ++ kerr = ipadb_get_ldap_mod_int(imods, "krbTicketFlags", tktflags, ++ LDAP_MOD_DELETE); ++ if (kerr) ++ goto end; ++ } ++ ++end: ++ return kerr; ++} ++ + static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext, + struct ipadb_mods *imods, + krb5_db_entry *entry, +@@ -2350,36 +2579,9 @@ static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext, + + /* KADM5_ATTRIBUTES */ + if (entry->mask & KMASK_ATTRIBUTES) { +- /* if the object does not have the krbTicketPolicyAux class +- * we need to add it or this will fail, only for modifications. +- * We always add this objectclass by default when doing an add +- * from scratch. */ +- if ((mod_op == LDAP_MOD_REPLACE) && entry->e_data) { +- struct ipadb_e_data *ied; +- +- ied = (struct ipadb_e_data *)entry->e_data; +- if (ied->magic != IPA_E_DATA_MAGIC) { +- kerr = EINVAL; +- goto done; +- } +- +- if (!ied->has_tktpolaux) { +- kerr = ipadb_get_ldap_mod_str(imods, "objectclass", +- "krbTicketPolicyAux", +- LDAP_MOD_ADD); +- if (kerr) { +- goto done; +- } +- } +- } +- +- kerr = ipadb_get_ldap_mod_int(imods, +- "krbTicketFlags", +- (int)entry->attributes, +- mod_op); +- if (kerr) { ++ kerr = update_tktflags(kcontext, imods, entry, mod_op); ++ if (kerr) + goto done; +- } + } + + /* KADM5_MAX_LIFE */ +diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c +index 1ba6d25ee..2e663c506 100644 +--- a/util/ipa_krb5.c ++++ b/util/ipa_krb5.c +@@ -38,6 +38,12 @@ const char *ipapwd_password_max_len_errmsg = \ + TOSTR(IPAPWD_PASSWORD_MAX_LEN) \ + " chars)!"; + ++/* Case-insensitive string values to by parsed as boolean true */ ++static const char *const conf_yes[] = { ++ "y", "yes", "true", "t", "1", "on", ++ NULL, ++}; ++ + /* Salt types */ + #define KRB5P_SALT_SIZE 16 + +@@ -1237,3 +1243,15 @@ done: + } + return ret; + } ++ ++bool ipa_krb5_parse_bool(const char *str) ++{ ++ const char *const *p; ++ ++ for (p = conf_yes; *p; p++) { ++ if (!strcasecmp(*p, str)) ++ return true; ++ } ++ ++ return false; ++} +diff --git a/util/ipa_krb5.h b/util/ipa_krb5.h +index 7d2ebae98..d0280940a 100644 +--- a/util/ipa_krb5.h ++++ b/util/ipa_krb5.h +@@ -174,3 +174,7 @@ static inline bool + krb5_ts_after(krb5_timestamp a, krb5_timestamp b) { + return (uint32_t)a > (uint32_t)b; + } ++ ++/* Implement boolean string parsing function from MIT krb5: ++ * src/lib/krb5/krb/libdef_parse.c:_krb5_conf_boolean() */ ++bool ipa_krb5_parse_bool(const char *str); +-- +2.44.0 + diff --git a/ipa.spec b/ipa.spec index a7f2e9f..65595ba 100644 --- a/ipa.spec +++ b/ipa.spec @@ -190,7 +190,7 @@ Name: %{package_name} Version: %{IPA_VERSION} -Release: 9%{?rc_version:.%rc_version}%{?dist} +Release: 10%{?rc_version:.%rc_version}%{?dist} Summary: The Identity, Policy and Audit system License: GPLv3+ @@ -235,6 +235,8 @@ Patch0023: 0023-rpcserver-validate-Kerberos-principal-name-before-running-k Patch0024: 0024-Vault-add-additional-fallback-to-RSA-OAEP-wrapping-algo_rhel#28259.patch Patch0025: 0025-dcerpc-invalidate-forest-trust-intfo-cache-when-filtering-out-realm-domains_rhel#28559.patch Patch0026: 0026-backport-test-fixes_rhel#29908.patch +Patch0027: 0027-kdb-fix-vulnerability-in-GCD-rules-handling.patch +Patch0028: 0028-kdb-apply-combinatorial-logic-for-ticket-flags.patch %if 0%{?rhel} >= 8 Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch Patch1002: 1002-Revert-freeipa.spec-depend-on-bind-dnssec-utils.patch @@ -1750,6 +1752,12 @@ fi %endif %changelog +* Tue Apr 30 2024 Julien Rische - 4.9.13-10 +- kdb: apply combinatorial logic for ticket flags (CVE-2024-3183) + Resolves: RHEL-29927 +- kdb: fix vulnerability in GCD rules handling (CVE-2024-2698) + Resolves: RHEL-29692 + * Fri Apr 12 2024 Rafael Jeffman - 9.4.13-9 - dcerpc: invalidate forest trust intfo cache when filtering out realm domains Resolves: RHEL-28559