Browse code

Fix for CVE-2018-5729 and CVE-2018-5730

MIT Kerberos (krb5) 1.6 or later allowed an authenticated kadmin, having permissions to add principals to an LDAP Kerberos database, to cause a denial of service (NULL pointer dereference), or bypass a DN container check by supplying tagged data that is internal to the database module, or circumvent a DN containership check by supplying both a "linkdn" and "containerdn" database argument or by supplying a DN string which is a left extension of a container DN string but is not hierarchically within the container DN.

Change-Id: Ic5778db023139f8b03eb1d0c6e24c2b48387d625
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/5483
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Jonathan Brown
Reviewed-by: Sharath George

dweepadvani authored on 2018/08/14 02:51:44
Showing 2 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,344 @@
0
+From e1caf6fb74981da62039846931ebdffed71309d1 Mon Sep 17 00:00:00 2001
1
+From: Greg Hudson <ghudson@mit.edu>
2
+Date: Fri, 12 Jan 2018 11:43:01 -0500
3
+Subject: [PATCH] Fix flaws in LDAP DN checking
4
+
5
+KDB_TL_USER_INFO tl-data is intended to be internal to the LDAP KDB
6
+module, and not used in disk or wire principal entries.  Prevent
7
+kadmin clients from sending KDB_TL_USER_INFO tl-data by giving it a
8
+type number less than 256 and filtering out type numbers less than 256
9
+in kadm5_create_principal_3().  (We already filter out low type
10
+numbers in kadm5_modify_principal()).
11
+
12
+In the LDAP KDB module, if containerdn and linkdn are both specified
13
+in a put_principal operation, check both linkdn and the computed
14
+standalone_principal_dn for container membership.  To that end, factor
15
+out the checks into helper functions and call them on all applicable
16
+client-influenced DNs.
17
+
18
+CVE-2018-5729:
19
+
20
+In MIT krb5 1.6 or later, an authenticated kadmin user with permission
21
+to add principals to an LDAP Kerberos database can cause a null
22
+dereference in kadmind, or circumvent a DN container check, by
23
+supplying tagged data intended to be internal to the database module.
24
+Thanks to Sharwan Ram and Pooja Anil for discovering the potential
25
+null dereference.
26
+
27
+CVE-2018-5730:
28
+
29
+In MIT krb5 1.6 or later, an authenticated kadmin user with permission
30
+to add principals to an LDAP Kerberos database can circumvent a DN
31
+containership check by supplying both a "linkdn" and "containerdn"
32
+database argument, or by supplying a DN string which is a left
33
+extension of a container DN string but is not hierarchically within
34
+the container DN.
35
+
36
+ticket: 8643 (new)
37
+tags: pullup
38
+target_version: 1.16-next
39
+target_version: 1.15-next
40
+---
41
+ src/lib/kadm5/srv/svr_principal.c             |   7 +
42
+ src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h   |   2 +-
43
+ .../kdb/ldap/libkdb_ldap/ldap_principal2.c    | 200 ++++++++++--------
44
+ src/tests/t_kdb.py                            |  11 +
45
+ 4 files changed, 125 insertions(+), 95 deletions(-)
46
+
47
+diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
48
+index 2420f2c2be..a59a65e8f6 100644
49
+--- a/src/lib/kadm5/srv/svr_principal.c
50
+@@ -330,6 +330,13 @@ kadm5_create_principal_3(void *server_handle,
51
+         return KADM5_BAD_MASK;
52
+     if((mask & ~ALL_PRINC_MASK))
53
+         return KADM5_BAD_MASK;
54
++    if (mask & KADM5_TL_DATA) {
55
++        for (tl_data_tail = entry->tl_data; tl_data_tail != NULL;
56
++             tl_data_tail = tl_data_tail->tl_data_next) {
57
++            if (tl_data_tail->tl_data_type < 256)
58
++                return KADM5_BAD_TL_TYPE;
59
++        }
60
++    }
61
+ 
62
+     /*
63
+      * Check to see if the principal exists
64
+diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
65
+index 535a1f309e..8b8420faa9 100644
66
+--- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h
67
+@@ -141,7 +141,7 @@ extern int set_ldap_error (krb5_context ctx, int st, int op);
68
+ #define UNSTORE16_INT(ptr, val) (val = load_16_be(ptr))
69
+ #define UNSTORE32_INT(ptr, val) (val = load_32_be(ptr))
70
+ 
71
+-#define  KDB_TL_USER_INFO      0x7ffe
72
++#define  KDB_TL_USER_INFO      0xff
73
+ 
74
+ #define KDB_TL_PRINCTYPE          0x01
75
+ #define KDB_TL_PRINCCOUNT         0x02
76
+diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
77
+index 88a1704950..b7c9212cb2 100644
78
+--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
79
+@@ -651,6 +651,107 @@ update_ldap_mod_auth_ind(krb5_context context, krb5_db_entry *entry,
80
+     return ret;
81
+ }
82
+ 
83
++static krb5_error_code
84
++check_dn_in_container(krb5_context context, const char *dn,
85
++                      char *const *subtrees, unsigned int ntrees)
86
++{
87
++    unsigned int i;
88
++    size_t dnlen = strlen(dn), stlen;
89
++
90
++    for (i = 0; i < ntrees; i++) {
91
++        if (subtrees[i] == NULL || *subtrees[i] == '\0')
92
++            return 0;
93
++        stlen = strlen(subtrees[i]);
94
++        if (dnlen >= stlen &&
95
++            strcasecmp(dn + dnlen - stlen, subtrees[i]) == 0 &&
96
++            (dnlen == stlen || dn[dnlen - stlen - 1] == ','))
97
++            return 0;
98
++    }
99
++
100
++    k5_setmsg(context, EINVAL, _("DN is out of the realm subtree"));
101
++    return EINVAL;
102
++}
103
++
104
++static krb5_error_code
105
++check_dn_exists(krb5_context context,
106
++                krb5_ldap_server_handle *ldap_server_handle,
107
++                const char *dn, krb5_boolean nonkrb_only)
108
++{
109
++    krb5_error_code st = 0, tempst;
110
++    krb5_ldap_context *ldap_context = context->dal_handle->db_context;
111
++    LDAP *ld = ldap_server_handle->ldap_handle;
112
++    LDAPMessage *result = NULL, *ent;
113
++    char *attrs[] = { "krbticketpolicyreference", "krbprincipalname", NULL };
114
++    char **values;
115
++
116
++    LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attrs, IGNORE_STATUS);
117
++    if (st != LDAP_SUCCESS)
118
++        return set_ldap_error(context, st, OP_SEARCH);
119
++
120
++    ent = ldap_first_entry(ld, result);
121
++    CHECK_NULL(ent);
122
++
123
++    values = ldap_get_values(ld, ent, "krbticketpolicyreference");
124
++    if (values != NULL)
125
++        ldap_value_free(values);
126
++
127
++    values = ldap_get_values(ld, ent, "krbprincipalname");
128
++    if (values != NULL) {
129
++        ldap_value_free(values);
130
++        if (nonkrb_only) {
131
++            st = EINVAL;
132
++            k5_setmsg(context, st, _("ldap object is already kerberized"));
133
++            goto cleanup;
134
++        }
135
++    }
136
++
137
++cleanup:
138
++    ldap_msgfree(result);
139
++    return st;
140
++}
141
++
142
++static krb5_error_code
143
++validate_xargs(krb5_context context,
144
++               krb5_ldap_server_handle *ldap_server_handle,
145
++               const xargs_t *xargs, const char *standalone_dn,
146
++               char *const *subtrees, unsigned int ntrees)
147
++{
148
++    krb5_error_code st;
149
++
150
++    if (xargs->dn != NULL) {
151
++        /* The supplied dn must be within a realm container. */
152
++        st = check_dn_in_container(context, xargs->dn, subtrees, ntrees);
153
++        if (st)
154
++            return st;
155
++        /* The supplied dn must exist without Kerberos attributes. */
156
++        st = check_dn_exists(context, ldap_server_handle, xargs->dn, TRUE);
157
++        if (st)
158
++            return st;
159
++    }
160
++
161
++    if (xargs->linkdn != NULL) {
162
++        /* The supplied linkdn must be within a realm container. */
163
++        st = check_dn_in_container(context, xargs->linkdn, subtrees, ntrees);
164
++        if (st)
165
++            return st;
166
++        /* The supplied linkdn must exist. */
167
++        st = check_dn_exists(context, ldap_server_handle, xargs->linkdn,
168
++                             FALSE);
169
++        if (st)
170
++            return st;
171
++    }
172
++
173
++    if (xargs->containerdn != NULL && standalone_dn != NULL) {
174
++        /* standalone_dn (likely composed using containerdn) must be within a
175
++         * container. */
176
++        st = check_dn_in_container(context, standalone_dn, subtrees, ntrees);
177
++        if (st)
178
++            return st;
179
++    }
180
++
181
++    return 0;
182
++}
183
++
184
+ krb5_error_code
185
+ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
186
+                         char **db_args)
187
+@@ -662,12 +763,12 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
188
+     LDAPMessage                 *result=NULL, *ent=NULL;
189
+     char                        **subtreelist = NULL;
190
+     char                        *user=NULL, *subtree=NULL, *principal_dn=NULL;
191
+-    char                        **values=NULL, *strval[10]={NULL}, errbuf[1024];
192
++    char                        *strval[10]={NULL}, errbuf[1024];
193
+     char                        *filtuser=NULL;
194
+     struct berval               **bersecretkey=NULL;
195
+     LDAPMod                     **mods=NULL;
196
+     krb5_boolean                create_standalone=FALSE;
197
+-    krb5_boolean                krb_identity_exists=FALSE, establish_links=FALSE;
198
++    krb5_boolean                establish_links=FALSE;
199
+     char                        *standalone_principal_dn=NULL;
200
+     krb5_tl_data                *tl_data=NULL;
201
+     krb5_key_data               **keys=NULL;
202
+@@ -860,24 +961,6 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
203
+      * any of the subtrees
204
+      */
205
+     if (xargs.dn_from_kbd == TRUE) {
206
+-        /* make sure the DN falls in the subtree */
207
+-        int              dnlen=0, subtreelen=0;
208
+-        char             *dn=NULL;
209
+-        krb5_boolean     outofsubtree=TRUE;
210
+-
211
+-        if (xargs.dn != NULL) {
212
+-            dn = xargs.dn;
213
+-        } else if (xargs.linkdn != NULL) {
214
+-            dn = xargs.linkdn;
215
+-        } else if (standalone_principal_dn != NULL) {
216
+-            /*
217
+-             * Even though the standalone_principal_dn is constructed
218
+-             * within this function, there is the containerdn input
219
+-             * from the user that can become part of the it.
220
+-             */
221
+-            dn = standalone_principal_dn;
222
+-        }
223
+-
224
+         /* Get the current subtree list if we haven't already done so. */
225
+         if (subtreelist == NULL) {
226
+             st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
227
+@@ -885,81 +968,10 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
228
+                 goto cleanup;
229
+         }
230
+ 
231
+-        for (tre=0; tre<ntrees; ++tre) {
232
+-            if (subtreelist[tre] == NULL || strlen(subtreelist[tre]) == 0) {
233
+-                outofsubtree = FALSE;
234
+-                break;
235
+-            } else {
236
+-                dnlen = strlen (dn);
237
+-                subtreelen = strlen(subtreelist[tre]);
238
+-                if ((dnlen >= subtreelen) && (strcasecmp((dn + dnlen - subtreelen), subtreelist[tre]) == 0)) {
239
+-                    outofsubtree = FALSE;
240
+-                    break;
241
+-                }
242
+-            }
243
+-        }
244
+-
245
+-        if (outofsubtree == TRUE) {
246
+-            st = EINVAL;
247
+-            k5_setmsg(context, st, _("DN is out of the realm subtree"));
248
++        st = validate_xargs(context, ldap_server_handle, &xargs,
249
++                            standalone_principal_dn, subtreelist, ntrees);
250
++        if (st)
251
+             goto cleanup;
252
+-        }
253
+-
254
+-        /*
255
+-         * dn value will be set either by dn, linkdn or the standalone_principal_dn
256
+-         * In the first 2 cases, the dn should be existing and in the last case we
257
+-         * are supposed to create the ldap object. so the below should not be
258
+-         * executed for the last case.
259
+-         */
260
+-
261
+-        if (standalone_principal_dn == NULL) {
262
+-            /*
263
+-             * If the ldap object is missing, this results in an error.
264
+-             */
265
+-
266
+-            /*
267
+-             * Search for krbprincipalname attribute here.
268
+-             * This is to find if a kerberos identity is already present
269
+-             * on the ldap object, in which case adding a kerberos identity
270
+-             * on the ldap object should result in an error.
271
+-             */
272
+-            char  *attributes[]={"krbticketpolicyreference", "krbprincipalname", NULL};
273
+-
274
+-            ldap_msgfree(result);
275
+-            result = NULL;
276
+-            LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attributes, IGNORE_STATUS);
277
+-            if (st == LDAP_SUCCESS) {
278
+-                ent = ldap_first_entry(ld, result);
279
+-                if (ent != NULL) {
280
+-                    if ((values=ldap_get_values(ld, ent, "krbticketpolicyreference")) != NULL) {
281
+-                        ldap_value_free(values);
282
+-                    }
283
+-
284
+-                    if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) {
285
+-                        krb_identity_exists = TRUE;
286
+-                        ldap_value_free(values);
287
+-                    }
288
+-                }
289
+-            } else {
290
+-                st = set_ldap_error(context, st, OP_SEARCH);
291
+-                goto cleanup;
292
+-            }
293
+-        }
294
+-    }
295
+-
296
+-    /*
297
+-     * If xargs.dn is set then the request is to add a
298
+-     * kerberos principal on a ldap object, but if
299
+-     * there is one already on the ldap object this
300
+-     * should result in an error.
301
+-     */
302
+-
303
+-    if (xargs.dn != NULL && krb_identity_exists == TRUE) {
304
+-        st = EINVAL;
305
+-        snprintf(errbuf, sizeof(errbuf),
306
+-                 _("ldap object is already kerberized"));
307
+-        k5_setmsg(context, st, "%s", errbuf);
308
+-        goto cleanup;
309
+     }
310
+ 
311
+     if (xargs.linkdn != NULL) {
312
+diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
313
+index 217f2cdc3b..6e563b1032 100755
314
+--- a/src/tests/t_kdb.py
315
+@@ -203,6 +203,12 @@ def ldap_add(dn, objectclass, attrs=[]):
316
+ # in the test LDAP server.
317
+ realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=krb5', 'princ1'],
318
+           expected_code=1, expected_msg='DN is out of the realm subtree')
319
++# Check that the DN container check is a hierarchy test, not a simple
320
++# suffix match (CVE-2018-5730).  We expect this operation to fail
321
++# either way (because "xcn" isn't a valid DN tag) but the container
322
++# check should happen before the DN is parsed.
323
++realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=xcn=t1,cn=krb5', 'princ1'],
324
++          expected_code=1, expected_msg='DN is out of the realm subtree')
325
+ realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=t2,cn=krb5', 'princ1'])
326
+ realm.run([kadminl, 'getprinc', 'princ1'], expected_msg='Principal: princ1')
327
+ realm.run([kadminl, 'ank', '-randkey', '-x', 'dn=cn=t2,cn=krb5', 'again'],
328
+@@ -226,6 +232,11 @@ def ldap_add(dn, objectclass, attrs=[]):
329
+            'princ3'])
330
+ realm.run([kadminl, 'modprinc', '-x', 'containerdn=cn=t2,cn=krb5', 'princ3'],
331
+           expected_code=1, expected_msg='containerdn option not supported')
332
++# Verify that containerdn is checked when linkdn is also supplied
333
++# (CVE-2018-5730).
334
++realm.run([kadminl, 'ank', '-randkey', '-x', 'containerdn=cn=krb5',
335
++           '-x', 'linkdn=cn=t2,cn=krb5', 'princ4'], expected_code=1,
336
++          expected_msg='DN is out of the realm subtree')
337
+ 
338
+ # Create and modify a ticket policy.
339
+ kldaputil(['create_policy', '-maxtktlife', '3hour', '-maxrenewlife', '6hour',
... ...
@@ -1,7 +1,7 @@
1 1
 Summary:        The Kerberos newtork authentication system
2 2
 Name:           krb5
3 3
 Version:        1.16
4
-Release:        1%{?dist}
4
+Release:        2%{?dist}
5 5
 License:        MIT
6 6
 URL:            http://web.mit.edu/kerberos/
7 7
 Group:          System Environment/Security
... ...
@@ -10,6 +10,7 @@ Distribution:   Photon
10 10
 Source0:        http://web.mit.edu/kerberos/www/dist/%{name}/1.16/%{name}-%{version}.tar.gz
11 11
 %define sha1    krb5=e1bd68d9121c337faf5dbd478d0a2b6998114fc7
12 12
 Patch0:         krb5-1.15-never-unload-mechanisms.patch
13
+Patch1:         krb5-CVE-2018-5730.patch
13 14
 Requires:       openssl
14 15
 Requires:       e2fsprogs
15 16
 BuildRequires:  openssl-devel
... ...
@@ -21,8 +22,8 @@ practice of clear text passwords.
21 21
 %prep
22 22
 %setup -q
23 23
 %patch0 -p1
24
+%patch1 -p1
24 25
 %build
25
-
26 26
 cd src &&
27 27
 sed -e "s@python2.5/Python.h@& python2.7/Python.h@g" \
28 28
     -e "s@-lpython2.5]@&,\n  AC_CHECK_LIB(python2.7,main,[PYTHON_LIB=-lpython2.7])@g" \
... ...
@@ -90,6 +91,8 @@ rm -rf %{buildroot}/*
90 90
 %{_datarootdir}/man/man5/.k5login.5.gz
91 91
 %{_docdir}/%{name}-%{version}
92 92
 %changelog
93
+*   Mon Aug 13 2018 Dweep Advani <advani@vmware.com> 1.16-2
94
+-   Fix for CVE-2018-5729 and CVE-2018-5730
93 95
 *   Wed Dec 13 2017 Xiaolin Li <xiaolinl@vmware.com> 1.16-1
94 96
 -   Update to version 1.16 to address CVE-2017-15088
95 97
 *   Thu Sep 28 2017 Xiaolin Li <xiaolinl@vmware.com> 1.15.2-1