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 <jrische@redhat.com>
This commit is contained in:
Julien Rische 2024-03-19 12:24:40 +01:00 committed by Antonio Torres
parent ea37593786
commit f77c0a573c
No known key found for this signature in database
GPG Key ID: 359FAF777296F653
3 changed files with 136 additions and 92 deletions

View File

@ -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

View File

@ -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;
switch (ldap_count_entries(ipactx->lcontext, ldap_acl)) {
case 0:
break;
case -1:
kerr = EINVAL;
goto end;
default:
in_res = true;
goto end;
}
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;
}
end:
if (res)
*res = in_res;
lentry = ldap_first_entry(ipactx->lcontext, results);
if (!lentry) {
kerr = ENOENT;
goto done;
}
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;
}

View File

@ -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.