From 30f649f9027583e1b28f7c958f02f1d8772a9b71 Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Jun 10 2024 10:50:09 +0000 Subject: kdb: fix vulnerability in GCD rules handling The initial implementation of MS-SFU by MIT Kerberos was missing a condition for granting the "forwardable" flag on S4U2Self tickets. Fixing this mistake required adding 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 --- diff --git a/daemons/ipa-kdb/README.s4u2proxy.txt b/daemons/ipa-kdb/README.s4u2proxy.txt index 254fcc4..ab34aff 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 ce5409d..fbeeaaa 100644 --- a/daemons/ipa-kdb/ipa_kdb_delegation.c +++ b/daemons/ipa-kdb/ipa_kdb_delegation.c @@ -99,120 +99,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; @@ -231,7 +221,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 */ @@ -269,27 +259,54 @@ 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; } diff --git a/doc/designs/rbcd.md b/doc/designs/rbcd.md index c3665ed..fa41c57 100644 --- a/doc/designs/rbcd.md +++ b/doc/designs/rbcd.md @@ -173,6 +173,7 @@ any user. However, to make it usable for S4U2Proxy (constrained delegation), the service ticket must be forwardable. In such case the Kerberos service would be able to impersonate user and requires an explicit administrative permission. + IPA API provides a way to record this permission in both host and service command families. The following commands have option `--ok-to-auth-as-delegate=BOOL`: @@ -183,6 +184,23 @@ command families. The following commands have option This flag is equivalent to MS-SFU's `TrustedToAuthenticationForDelegation` boolean setting. +The behavior of FreeIPA regarding S4U2Self-granted tickets differs depending of +the krb5 version that was used to compile: + +* **krb5 1.20+**: KDC will always respond to S4U2Self TGS-REQ with forwardable + tickets, except if the requester principal is set as impersonator service in + at least one general constrained delegation rule (even if the rule has no + target set) +* **krb5 1.19-**: KDC will respond to all S4U2Self TGS-REQs with non-forwardable + tickets + +In both cases, granting the `ok-to-auth-as-delegate` permission to a principal +will override this default behavior and allow it to obtain forwardable tickets +to itself. In practice, it means the `ok-to-auth-as-delegate` permission is +required if you want to grant a service the special privilege to impersonate +any user against services configured as targets in a general constrained +delegation rule. + ### General constrained delegation design General constrained delegation uses two objects: a rule and a target.