ipa release 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

Signed-off-by: Julien Rische <jrische@redhat.com>
This commit is contained in:
Julien Rische 2024-04-30 15:24:22 +02:00
parent 7b21739b0c
commit 1c57bc6872
3 changed files with 898 additions and 1 deletions

View File

@ -0,0 +1,341 @@
From 0a48726e104282fb40d8f471ebb306bc9134cb0c Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
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 <jrische@redhat.com>
---
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

View File

@ -0,0 +1,548 @@
From 70d23b8f6bbcabe2eb621ffa5009866b43e5570a Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
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 <jrische@redhat.com>
---
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, &gtpol_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

View File

@ -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 <jrische@redhat.com> - 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 <rjeffman@redhat.com> - 9.4.13-9
- dcerpc: invalidate forest trust intfo cache when filtering out realm domains
Resolves: RHEL-28559