|
|
665228 |
From 997e1bbb2ec662357089aa43763e138183860cc3 Mon Sep 17 00:00:00 2001
|
|
|
665228 |
From: Greg Hudson <ghudson@mit.edu>
|
|
|
665228 |
Date: Fri, 12 Jan 2018 11:43:01 -0500
|
|
|
665228 |
Subject: [PATCH] Fix flaws in LDAP DN checking
|
|
|
665228 |
|
|
|
665228 |
KDB_TL_USER_INFO tl-data is intended to be internal to the LDAP KDB
|
|
|
665228 |
module, and not used in disk or wire principal entries. Prevent
|
|
|
665228 |
kadmin clients from sending KDB_TL_USER_INFO tl-data by giving it a
|
|
|
665228 |
type number less than 256 and filtering out type numbers less than 256
|
|
|
665228 |
in kadm5_create_principal_3(). (We already filter out low type
|
|
|
665228 |
numbers in kadm5_modify_principal()).
|
|
|
665228 |
|
|
|
665228 |
In the LDAP KDB module, if containerdn and linkdn are both specified
|
|
|
665228 |
in a put_principal operation, check both linkdn and the computed
|
|
|
665228 |
standalone_principal_dn for container membership. To that end, factor
|
|
|
665228 |
out the checks into helper functions and call them on all applicable
|
|
|
665228 |
client-influenced DNs.
|
|
|
665228 |
|
|
|
665228 |
CVE-2018-5729:
|
|
|
665228 |
|
|
|
665228 |
In MIT krb5 1.6 or later, an authenticated kadmin user with permission
|
|
|
665228 |
to add principals to an LDAP Kerberos database can cause a null
|
|
|
665228 |
dereference in kadmind, or circumvent a DN container check, by
|
|
|
665228 |
supplying tagged data intended to be internal to the database module.
|
|
|
665228 |
Thanks to Sharwan Ram and Pooja Anil for discovering the potential
|
|
|
665228 |
null dereference.
|
|
|
665228 |
|
|
|
665228 |
CVE-2018-5730:
|
|
|
665228 |
|
|
|
665228 |
In MIT krb5 1.6 or later, an authenticated kadmin user with permission
|
|
|
665228 |
to add principals to an LDAP Kerberos database can circumvent a DN
|
|
|
665228 |
containership check by supplying both a "linkdn" and "containerdn"
|
|
|
665228 |
database argument, or by supplying a DN string which is a left
|
|
|
665228 |
extension of a container DN string but is not hierarchically within
|
|
|
665228 |
the container DN.
|
|
|
665228 |
|
|
|
665228 |
ticket: 8643 (new)
|
|
|
665228 |
tags: pullup
|
|
|
665228 |
target_version: 1.16-next
|
|
|
665228 |
target_version: 1.15-next
|
|
|
665228 |
|
|
|
665228 |
(cherry picked from commit e1caf6fb74981da62039846931ebdffed71309d1)
|
|
|
665228 |
[rharwood@redhat.com fuzz - didn't port tests to expected_msg]
|
|
|
665228 |
---
|
|
|
665228 |
src/lib/kadm5/srv/svr_principal.c | 7 +
|
|
|
665228 |
src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h | 2 +-
|
|
|
665228 |
.../kdb/ldap/libkdb_ldap/ldap_principal2.c | 200 ++++++++++--------
|
|
|
665228 |
src/tests/t_kdb.py | 14 ++
|
|
|
665228 |
4 files changed, 128 insertions(+), 95 deletions(-)
|
|
|
665228 |
|
|
|
665228 |
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
|
|
|
665228 |
index 0d4f0a632..64a4a2e97 100644
|
|
|
665228 |
--- a/src/lib/kadm5/srv/svr_principal.c
|
|
|
665228 |
+++ b/src/lib/kadm5/srv/svr_principal.c
|
|
|
665228 |
@@ -330,6 +330,13 @@ kadm5_create_principal_3(void *server_handle,
|
|
|
665228 |
return KADM5_BAD_MASK;
|
|
|
665228 |
if((mask & ~ALL_PRINC_MASK))
|
|
|
665228 |
return KADM5_BAD_MASK;
|
|
|
665228 |
+ if (mask & KADM5_TL_DATA) {
|
|
|
665228 |
+ for (tl_data_tail = entry->tl_data; tl_data_tail != NULL;
|
|
|
665228 |
+ tl_data_tail = tl_data_tail->tl_data_next) {
|
|
|
665228 |
+ if (tl_data_tail->tl_data_type < 256)
|
|
|
665228 |
+ return KADM5_BAD_TL_TYPE;
|
|
|
665228 |
+ }
|
|
|
665228 |
+ }
|
|
|
665228 |
|
|
|
665228 |
/*
|
|
|
665228 |
* Check to see if the principal exists
|
|
|
665228 |
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
|
|
|
665228 |
index 06b477537..0c19804ad 100644
|
|
|
665228 |
--- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
|
|
|
665228 |
+++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
|
|
|
665228 |
@@ -141,7 +141,7 @@ extern int set_ldap_error (krb5_context ctx, int st, int op);
|
|
|
665228 |
#define UNSTORE16_INT(ptr, val) (val = load_16_be(ptr))
|
|
|
665228 |
#define UNSTORE32_INT(ptr, val) (val = load_32_be(ptr))
|
|
|
665228 |
|
|
|
665228 |
-#define KDB_TL_USER_INFO 0x7ffe
|
|
|
665228 |
+#define KDB_TL_USER_INFO 0xff
|
|
|
665228 |
|
|
|
665228 |
#define KDB_TL_PRINCTYPE 0x01
|
|
|
665228 |
#define KDB_TL_PRINCCOUNT 0x02
|
|
|
665228 |
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
|
|
|
665228 |
index 88a170495..b7c9212cb 100644
|
|
|
665228 |
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
|
|
|
665228 |
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
|
|
|
665228 |
@@ -651,6 +651,107 @@ cleanup:
|
|
|
665228 |
return ret;
|
|
|
665228 |
}
|
|
|
665228 |
|
|
|
665228 |
+static krb5_error_code
|
|
|
665228 |
+check_dn_in_container(krb5_context context, const char *dn,
|
|
|
665228 |
+ char *const *subtrees, unsigned int ntrees)
|
|
|
665228 |
+{
|
|
|
665228 |
+ unsigned int i;
|
|
|
665228 |
+ size_t dnlen = strlen(dn), stlen;
|
|
|
665228 |
+
|
|
|
665228 |
+ for (i = 0; i < ntrees; i++) {
|
|
|
665228 |
+ if (subtrees[i] == NULL || *subtrees[i] == '\0')
|
|
|
665228 |
+ return 0;
|
|
|
665228 |
+ stlen = strlen(subtrees[i]);
|
|
|
665228 |
+ if (dnlen >= stlen &&
|
|
|
665228 |
+ strcasecmp(dn + dnlen - stlen, subtrees[i]) == 0 &&
|
|
|
665228 |
+ (dnlen == stlen || dn[dnlen - stlen - 1] == ','))
|
|
|
665228 |
+ return 0;
|
|
|
665228 |
+ }
|
|
|
665228 |
+
|
|
|
665228 |
+ k5_setmsg(context, EINVAL, _("DN is out of the realm subtree"));
|
|
|
665228 |
+ return EINVAL;
|
|
|
665228 |
+}
|
|
|
665228 |
+
|
|
|
665228 |
+static krb5_error_code
|
|
|
665228 |
+check_dn_exists(krb5_context context,
|
|
|
665228 |
+ krb5_ldap_server_handle *ldap_server_handle,
|
|
|
665228 |
+ const char *dn, krb5_boolean nonkrb_only)
|
|
|
665228 |
+{
|
|
|
665228 |
+ krb5_error_code st = 0, tempst;
|
|
|
665228 |
+ krb5_ldap_context *ldap_context = context->dal_handle->db_context;
|
|
|
665228 |
+ LDAP *ld = ldap_server_handle->ldap_handle;
|
|
|
665228 |
+ LDAPMessage *result = NULL, *ent;
|
|
|
665228 |
+ char *attrs[] = { "krbticketpolicyreference", "krbprincipalname", NULL };
|
|
|
665228 |
+ char **values;
|
|
|
665228 |
+
|
|
|
665228 |
+ LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attrs, IGNORE_STATUS);
|
|
|
665228 |
+ if (st != LDAP_SUCCESS)
|
|
|
665228 |
+ return set_ldap_error(context, st, OP_SEARCH);
|
|
|
665228 |
+
|
|
|
665228 |
+ ent = ldap_first_entry(ld, result);
|
|
|
665228 |
+ CHECK_NULL(ent);
|
|
|
665228 |
+
|
|
|
665228 |
+ values = ldap_get_values(ld, ent, "krbticketpolicyreference");
|
|
|
665228 |
+ if (values != NULL)
|
|
|
665228 |
+ ldap_value_free(values);
|
|
|
665228 |
+
|
|
|
665228 |
+ values = ldap_get_values(ld, ent, "krbprincipalname");
|
|
|
665228 |
+ if (values != NULL) {
|
|
|
665228 |
+ ldap_value_free(values);
|
|
|
665228 |
+ if (nonkrb_only) {
|
|
|
665228 |
+ st = EINVAL;
|
|
|
665228 |
+ k5_setmsg(context, st, _("ldap object is already kerberized"));
|
|
|
665228 |
+ goto cleanup;
|
|
|
665228 |
+ }
|
|
|
665228 |
+ }
|
|
|
665228 |
+
|
|
|
665228 |
+cleanup:
|
|
|
665228 |
+ ldap_msgfree(result);
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+}
|
|
|
665228 |
+
|
|
|
665228 |
+static krb5_error_code
|
|
|
665228 |
+validate_xargs(krb5_context context,
|
|
|
665228 |
+ krb5_ldap_server_handle *ldap_server_handle,
|
|
|
665228 |
+ const xargs_t *xargs, const char *standalone_dn,
|
|
|
665228 |
+ char *const *subtrees, unsigned int ntrees)
|
|
|
665228 |
+{
|
|
|
665228 |
+ krb5_error_code st;
|
|
|
665228 |
+
|
|
|
665228 |
+ if (xargs->dn != NULL) {
|
|
|
665228 |
+ /* The supplied dn must be within a realm container. */
|
|
|
665228 |
+ st = check_dn_in_container(context, xargs->dn, subtrees, ntrees);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+ /* The supplied dn must exist without Kerberos attributes. */
|
|
|
665228 |
+ st = check_dn_exists(context, ldap_server_handle, xargs->dn, TRUE);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+ }
|
|
|
665228 |
+
|
|
|
665228 |
+ if (xargs->linkdn != NULL) {
|
|
|
665228 |
+ /* The supplied linkdn must be within a realm container. */
|
|
|
665228 |
+ st = check_dn_in_container(context, xargs->linkdn, subtrees, ntrees);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+ /* The supplied linkdn must exist. */
|
|
|
665228 |
+ st = check_dn_exists(context, ldap_server_handle, xargs->linkdn,
|
|
|
665228 |
+ FALSE);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+ }
|
|
|
665228 |
+
|
|
|
665228 |
+ if (xargs->containerdn != NULL && standalone_dn != NULL) {
|
|
|
665228 |
+ /* standalone_dn (likely composed using containerdn) must be within a
|
|
|
665228 |
+ * container. */
|
|
|
665228 |
+ st = check_dn_in_container(context, standalone_dn, subtrees, ntrees);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
+ return st;
|
|
|
665228 |
+ }
|
|
|
665228 |
+
|
|
|
665228 |
+ return 0;
|
|
|
665228 |
+}
|
|
|
665228 |
+
|
|
|
665228 |
krb5_error_code
|
|
|
665228 |
krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
|
|
|
665228 |
char **db_args)
|
|
|
665228 |
@@ -662,12 +763,12 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
|
|
|
665228 |
LDAPMessage *result=NULL, *ent=NULL;
|
|
|
665228 |
char **subtreelist = NULL;
|
|
|
665228 |
char *user=NULL, *subtree=NULL, *principal_dn=NULL;
|
|
|
665228 |
- char **values=NULL, *strval[10]={NULL}, errbuf[1024];
|
|
|
665228 |
+ char *strval[10]={NULL}, errbuf[1024];
|
|
|
665228 |
char *filtuser=NULL;
|
|
|
665228 |
struct berval **bersecretkey=NULL;
|
|
|
665228 |
LDAPMod **mods=NULL;
|
|
|
665228 |
krb5_boolean create_standalone=FALSE;
|
|
|
665228 |
- krb5_boolean krb_identity_exists=FALSE, establish_links=FALSE;
|
|
|
665228 |
+ krb5_boolean establish_links=FALSE;
|
|
|
665228 |
char *standalone_principal_dn=NULL;
|
|
|
665228 |
krb5_tl_data *tl_data=NULL;
|
|
|
665228 |
krb5_key_data **keys=NULL;
|
|
|
665228 |
@@ -860,24 +961,6 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
|
|
|
665228 |
* any of the subtrees
|
|
|
665228 |
*/
|
|
|
665228 |
if (xargs.dn_from_kbd == TRUE) {
|
|
|
665228 |
- /* make sure the DN falls in the subtree */
|
|
|
665228 |
- int dnlen=0, subtreelen=0;
|
|
|
665228 |
- char *dn=NULL;
|
|
|
665228 |
- krb5_boolean outofsubtree=TRUE;
|
|
|
665228 |
-
|
|
|
665228 |
- if (xargs.dn != NULL) {
|
|
|
665228 |
- dn = xargs.dn;
|
|
|
665228 |
- } else if (xargs.linkdn != NULL) {
|
|
|
665228 |
- dn = xargs.linkdn;
|
|
|
665228 |
- } else if (standalone_principal_dn != NULL) {
|
|
|
665228 |
- /*
|
|
|
665228 |
- * Even though the standalone_principal_dn is constructed
|
|
|
665228 |
- * within this function, there is the containerdn input
|
|
|
665228 |
- * from the user that can become part of the it.
|
|
|
665228 |
- */
|
|
|
665228 |
- dn = standalone_principal_dn;
|
|
|
665228 |
- }
|
|
|
665228 |
-
|
|
|
665228 |
/* Get the current subtree list if we haven't already done so. */
|
|
|
665228 |
if (subtreelist == NULL) {
|
|
|
665228 |
st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
|
|
|
665228 |
@@ -885,81 +968,10 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
|
|
|
665228 |
goto cleanup;
|
|
|
665228 |
}
|
|
|
665228 |
|
|
|
665228 |
- for (tre=0; tre
|
|
|
665228 |
- if (subtreelist[tre] == NULL || strlen(subtreelist[tre]) == 0) {
|
|
|
665228 |
- outofsubtree = FALSE;
|
|
|
665228 |
- break;
|
|
|
665228 |
- } else {
|
|
|
665228 |
- dnlen = strlen (dn);
|
|
|
665228 |
- subtreelen = strlen(subtreelist[tre]);
|
|
|
665228 |
- if ((dnlen >= subtreelen) && (strcasecmp((dn + dnlen - subtreelen), subtreelist[tre]) == 0)) {
|
|
|
665228 |
- outofsubtree = FALSE;
|
|
|
665228 |
- break;
|
|
|
665228 |
- }
|
|
|
665228 |
- }
|
|
|
665228 |
- }
|
|
|
665228 |
-
|
|
|
665228 |
- if (outofsubtree == TRUE) {
|
|
|
665228 |
- st = EINVAL;
|
|
|
665228 |
- k5_setmsg(context, st, _("DN is out of the realm subtree"));
|
|
|
665228 |
+ st = validate_xargs(context, ldap_server_handle, &xargs,
|
|
|
665228 |
+ standalone_principal_dn, subtreelist, ntrees);
|
|
|
665228 |
+ if (st)
|
|
|
665228 |
goto cleanup;
|
|
|
665228 |
- }
|
|
|
665228 |
-
|
|
|
665228 |
- /*
|
|
|
665228 |
- * dn value will be set either by dn, linkdn or the standalone_principal_dn
|
|
|
665228 |
- * In the first 2 cases, the dn should be existing and in the last case we
|
|
|
665228 |
- * are supposed to create the ldap object. so the below should not be
|
|
|
665228 |
- * executed for the last case.
|
|
|
665228 |
- */
|
|
|
665228 |
-
|
|
|
665228 |
- if (standalone_principal_dn == NULL) {
|
|
|
665228 |
- /*
|
|
|
665228 |
- * If the ldap object is missing, this results in an error.
|
|
|
665228 |
- */
|
|
|
665228 |
-
|
|
|
665228 |
- /*
|
|
|
665228 |
- * Search for krbprincipalname attribute here.
|
|
|
665228 |
- * This is to find if a kerberos identity is already present
|
|
|
665228 |
- * on the ldap object, in which case adding a kerberos identity
|
|
|
665228 |
- * on the ldap object should result in an error.
|
|
|
665228 |
- */
|
|
|
665228 |
- char *attributes[]={"krbticketpolicyreference", "krbprincipalname", NULL};
|
|
|
665228 |
-
|
|
|
665228 |
- ldap_msgfree(result);
|
|
|
665228 |
- result = NULL;
|
|
|
665228 |
- LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attributes, IGNORE_STATUS);
|
|
|
665228 |
- if (st == LDAP_SUCCESS) {
|
|
|
665228 |
- ent = ldap_first_entry(ld, result);
|
|
|
665228 |
- if (ent != NULL) {
|
|
|
665228 |
- if ((values=ldap_get_values(ld, ent, "krbticketpolicyreference")) != NULL) {
|
|
|
665228 |
- ldap_value_free(values);
|
|
|
665228 |
- }
|
|
|
665228 |
-
|
|
|
665228 |
- if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) {
|
|
|
665228 |
- krb_identity_exists = TRUE;
|
|
|
665228 |
- ldap_value_free(values);
|
|
|
665228 |
- }
|
|
|
665228 |
- }
|
|
|
665228 |
- } else {
|
|
|
665228 |
- st = set_ldap_error(context, st, OP_SEARCH);
|
|
|
665228 |
- goto cleanup;
|
|
|
665228 |
- }
|
|
|
665228 |
- }
|
|
|
665228 |
- }
|
|
|
665228 |
-
|
|
|
665228 |
- /*
|
|
|
665228 |
- * If xargs.dn is set then the request is to add a
|
|
|
665228 |
- * kerberos principal on a ldap object, but if
|
|
|
665228 |
- * there is one already on the ldap object this
|
|
|
665228 |
- * should result in an error.
|
|
|
665228 |
- */
|
|
|
665228 |
-
|
|
|
665228 |
- if (xargs.dn != NULL && krb_identity_exists == TRUE) {
|
|
|
665228 |
- st = EINVAL;
|
|
|
665228 |
- snprintf(errbuf, sizeof(errbuf),
|
|
|
665228 |
- _("ldap object is already kerberized"));
|
|
|
665228 |
- k5_setmsg(context, st, "%s", errbuf);
|
|
|
665228 |
- goto cleanup;
|
|
|
665228 |
}
|
|
|
665228 |
|
|
|
665228 |
if (xargs.linkdn != NULL) {
|
|
|
665228 |
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
|
|
|
665228 |
index c0eeb0118..319687ff3 100755
|
|
|
665228 |
--- a/src/tests/t_kdb.py
|
|
|
665228 |
+++ b/src/tests/t_kdb.py
|
|
|
665228 |
@@ -171,6 +171,14 @@ out = realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=krb5', 'princ1'],
|
|
|
665228 |
expected_code=1)
|
|
|
665228 |
if 'DN is out of the realm subtree' not in out:
|
|
|
665228 |
fail('Unexpected kadmin.local output for out-of-realm dn')
|
|
|
665228 |
+
|
|
|
665228 |
+# Check that the DN container check is a hierarchy test, not a simple
|
|
|
665228 |
+# suffix match (CVE-2018-5730). We expect this operation to fail
|
|
|
665228 |
+# either way (because "xcn" isn't a valid DN tag) but the container
|
|
|
665228 |
+# check should happen before the DN is parsed.
|
|
|
665228 |
+realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=xcn=t1,cn=krb5', 'princ1'],
|
|
|
665228 |
+ expected_code=1, expected_msg='DN is out of the realm subtree')
|
|
|
665228 |
+
|
|
|
665228 |
realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=t2,cn=krb5', 'princ1'])
|
|
|
665228 |
out = realm.run([kadminl, 'getprinc', 'princ1'])
|
|
|
665228 |
if 'Principal: princ1' not in out:
|
|
|
665228 |
@@ -209,6 +217,12 @@ out = realm.run([kadminl, 'modprinc', '-x', 'containerdn=cn=t2,cn=krb5',
|
|
|
665228 |
if 'containerdn option not supported' not in out:
|
|
|
665228 |
fail('Unexpected kadmin.local output trying to reset containerdn')
|
|
|
665228 |
|
|
|
665228 |
+# Verify that containerdn is checked when linkdn is also supplied
|
|
|
665228 |
+# (CVE-2018-5730).
|
|
|
665228 |
+realm.run([kadminl, 'ank', '-randkey', '-x', 'containerdn=cn=krb5',
|
|
|
665228 |
+ '-x', 'linkdn=cn=t2,cn=krb5', 'princ4'], expected_code=1,
|
|
|
665228 |
+ expected_msg='DN is out of the realm subtree')
|
|
|
665228 |
+
|
|
|
665228 |
# Create and modify a ticket policy.
|
|
|
665228 |
kldaputil(['create_policy', '-maxtktlife', '3hour', '-maxrenewlife', '6hour',
|
|
|
665228 |
'-allow_forwardable', 'tktpol'])
|