From 706ba1ce6fbb349f9956701ebe04cebd8ddd7ba6 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Tue, 8 Dec 2020 22:00:55 +1300 Subject: [PATCH 001/224] CVE-2020-25718 ldb/attrib_handler casefold: simplify space dropping As seen in CVE-2021-20277, ldb_handler_fold() has been making mistakes when collapsing spaces down to a single space. This patch fixes the way it handles internal spaces (CVE-2021-20277 was about leading spaces), and involves a rewrite of the parsing loop. The bug has a detailed description of the problem. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14656 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett Autobuild-User(master): Andrew Bartlett Autobuild-Date(master): Wed Apr 7 03:16:39 UTC 2021 on sn-devel-184 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 24ddc1ca9cad95673bdd8023d99867707b37085f) --- lib/ldb/common/attrib_handlers.c | 53 +++++++++++++++----------------- lib/ldb/tests/ldb_match_test.c | 2 ++ 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/ldb/common/attrib_handlers.c b/lib/ldb/common/attrib_handlers.c index c6ef5ad477b0..f0fd4f50d8df 100644 --- a/lib/ldb/common/attrib_handlers.c +++ b/lib/ldb/common/attrib_handlers.c @@ -54,8 +54,8 @@ int ldb_handler_copy(struct ldb_context *ldb, void *mem_ctx, int ldb_handler_fold(struct ldb_context *ldb, void *mem_ctx, const struct ldb_val *in, struct ldb_val *out) { - char *s, *t; - size_t l; + char *s, *t, *start; + bool in_space; if (!in || !out || !(in->data)) { return -1; @@ -67,36 +67,33 @@ int ldb_handler_fold(struct ldb_context *ldb, void *mem_ctx, return -1; } - s = (char *)(out->data); - - /* remove trailing spaces if any */ - l = strlen(s); - while (l > 0 && s[l - 1] == ' ') l--; - s[l] = '\0'; - - /* remove leading spaces if any */ - if (*s == ' ') { - for (t = s; *s == ' '; s++, l--) ; - - /* remove leading spaces by moving down the string */ - memmove(t, s, l); - - s = t; + start = (char *)(out->data); + in_space = true; + t = start; + for (s = start; *s != '\0'; s++) { + if (*s == ' ') { + if (in_space) { + /* + * We already have one (or this is the start) + * and we don't want to add more + */ + continue; + } + in_space = true; + } else { + in_space = false; + } + *t = *s; + t++; } - /* check middle spaces */ - while ((t = strchr(s, ' ')) != NULL) { - for (s = t; *s == ' '; s++) ; - - if ((s - t) > 1) { - l = strlen(s); - - /* remove all spaces but one by moving down the string */ - memmove(t + 1, s, l); - } + if (in_space && t != start) { + /* the loop will have left a single trailing space */ + t--; } + *t = '\0'; - out->length = strlen((char *)out->data); + out->length = t - start; return 0; } diff --git a/lib/ldb/tests/ldb_match_test.c b/lib/ldb/tests/ldb_match_test.c index fbf4106fa786..eb5d9fcee200 100644 --- a/lib/ldb/tests/ldb_match_test.c +++ b/lib/ldb/tests/ldb_match_test.c @@ -183,6 +183,8 @@ static void test_wildcard_match(void **state) struct wildcard_test tests[] = { TEST_ENTRY(" 1 0", "1*0*", true, true), TEST_ENTRY(" 1 0", "1 *0", true, true), + TEST_ENTRY(" 1 0", "*1 0", true, true), + TEST_ENTRY("1 0", "*1 0", true, true), TEST_ENTRY("The value.......end", "*end", true, true), TEST_ENTRY("The value.......end", "*fend", false, true), TEST_ENTRY("The value.......end", "*eel", false, true), -- 2.25.1 From fd0c3f228166cf0fc49f278e724a110db77cd113 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 3 Mar 2021 19:17:36 +1300 Subject: [PATCH 002/224] CVE-2020-25718 ldb_match: trailing chunk must match end of string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A wildcard search is divided into chunks by the asterisks. While most chunks match the first suitable string, the last chunk matches the last possible string (unless there is a trailing asterisk, in which case this distinction is moot). We always knew this in our hearts, but we tried to do it in a funny complicated way that stepped through the string, comparing here and there, leading to CVE-2019-3824 and missed matches (bug 14044). With this patch, we just jump to the end of the string and compare it. As well as being correct, this should also improve performance, as the previous algorithm involved a quadratic loop of erroneous memmem()s. See https://tools.ietf.org/html/rfc4517 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14044 Signed-off-by: Douglas Bagnall Reviewed-by: Björn Jacke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit cc098f1cad04b2cfec4ddd6b2511cd5a600f31c6) --- lib/ldb/common/ldb_match.c | 80 +++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index 829afa77e716..da595615bd94 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -295,8 +295,9 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, uint8_t *p; chunk = tree->u.substring.chunks[c]; - if(a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) goto mismatch; - + if(a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { + goto mismatch; + } /* * Empty strings are returned as length 0. Ensure * we can cope with this. @@ -304,52 +305,41 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, if (cnk.length == 0) { goto mismatch; } - /* - * Values might be binary blobs. Don't use string - * search, but memory search instead. - */ - p = memmem((const void *)val.data,val.length, - (const void *)cnk.data, cnk.length); - if (p == NULL) goto mismatch; - - /* - * At this point we know cnk.length <= val.length as - * otherwise there could be no match - */ + if (cnk.length > val.length) { + goto mismatch; + } - if ( (! tree->u.substring.chunks[c + 1]) && (! tree->u.substring.end_with_wildcard) ) { - uint8_t *g; - uint8_t *end = val.data + val.length; - do { /* greedy */ - - /* - * haystack is a valid pointer in val - * because the memmem() can only - * succeed if the needle (cnk.length) - * is <= haystacklen - * - * p will be a pointer at least - * cnk.length from the end of haystack - */ - uint8_t *haystack - = p + cnk.length; - size_t haystacklen - = end - (haystack); - - g = memmem(haystack, - haystacklen, - (const uint8_t *)cnk.data, - cnk.length); - if (g) { - p = g; - } - } while(g); + if ( (tree->u.substring.chunks[c + 1]) == NULL && + (! tree->u.substring.end_with_wildcard) ) { + /* + * The last bit, after all the asterisks, must match + * exactly the last bit of the string. + */ + int cmp; + p = val.data + val.length - cnk.length; + cmp = memcmp(p, + cnk.data, + cnk.length); + if (cmp != 0) { + goto mismatch; + } + } else { + /* + * Values might be binary blobs. Don't use string + * search, but memory search instead. + */ + p = memmem((const void *)val.data, val.length, + (const void *)cnk.data, cnk.length); + if (p == NULL) { + goto mismatch; + } + /* move val to the end of the match */ + p += cnk.length; + val.length -= (p - val.data); + val.data = p; } - val.length = val.length - (p - (uint8_t *)(val.data)) - cnk.length; - val.data = (uint8_t *)(p + cnk.length); c++; - talloc_free(cnk.data); - cnk.data = NULL; + TALLOC_FREE(cnk.data); } /* last chunk may not have reached end of string */ -- 2.25.1 From 2066b00d1200b004dad1307919f7d317d7910b89 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Sat, 6 Mar 2021 16:05:15 +1300 Subject: [PATCH 003/224] CVE-2020-25718 ldb: fix ldb_comparison_fold off-by-one overrun We run one character over in comparing all the bytes in two ldb_vals. In almost all circumstances both ldb_vals would have an allocated '\0' in the overrun position, but it is best not to rely on that. Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 2b2f4f519454beb6f2a46705675a62274019fc09) --- lib/ldb/common/attrib_handlers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ldb/common/attrib_handlers.c b/lib/ldb/common/attrib_handlers.c index f0fd4f50d8df..6a885065f773 100644 --- a/lib/ldb/common/attrib_handlers.c +++ b/lib/ldb/common/attrib_handlers.c @@ -334,8 +334,8 @@ int ldb_comparison_fold(struct ldb_context *ldb, void *mem_ctx, if (toupper((unsigned char)*s1) != toupper((unsigned char)*s2)) break; if (*s1 == ' ') { - while (n1 && s1[0] == s1[1]) { s1++; n1--; } - while (n2 && s2[0] == s2[1]) { s2++; n2--; } + while (n1 > 1 && s1[0] == s1[1]) { s1++; n1--; } + while (n2 > 1 && s2[0] == s2[1]) { s2++; n2--; } } s1++; s2++; n1--; n2--; -- 2.25.1 From da7959ce592bace3d13ed8b87cdbc41e27ba88ac Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 19 Jan 2021 16:53:55 +0100 Subject: [PATCH 004/224] CVE-2020-25718 pyldb: catch potential overflow error in py_timestring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pair-Programmed-With: Björn Baumbach Signed-off-by: Stefan Metzmacher Signed-off-by: Björn Baumbach Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 71e8b24b8a031de26b21539e36a60f459257d2fd) --- lib/ldb/common/ldb_msg.c | 1 + lib/ldb/pyldb.c | 7 +++++++ lib/ldb/tests/python/api.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/lib/ldb/common/ldb_msg.c b/lib/ldb/common/ldb_msg.c index 7131f013f71b..57dfc5a04c2b 100644 --- a/lib/ldb/common/ldb_msg.c +++ b/lib/ldb/common/ldb_msg.c @@ -1272,6 +1272,7 @@ char *ldb_timestring(TALLOC_CTX *mem_ctx, time_t t) if (r != 17) { talloc_free(ts); + errno = EOVERFLOW; return NULL; } diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c index d093daedf5c7..257351b2bc45 100644 --- a/lib/ldb/pyldb.c +++ b/lib/ldb/pyldb.c @@ -4227,6 +4227,13 @@ static PyObject *py_timestring(PyObject *module, PyObject *args) if (!PyArg_ParseTuple(args, "l", &t_val)) return NULL; tresult = ldb_timestring(NULL, (time_t) t_val); + if (tresult == NULL) { + /* + * Most likely EOVERFLOW from gmtime() + */ + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } ret = PyUnicode_FromString(tresult); talloc_free(tresult); return ret; diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py index 675b5859af8f..8d154aac6adf 100755 --- a/lib/ldb/tests/python/api.py +++ b/lib/ldb/tests/python/api.py @@ -5,10 +5,12 @@ import os from unittest import TestCase import sys +sys.path.insert(0, "bin/python") import gc import time import ldb import shutil +import errno PY3 = sys.version_info > (3, 0) @@ -42,10 +44,27 @@ class NoContextTests(TestCase): self.assertEqual("19700101000000.0Z", ldb.timestring(0)) self.assertEqual("20071119191012.0Z", ldb.timestring(1195499412)) + self.assertEqual("00000101000000.0Z", ldb.timestring(-62167219200)) + self.assertEqual("99991231235959.0Z", ldb.timestring(253402300799)) + + # should result with OSError EOVERFLOW from gmtime() + with self.assertRaises(OSError) as err: + ldb.timestring(-62167219201) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + with self.assertRaises(OSError) as err: + ldb.timestring(253402300800) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + with self.assertRaises(OSError) as err: + ldb.timestring(0x7fffffffffffffff) + self.assertEqual(err.exception.errno, errno.EOVERFLOW) + def test_string_to_time(self): self.assertEqual(0, ldb.string_to_time("19700101000000.0Z")) self.assertEqual(1195499412, ldb.string_to_time("20071119191012.0Z")) + self.assertEqual(-62167219200, ldb.string_to_time("00000101000000.0Z")) + self.assertEqual(253402300799, ldb.string_to_time("99991231235959.0Z")) + def test_binary_encode(self): encoded = ldb.binary_encode(b'test\\x') decoded = ldb.binary_decode(encoded) -- 2.25.1 From c13b130ee229c4d618997feef431c54f5c4a76a2 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 3 Mar 2021 19:54:37 +1300 Subject: [PATCH 005/224] CVE-2020-25718 ldb_match: remove redundant check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already ensure the no-trailing-asterisk case ends at the end of the string. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14044 Signed-off-by: Douglas Bagnall Reviewed-by: Björn Jacke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit fa93339978040eab52b2722c1716028b48d8d084) --- lib/ldb/common/ldb_match.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index da595615bd94..2f4d41f34416 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -342,8 +342,6 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, TALLOC_FREE(cnk.data); } - /* last chunk may not have reached end of string */ - if ( (! tree->u.substring.end_with_wildcard) && (val.length != 0) ) goto mismatch; talloc_free(save_p); *matched = true; return LDB_SUCCESS; -- 2.25.1 From 40821ff23903d8a8866c5dce881a9ae513113300 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 28 May 2021 14:15:43 +1200 Subject: [PATCH 006/224] CVE-2020-25718 pyldb: Fix Message.items() for a message containing elements Previously, message elements were being freed before the call to Py_BuildValue(), resulting in an exception being raised. Additionally, only the first element of the returned list was ever assigned to. Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 3e4ec0a90a222c1cff4a91912afc703ca4cbbb0e) --- lib/ldb/pyldb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c index 257351b2bc45..df7c5c54eaaa 100644 --- a/lib/ldb/pyldb.c +++ b/lib/ldb/pyldb.c @@ -3535,13 +3535,13 @@ static PyObject *py_ldb_msg_items(PyLdbMessageObject *self, PyObject *value = NULL; PyObject *py_el = PyLdbMessageElement_FromMessageElement(&msg->elements[i], msg->elements); int res = 0; - Py_CLEAR(py_el); value = Py_BuildValue("(sO)", msg->elements[i].name, py_el); + Py_CLEAR(py_el); if (value == NULL ) { Py_CLEAR(l); return NULL; } - res = PyList_SetItem(l, 0, value); + res = PyList_SetItem(l, j, value); if (res == -1) { Py_CLEAR(l); return NULL; -- 2.25.1 From 79c1f68a312f61811ec3e6a9291b9eeb95d42b9c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 1 Feb 2021 14:21:21 +0100 Subject: [PATCH 007/224] CVE-2020-25718 lib:ldb: Add missing break in switch statement error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough] Signed-off-by: Andreas Schneider Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 1ffacac547a8ce29c6696dda73991a8db7e34dfd) --- lib/ldb/ldb_map/ldb_map_inbound.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ldb/ldb_map/ldb_map_inbound.c b/lib/ldb/ldb_map/ldb_map_inbound.c index 861c4c1622d5..324295737da1 100644 --- a/lib/ldb/ldb_map/ldb_map_inbound.c +++ b/lib/ldb/ldb_map/ldb_map_inbound.c @@ -262,6 +262,7 @@ static int map_search_self_callback(struct ldb_request *req, struct ldb_reply *a LDB_ERR_OPERATIONS_ERROR); } + break; default: /* ignore referrals */ break; -- 2.25.1 From 69ff5f9528f1882bce0fec2150e2033af8260209 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Sat, 19 Dec 2020 11:43:56 +1300 Subject: [PATCH 008/224] CVE-2020-25718 ldb.h: remove undefined async_ctx function signatures These functions do not exist. Signed-off-by: Douglas Bagnall Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 1a05b58edaf96e7da707f9ad0a237551dbe13eb5) --- lib/ldb/include/ldb.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/ldb/include/ldb.h b/lib/ldb/include/ldb.h index 08e2096b9140..bc44157eaf47 100644 --- a/lib/ldb/include/ldb.h +++ b/lib/ldb/include/ldb.h @@ -1093,18 +1093,6 @@ int ldb_global_init(void); */ struct ldb_context *ldb_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx); -typedef void (*ldb_async_timeout_fn) (void *); -typedef bool (*ldb_async_callback_fn) (void *); -typedef int (*ldb_async_ctx_add_op_fn)(void *, time_t, void *, ldb_async_timeout_fn, ldb_async_callback_fn); -typedef int (*ldb_async_ctx_wait_op_fn)(void *); - -void ldb_async_ctx_set_private_data(struct ldb_context *ldb, - void *private_data); -void ldb_async_ctx_set_add_op(struct ldb_context *ldb, - ldb_async_ctx_add_op_fn add_op); -void ldb_async_ctx_set_wait_op(struct ldb_context *ldb, - ldb_async_ctx_wait_op_fn wait_op); - /** Connect to a database. -- 2.25.1 From e42c6e62b4e69d9a3c590bbf7a601771b20c196a Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Sat, 6 Mar 2021 09:57:44 +1300 Subject: [PATCH 009/224] CVE-2020-25718 ldb: correct comments in attrib_handers val_to_int64 c.f. the identical static function in lib/ldb-samba/ldif_handlers.c Signed-off-by: Douglas Bagnall Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 46e6f6ef8436df7e083f34556c25f66f65ea1ce5) --- lib/ldb/common/attrib_handlers.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ldb/common/attrib_handlers.c b/lib/ldb/common/attrib_handlers.c index 6a885065f773..febf2f414ca0 100644 --- a/lib/ldb/common/attrib_handlers.c +++ b/lib/ldb/common/attrib_handlers.c @@ -97,7 +97,7 @@ int ldb_handler_fold(struct ldb_context *ldb, void *mem_ctx, return 0; } -/* length limited conversion of a ldb_val to a int32_t */ +/* length limited conversion of a ldb_val to an int64_t */ static int val_to_int64(const struct ldb_val *in, int64_t *v) { char *end; @@ -110,8 +110,6 @@ static int val_to_int64(const struct ldb_val *in, int64_t *v) strncpy(buf, (char *)in->data, in->length); buf[in->length] = 0; - /* We've to use "strtoll" here to have the intended overflows. - * Otherwise we may get "LONG_MAX" and the conversion is wrong. */ *v = (int64_t) strtoll(buf, &end, 0); if (*end != 0) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; -- 2.25.1 From 7fb571aa6a5cf12cfa23854a3ab7b273194c52f0 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 29 Jan 2021 13:49:02 +1300 Subject: [PATCH 010/224] CVE-2020-25718 ldb: improve comments for ldb_module_connect_backend() There is no flags argument. There are more URI forms. Signed-off-by: Douglas Bagnall Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 48068a58df0313cd904f27e2c918ee10275ae373) --- lib/ldb/common/ldb_modules.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/ldb/common/ldb_modules.c b/lib/ldb/common/ldb_modules.c index cc067abdfe04..4366f05e066c 100644 --- a/lib/ldb/common/ldb_modules.c +++ b/lib/ldb/common/ldb_modules.c @@ -173,11 +173,15 @@ int ldb_register_backend(const char *url_prefix, ldb_connect_fn connectfn, bool /* Return the ldb module form of a database. - The URL can either be one of the following forms - ldb://path - ldapi://path - - flags is made up of LDB_FLG_* + The URL looks something like this: + tdb://PATH + ldb://PATH + mdb://PATH + ldapi://PATH + PATH (unadorned PATH defaults to tdb://) + + for a complete list of backends (including possibly unmaintained ones) grep + for calls to ldb_register_backend(). the options are passed uninterpreted to the backend, and are backend specific. -- 2.25.1 From 2e239cda569aa451d4b27a581d68ceb984de7c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Baumbach?= Date: Mon, 18 Jan 2021 16:48:21 +0100 Subject: [PATCH 011/224] CVE-2020-25718 pyldb: fix a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Baumbach Reviewed-by: Rowland penny BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit 6fcde09f093db5d26c582a3c28531265f06b9fde) --- lib/ldb/pyldb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c index df7c5c54eaaa..3f4b0c7a45cf 100644 --- a/lib/ldb/pyldb.c +++ b/lib/ldb/pyldb.c @@ -4314,7 +4314,7 @@ static PyMethodDef py_ldb_global_methods[] = { "S.string_to_time(string) -> int\n\n" "Parse a LDAP time string into a UNIX timestamp." }, { "valid_attr_name", py_valid_attr_name, METH_VARARGS, - "S.valid_attr_name(name) -> bool\n\nn" + "S.valid_attr_name(name) -> bool\n\n" "Check whether the supplied name is a valid attribute name." }, { "binary_encode", py_binary_encode, METH_VARARGS, "S.binary_encode(string) -> string\n\n" -- 2.25.1 From dbe65ae73a14fda277550c227067a8c0ba0ae68d Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 17 Dec 2020 11:56:08 +0100 Subject: [PATCH 012/224] CVE-2020-25718 lib:ldb: Use C99 initializers for builtin_popt_options[] Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit a593065c7f22e17434f33d0132cc6a7073acf414) --- lib/ldb/tools/cmdline.c | 250 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 225 insertions(+), 25 deletions(-) diff --git a/lib/ldb/tools/cmdline.c b/lib/ldb/tools/cmdline.c index c32470864d4f..affce47ac84d 100644 --- a/lib/ldb/tools/cmdline.c +++ b/lib/ldb/tools/cmdline.c @@ -34,31 +34,231 @@ enum ldb_cmdline_options { CMDLINE_RELAX=1 }; static struct poptOption builtin_popt_options[] = { POPT_AUTOHELP - { "url", 'H', POPT_ARG_STRING, &options.url, 0, "database URL", "URL" }, - { "basedn", 'b', POPT_ARG_STRING, &options.basedn, 0, "base DN", "DN" }, - { "editor", 'e', POPT_ARG_STRING, &options.editor, 0, "external editor", "PROGRAM" }, - { "scope", 's', POPT_ARG_STRING, NULL, 's', "search scope", "SCOPE" }, - { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', "increase verbosity", NULL }, - { "trace", 0, POPT_ARG_NONE, &options.tracing, 0, "enable tracing", NULL }, - { "interactive", 'i', POPT_ARG_NONE, &options.interactive, 0, "input from stdin", NULL }, - { "recursive", 'r', POPT_ARG_NONE, &options.recursive, 0, "recursive delete", NULL }, - { "modules-path", 0, POPT_ARG_STRING, &options.modules_path, 0, "modules path", "PATH" }, - { "num-searches", 0, POPT_ARG_INT, &options.num_searches, 0, "number of test searches", NULL }, - { "num-records", 0, POPT_ARG_INT, &options.num_records, 0, "number of test records", NULL }, - { "all", 'a', POPT_ARG_NONE, &options.all_records, 0, "(|(objectClass=*)(distinguishedName=*))", NULL }, - { "nosync", 0, POPT_ARG_NONE, &options.nosync, 0, "non-synchronous transactions", NULL }, - { "sorted", 'S', POPT_ARG_NONE, &options.sorted, 0, "sort attributes", NULL }, - { NULL, 'o', POPT_ARG_STRING, NULL, 'o', "ldb_connect option", "OPTION" }, - { "controls", 0, POPT_ARG_STRING, NULL, 'c', "controls", NULL }, - { "show-binary", 0, POPT_ARG_NONE, &options.show_binary, 0, "display binary LDIF", NULL }, - { "paged", 0, POPT_ARG_NONE, NULL, 'P', "use a paged search", NULL }, - { "show-deleted", 0, POPT_ARG_NONE, NULL, 'D', "show deleted objects", NULL }, - { "show-recycled", 0, POPT_ARG_NONE, NULL, 'R', "show recycled objects", NULL }, - { "show-deactivated-link", 0, POPT_ARG_NONE, NULL, 'd', "show deactivated links", NULL }, - { "reveal", 0, POPT_ARG_NONE, NULL, 'r', "reveal ldb internals", NULL }, - { "relax", 0, POPT_ARG_NONE, NULL, CMDLINE_RELAX, "pass relax control", NULL }, - { "cross-ncs", 0, POPT_ARG_NONE, NULL, 'N', "search across NC boundaries", NULL }, - { "extended-dn", 0, POPT_ARG_NONE, NULL, 'E', "show extended DNs", NULL }, + { + .longName = "url", + .shortName = 'H', + .argInfo = POPT_ARG_STRING, + .arg = &options.url, + .val = 0, + .descrip = "database URL", + .argDescrip = "URL" + }, + { + .longName = "basedn", + .shortName = 'b', + .argInfo = POPT_ARG_STRING, + .arg = &options.basedn, + .val = 0, + .descrip = "base DN", + .argDescrip = "DN" + }, + { + .longName = "editor", + .shortName = 'e', + .argInfo = POPT_ARG_STRING, + .arg = &options.editor, + .val = 0, + .descrip = "external editor", + .argDescrip = "PROGRAM" + }, + { + .longName = "scope", + .shortName = 's', + .argInfo = POPT_ARG_STRING, + .arg = NULL, + .val = 's', + .descrip = "search scope", + .argDescrip = "SCOPE" + }, + { + .longName = "verbose", + .shortName = 'v', + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'v', + .descrip = "increase verbosity", + .argDescrip = NULL + }, + { + .longName = "trace", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = &options.tracing, + .val = 0, + .descrip = "enable tracing", + .argDescrip = NULL + }, + { + .longName = "interactive", + .shortName = 'i', + .argInfo = POPT_ARG_NONE, + .arg = &options.interactive, + .val = 0, + .descrip = "input from stdin", + .argDescrip = NULL + }, + { + .longName = "recursive", + .shortName = 'r', + .argInfo = POPT_ARG_NONE, + .arg = &options.recursive, + .val = 0, + .descrip = "recursive delete", + .argDescrip = NULL + }, + { + .longName = "modules-path", + .shortName = 0, + .argInfo = POPT_ARG_STRING, + .arg = &options.modules_path, + .val = 0, + .descrip = "modules path", + .argDescrip = "PATH" + }, + { + .longName = "num-searches", + .shortName = 0, + .argInfo = POPT_ARG_INT, + .arg = &options.num_searches, + .val = 0, + .descrip = "number of test searches", + .argDescrip = NULL + }, + { + .longName = "num-records", + .shortName = 0, + .argInfo = POPT_ARG_INT, + .arg = &options.num_records, + .val = 0, + .descrip = "number of test records", + .argDescrip = NULL + }, + { + .longName = "all", + .shortName = 'a', + .argInfo = POPT_ARG_NONE, + .arg = &options.all_records, + .val = 0, + .descrip = "(|(objectClass=*)(distinguishedName=*))", + .argDescrip = NULL + }, + { + .longName = "nosync", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = &options.nosync, + .val = 0, + .descrip = "non-synchronous transactions", + .argDescrip = NULL + }, + { + .longName = "sorted", + .shortName = 'S', + .argInfo = POPT_ARG_NONE, + .arg = &options.sorted, + .val = 0, + .descrip = "sort attributes", + .argDescrip = NULL + }, + { + .longName = NULL, + .shortName = 'o', + .argInfo = POPT_ARG_STRING, + .arg = NULL, + .val = 'o', + .descrip = "ldb_connect option", + .argDescrip = "OPTION" + }, + { + .longName = "controls", + .shortName = 0, + .argInfo = POPT_ARG_STRING, + .arg = NULL, + .val = 'c', + .descrip = "controls", + .argDescrip = NULL + }, + { + .longName = "show-binary", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = &options.show_binary, + .val = 0, + .descrip = "display binary LDIF", + .argDescrip = NULL + }, + { + .longName = "paged", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'P', + .descrip = "use a paged search", + .argDescrip = NULL + }, + { + .longName = "show-deleted", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'D', + .descrip = "show deleted objects", + .argDescrip = NULL + }, + { + .longName = "show-recycled", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'R', + .descrip = "show recycled objects", + .argDescrip = NULL + }, + { + .longName = "show-deactivated-link", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'd', + .descrip = "show deactivated links", + .argDescrip = NULL + }, + { + .longName = "reveal", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'r', + .descrip = "reveal ldb internals", + .argDescrip = NULL + }, + { + .longName = "relax", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = CMDLINE_RELAX, + .descrip = "pass relax control", + .argDescrip = NULL + }, + { + .longName = "cross-ncs", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'N', + .descrip = "search across NC boundaries", + .argDescrip = NULL + }, + { + .longName = "extended-dn", + .shortName = 0, + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'E', + .descrip = "show extended DNs", + .argDescrip = NULL + }, {0} }; -- 2.25.1 From 8da5fb3c0b95b7c2162e43e0a54444ed4e8c87d4 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 17 Dec 2020 19:16:13 +0100 Subject: [PATCH 013/224] CVE-2020-25718 lib:ldb-samba: Improve calculate_popt_array_length() Note that memcmp() doesn't work well with padding bytes. So avoid it! (gdb) ptype/o struct poptOption /* offset | size */ type = struct poptOption { /* 0 | 8 */ const char *longName; /* 8 | 1 */ char shortName; /* XXX 3-byte hole */ /* 12 | 4 */ unsigned int argInfo; /* 16 | 8 */ void *arg; /* 24 | 4 */ int val; /* XXX 4-byte hole */ /* 32 | 8 */ const char *descrip; /* 40 | 8 */ const char *argDescrip; /* total size (bytes): 48 */ Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 (cherry picked from commit c2c7c1f50a8acb3169e19ba4329aa78839b66def) --- lib/ldb-samba/samba_extensions.c | 27 ++++++++++++++++++++++----- lib/ldb/tools/cmdline.c | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/ldb-samba/samba_extensions.c b/lib/ldb-samba/samba_extensions.c index 65a4079ec97e..60aa1a332b5a 100644 --- a/lib/ldb-samba/samba_extensions.c +++ b/lib/ldb-samba/samba_extensions.c @@ -34,15 +34,32 @@ #include "popt.h" +static bool is_popt_table_end(const struct poptOption *o) +{ + if (o->longName == NULL && + o->shortName =='\0' && + o->arg == NULL) { + return true; + } + + return false; +} /* work out the length of a popt array */ -static unsigned calculate_popt_array_length(struct poptOption *opts) +static size_t calculate_popt_array_length(struct poptOption *opts) { - unsigned i; - struct poptOption zero_opt = { 0 }; - for (i=0; memcmp(&zero_opt, &opts[i], sizeof(zero_opt)) != 0; i++) ; + size_t i = 0; + + for (i = 0; i < UINT32_MAX; i++) { + struct poptOption *o = &(opts[i]); + + if (is_popt_table_end(o)) { + break; + } + } + return i; } @@ -61,7 +78,7 @@ static int extensions_hook(struct ldb_context *ldb, enum ldb_module_hook_type t) { switch (t) { case LDB_MODULE_HOOK_CMDLINE_OPTIONS: { - unsigned len1, len2; + size_t len1, len2; struct poptOption **popt_options = ldb_module_popt_options(ldb); struct poptOption *new_array; diff --git a/lib/ldb/tools/cmdline.c b/lib/ldb/tools/cmdline.c index affce47ac84d..ff25fe05ec7c 100644 --- a/lib/ldb/tools/cmdline.c +++ b/lib/ldb/tools/cmdline.c @@ -259,7 +259,7 @@ static struct poptOption builtin_popt_options[] = { .descrip = "show extended DNs", .argDescrip = NULL }, - {0} + POPT_TABLEEND }; void ldb_cmdline_help(struct ldb_context *ldb, const char *cmdname, FILE *f) -- 2.25.1 From da017e0fa66152020041c8c528444972b5cd55e6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 2 Nov 2021 15:19:31 +0100 Subject: [PATCH 014/224] CVE-2020-25718 ldb: version 2.3.2 Backport all C code changes from ldb-2.4.1 to be available for Samba 4.14.x BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Stefan Metzmacher --- lib/ldb/ABI/ldb-2.3.2.sigs | 283 ++++++++++++++++++++++++++++++ lib/ldb/ABI/pyldb-util-2.3.2.sigs | 3 + lib/ldb/wscript | 3 +- 3 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 lib/ldb/ABI/ldb-2.3.2.sigs create mode 100644 lib/ldb/ABI/pyldb-util-2.3.2.sigs diff --git a/lib/ldb/ABI/ldb-2.3.2.sigs b/lib/ldb/ABI/ldb-2.3.2.sigs new file mode 100644 index 000000000000..5049dc64ce1a --- /dev/null +++ b/lib/ldb/ABI/ldb-2.3.2.sigs @@ -0,0 +1,283 @@ +ldb_add: int (struct ldb_context *, const struct ldb_message *) +ldb_any_comparison: int (struct ldb_context *, void *, ldb_attr_handler_t, const struct ldb_val *, const struct ldb_val *) +ldb_asprintf_errstring: void (struct ldb_context *, const char *, ...) +ldb_attr_casefold: char *(TALLOC_CTX *, const char *) +ldb_attr_dn: int (const char *) +ldb_attr_in_list: int (const char * const *, const char *) +ldb_attr_list_copy: const char **(TALLOC_CTX *, const char * const *) +ldb_attr_list_copy_add: const char **(TALLOC_CTX *, const char * const *, const char *) +ldb_base64_decode: int (char *) +ldb_base64_encode: char *(TALLOC_CTX *, const char *, int) +ldb_binary_decode: struct ldb_val (TALLOC_CTX *, const char *) +ldb_binary_encode: char *(TALLOC_CTX *, struct ldb_val) +ldb_binary_encode_string: char *(TALLOC_CTX *, const char *) +ldb_build_add_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_del_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_extended_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const char *, void *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_mod_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_rename_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_search_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, const char *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_search_req_ex: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, struct ldb_parse_tree *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_casefold: char *(struct ldb_context *, TALLOC_CTX *, const char *, size_t) +ldb_casefold_default: char *(void *, TALLOC_CTX *, const char *, size_t) +ldb_check_critical_controls: int (struct ldb_control **) +ldb_comparison_binary: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) +ldb_comparison_fold: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) +ldb_connect: int (struct ldb_context *, const char *, unsigned int, const char **) +ldb_control_to_string: char *(TALLOC_CTX *, const struct ldb_control *) +ldb_controls_except_specified: struct ldb_control **(struct ldb_control **, TALLOC_CTX *, struct ldb_control *) +ldb_debug: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) +ldb_debug_add: void (struct ldb_context *, const char *, ...) +ldb_debug_end: void (struct ldb_context *, enum ldb_debug_level) +ldb_debug_set: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) +ldb_delete: int (struct ldb_context *, struct ldb_dn *) +ldb_dn_add_base: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_add_base_fmt: bool (struct ldb_dn *, const char *, ...) +ldb_dn_add_child: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_add_child_fmt: bool (struct ldb_dn *, const char *, ...) +ldb_dn_add_child_val: bool (struct ldb_dn *, const char *, struct ldb_val) +ldb_dn_alloc_casefold: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_alloc_linearized: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_canonical_ex_string: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_canonical_string: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_check_local: bool (struct ldb_module *, struct ldb_dn *) +ldb_dn_check_special: bool (struct ldb_dn *, const char *) +ldb_dn_compare: int (struct ldb_dn *, struct ldb_dn *) +ldb_dn_compare_base: int (struct ldb_dn *, struct ldb_dn *) +ldb_dn_copy: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_escape_value: char *(TALLOC_CTX *, struct ldb_val) +ldb_dn_extended_add_syntax: int (struct ldb_context *, unsigned int, const struct ldb_dn_extended_syntax *) +ldb_dn_extended_filter: void (struct ldb_dn *, const char * const *) +ldb_dn_extended_syntax_by_name: const struct ldb_dn_extended_syntax *(struct ldb_context *, const char *) +ldb_dn_from_ldb_val: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const struct ldb_val *) +ldb_dn_get_casefold: const char *(struct ldb_dn *) +ldb_dn_get_comp_num: int (struct ldb_dn *) +ldb_dn_get_component_name: const char *(struct ldb_dn *, unsigned int) +ldb_dn_get_component_val: const struct ldb_val *(struct ldb_dn *, unsigned int) +ldb_dn_get_extended_comp_num: int (struct ldb_dn *) +ldb_dn_get_extended_component: const struct ldb_val *(struct ldb_dn *, const char *) +ldb_dn_get_extended_linearized: char *(TALLOC_CTX *, struct ldb_dn *, int) +ldb_dn_get_ldb_context: struct ldb_context *(struct ldb_dn *) +ldb_dn_get_linearized: const char *(struct ldb_dn *) +ldb_dn_get_parent: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_get_rdn_name: const char *(struct ldb_dn *) +ldb_dn_get_rdn_val: const struct ldb_val *(struct ldb_dn *) +ldb_dn_has_extended: bool (struct ldb_dn *) +ldb_dn_is_null: bool (struct ldb_dn *) +ldb_dn_is_special: bool (struct ldb_dn *) +ldb_dn_is_valid: bool (struct ldb_dn *) +ldb_dn_map_local: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_map_rebase_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_map_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_minimise: bool (struct ldb_dn *) +ldb_dn_new: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *) +ldb_dn_new_fmt: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *, ...) +ldb_dn_remove_base_components: bool (struct ldb_dn *, unsigned int) +ldb_dn_remove_child_components: bool (struct ldb_dn *, unsigned int) +ldb_dn_remove_extended_components: void (struct ldb_dn *) +ldb_dn_replace_components: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_set_component: int (struct ldb_dn *, int, const char *, const struct ldb_val) +ldb_dn_set_extended_component: int (struct ldb_dn *, const char *, const struct ldb_val *) +ldb_dn_update_components: int (struct ldb_dn *, const struct ldb_dn *) +ldb_dn_validate: bool (struct ldb_dn *) +ldb_dump_results: void (struct ldb_context *, struct ldb_result *, FILE *) +ldb_error_at: int (struct ldb_context *, int, const char *, const char *, int) +ldb_errstring: const char *(struct ldb_context *) +ldb_extended: int (struct ldb_context *, const char *, void *, struct ldb_result **) +ldb_extended_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_filter_attrs: int (struct ldb_context *, const struct ldb_message *, const char * const *, struct ldb_message *) +ldb_filter_from_tree: char *(TALLOC_CTX *, const struct ldb_parse_tree *) +ldb_get_config_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_create_perms: unsigned int (struct ldb_context *) +ldb_get_default_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_event_context: struct tevent_context *(struct ldb_context *) +ldb_get_flags: unsigned int (struct ldb_context *) +ldb_get_opaque: void *(struct ldb_context *, const char *) +ldb_get_root_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_schema_basedn: struct ldb_dn *(struct ldb_context *) +ldb_global_init: int (void) +ldb_handle_get_event_context: struct tevent_context *(struct ldb_handle *) +ldb_handle_new: struct ldb_handle *(TALLOC_CTX *, struct ldb_context *) +ldb_handle_use_global_event_context: void (struct ldb_handle *) +ldb_handler_copy: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) +ldb_handler_fold: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) +ldb_init: struct ldb_context *(TALLOC_CTX *, struct tevent_context *) +ldb_ldif_message_redacted_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) +ldb_ldif_message_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) +ldb_ldif_parse_modrdn: int (struct ldb_context *, const struct ldb_ldif *, TALLOC_CTX *, struct ldb_dn **, struct ldb_dn **, bool *, struct ldb_dn **, struct ldb_dn **) +ldb_ldif_read: struct ldb_ldif *(struct ldb_context *, int (*)(void *), void *) +ldb_ldif_read_file: struct ldb_ldif *(struct ldb_context *, FILE *) +ldb_ldif_read_file_state: struct ldb_ldif *(struct ldb_context *, struct ldif_read_file_state *) +ldb_ldif_read_free: void (struct ldb_context *, struct ldb_ldif *) +ldb_ldif_read_string: struct ldb_ldif *(struct ldb_context *, const char **) +ldb_ldif_write: int (struct ldb_context *, int (*)(void *, const char *, ...), void *, const struct ldb_ldif *) +ldb_ldif_write_file: int (struct ldb_context *, FILE *, const struct ldb_ldif *) +ldb_ldif_write_redacted_trace_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) +ldb_ldif_write_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) +ldb_load_modules: int (struct ldb_context *, const char **) +ldb_map_add: int (struct ldb_module *, struct ldb_request *) +ldb_map_delete: int (struct ldb_module *, struct ldb_request *) +ldb_map_init: int (struct ldb_module *, const struct ldb_map_attribute *, const struct ldb_map_objectclass *, const char * const *, const char *, const char *) +ldb_map_modify: int (struct ldb_module *, struct ldb_request *) +ldb_map_rename: int (struct ldb_module *, struct ldb_request *) +ldb_map_search: int (struct ldb_module *, struct ldb_request *) +ldb_match_message: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, enum ldb_scope, bool *) +ldb_match_msg: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope) +ldb_match_msg_error: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope, bool *) +ldb_match_msg_objectclass: int (const struct ldb_message *, const char *) +ldb_mod_register_control: int (struct ldb_module *, const char *) +ldb_modify: int (struct ldb_context *, const struct ldb_message *) +ldb_modify_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_module_call_chain: char *(struct ldb_request *, TALLOC_CTX *) +ldb_module_connect_backend: int (struct ldb_context *, const char *, const char **, struct ldb_module **) +ldb_module_done: int (struct ldb_request *, struct ldb_control **, struct ldb_extended *, int) +ldb_module_flags: uint32_t (struct ldb_context *) +ldb_module_get_ctx: struct ldb_context *(struct ldb_module *) +ldb_module_get_name: const char *(struct ldb_module *) +ldb_module_get_ops: const struct ldb_module_ops *(struct ldb_module *) +ldb_module_get_private: void *(struct ldb_module *) +ldb_module_init_chain: int (struct ldb_context *, struct ldb_module *) +ldb_module_load_list: int (struct ldb_context *, const char **, struct ldb_module *, struct ldb_module **) +ldb_module_new: struct ldb_module *(TALLOC_CTX *, struct ldb_context *, const char *, const struct ldb_module_ops *) +ldb_module_next: struct ldb_module *(struct ldb_module *) +ldb_module_popt_options: struct poptOption **(struct ldb_context *) +ldb_module_send_entry: int (struct ldb_request *, struct ldb_message *, struct ldb_control **) +ldb_module_send_referral: int (struct ldb_request *, char *) +ldb_module_set_next: void (struct ldb_module *, struct ldb_module *) +ldb_module_set_private: void (struct ldb_module *, void *) +ldb_modules_hook: int (struct ldb_context *, enum ldb_module_hook_type) +ldb_modules_list_from_string: const char **(struct ldb_context *, TALLOC_CTX *, const char *) +ldb_modules_load: int (const char *, const char *) +ldb_msg_add: int (struct ldb_message *, const struct ldb_message_element *, int) +ldb_msg_add_empty: int (struct ldb_message *, const char *, int, struct ldb_message_element **) +ldb_msg_add_fmt: int (struct ldb_message *, const char *, const char *, ...) +ldb_msg_add_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *) +ldb_msg_add_steal_string: int (struct ldb_message *, const char *, char *) +ldb_msg_add_steal_value: int (struct ldb_message *, const char *, struct ldb_val *) +ldb_msg_add_string: int (struct ldb_message *, const char *, const char *) +ldb_msg_add_value: int (struct ldb_message *, const char *, const struct ldb_val *, struct ldb_message_element **) +ldb_msg_canonicalize: struct ldb_message *(struct ldb_context *, const struct ldb_message *) +ldb_msg_check_string_attribute: int (const struct ldb_message *, const char *, const char *) +ldb_msg_copy: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) +ldb_msg_copy_attr: int (struct ldb_message *, const char *, const char *) +ldb_msg_copy_shallow: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) +ldb_msg_diff: struct ldb_message *(struct ldb_context *, struct ldb_message *, struct ldb_message *) +ldb_msg_difference: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message *, struct ldb_message *, struct ldb_message **) +ldb_msg_element_compare: int (struct ldb_message_element *, struct ldb_message_element *) +ldb_msg_element_compare_name: int (struct ldb_message_element *, struct ldb_message_element *) +ldb_msg_element_equal_ordered: bool (const struct ldb_message_element *, const struct ldb_message_element *) +ldb_msg_find_attr_as_bool: int (const struct ldb_message *, const char *, int) +ldb_msg_find_attr_as_dn: struct ldb_dn *(struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, const char *) +ldb_msg_find_attr_as_double: double (const struct ldb_message *, const char *, double) +ldb_msg_find_attr_as_int: int (const struct ldb_message *, const char *, int) +ldb_msg_find_attr_as_int64: int64_t (const struct ldb_message *, const char *, int64_t) +ldb_msg_find_attr_as_string: const char *(const struct ldb_message *, const char *, const char *) +ldb_msg_find_attr_as_uint: unsigned int (const struct ldb_message *, const char *, unsigned int) +ldb_msg_find_attr_as_uint64: uint64_t (const struct ldb_message *, const char *, uint64_t) +ldb_msg_find_common_values: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message_element *, struct ldb_message_element *, uint32_t) +ldb_msg_find_duplicate_val: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message_element *, struct ldb_val **, uint32_t) +ldb_msg_find_element: struct ldb_message_element *(const struct ldb_message *, const char *) +ldb_msg_find_ldb_val: const struct ldb_val *(const struct ldb_message *, const char *) +ldb_msg_find_val: struct ldb_val *(const struct ldb_message_element *, struct ldb_val *) +ldb_msg_new: struct ldb_message *(TALLOC_CTX *) +ldb_msg_normalize: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_message **) +ldb_msg_remove_attr: void (struct ldb_message *, const char *) +ldb_msg_remove_element: void (struct ldb_message *, struct ldb_message_element *) +ldb_msg_rename_attr: int (struct ldb_message *, const char *, const char *) +ldb_msg_sanity_check: int (struct ldb_context *, const struct ldb_message *) +ldb_msg_sort_elements: void (struct ldb_message *) +ldb_next_del_trans: int (struct ldb_module *) +ldb_next_end_trans: int (struct ldb_module *) +ldb_next_init: int (struct ldb_module *) +ldb_next_prepare_commit: int (struct ldb_module *) +ldb_next_read_lock: int (struct ldb_module *) +ldb_next_read_unlock: int (struct ldb_module *) +ldb_next_remote_request: int (struct ldb_module *, struct ldb_request *) +ldb_next_request: int (struct ldb_module *, struct ldb_request *) +ldb_next_start_trans: int (struct ldb_module *) +ldb_op_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_options_copy: const char **(TALLOC_CTX *, const char **) +ldb_options_find: const char *(struct ldb_context *, const char **, const char *) +ldb_options_get: const char **(struct ldb_context *) +ldb_pack_data: int (struct ldb_context *, const struct ldb_message *, struct ldb_val *, uint32_t) +ldb_parse_control_from_string: struct ldb_control *(struct ldb_context *, TALLOC_CTX *, const char *) +ldb_parse_control_strings: struct ldb_control **(struct ldb_context *, TALLOC_CTX *, const char **) +ldb_parse_tree: struct ldb_parse_tree *(TALLOC_CTX *, const char *) +ldb_parse_tree_attr_replace: void (struct ldb_parse_tree *, const char *, const char *) +ldb_parse_tree_copy_shallow: struct ldb_parse_tree *(TALLOC_CTX *, const struct ldb_parse_tree *) +ldb_parse_tree_walk: int (struct ldb_parse_tree *, int (*)(struct ldb_parse_tree *, void *), void *) +ldb_qsort: void (void * const, size_t, size_t, void *, ldb_qsort_cmp_fn_t) +ldb_register_backend: int (const char *, ldb_connect_fn, bool) +ldb_register_extended_match_rule: int (struct ldb_context *, const struct ldb_extended_match_rule *) +ldb_register_hook: int (ldb_hook_fn) +ldb_register_module: int (const struct ldb_module_ops *) +ldb_rename: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *) +ldb_reply_add_control: int (struct ldb_reply *, const char *, bool, void *) +ldb_reply_get_control: struct ldb_control *(struct ldb_reply *, const char *) +ldb_req_get_custom_flags: uint32_t (struct ldb_request *) +ldb_req_is_untrusted: bool (struct ldb_request *) +ldb_req_location: const char *(struct ldb_request *) +ldb_req_mark_trusted: void (struct ldb_request *) +ldb_req_mark_untrusted: void (struct ldb_request *) +ldb_req_set_custom_flags: void (struct ldb_request *, uint32_t) +ldb_req_set_location: void (struct ldb_request *, const char *) +ldb_request: int (struct ldb_context *, struct ldb_request *) +ldb_request_add_control: int (struct ldb_request *, const char *, bool, void *) +ldb_request_done: int (struct ldb_request *, int) +ldb_request_get_control: struct ldb_control *(struct ldb_request *, const char *) +ldb_request_get_status: int (struct ldb_request *) +ldb_request_replace_control: int (struct ldb_request *, const char *, bool, void *) +ldb_request_set_state: void (struct ldb_request *, int) +ldb_reset_err_string: void (struct ldb_context *) +ldb_save_controls: int (struct ldb_control *, struct ldb_request *, struct ldb_control ***) +ldb_schema_attribute_add: int (struct ldb_context *, const char *, unsigned int, const char *) +ldb_schema_attribute_add_with_syntax: int (struct ldb_context *, const char *, unsigned int, const struct ldb_schema_syntax *) +ldb_schema_attribute_by_name: const struct ldb_schema_attribute *(struct ldb_context *, const char *) +ldb_schema_attribute_fill_with_syntax: int (struct ldb_context *, TALLOC_CTX *, const char *, unsigned int, const struct ldb_schema_syntax *, struct ldb_schema_attribute *) +ldb_schema_attribute_remove: void (struct ldb_context *, const char *) +ldb_schema_attribute_remove_flagged: void (struct ldb_context *, unsigned int) +ldb_schema_attribute_set_override_handler: void (struct ldb_context *, ldb_attribute_handler_override_fn_t, void *) +ldb_schema_set_override_GUID_index: void (struct ldb_context *, const char *, const char *) +ldb_schema_set_override_indexlist: void (struct ldb_context *, bool) +ldb_search: int (struct ldb_context *, TALLOC_CTX *, struct ldb_result **, struct ldb_dn *, enum ldb_scope, const char * const *, const char *, ...) +ldb_search_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_sequence_number: int (struct ldb_context *, enum ldb_sequence_type, uint64_t *) +ldb_set_create_perms: void (struct ldb_context *, unsigned int) +ldb_set_debug: int (struct ldb_context *, void (*)(void *, enum ldb_debug_level, const char *, va_list), void *) +ldb_set_debug_stderr: int (struct ldb_context *) +ldb_set_default_dns: void (struct ldb_context *) +ldb_set_errstring: void (struct ldb_context *, const char *) +ldb_set_event_context: void (struct ldb_context *, struct tevent_context *) +ldb_set_flags: void (struct ldb_context *, unsigned int) +ldb_set_modules_dir: void (struct ldb_context *, const char *) +ldb_set_opaque: int (struct ldb_context *, const char *, void *) +ldb_set_require_private_event_context: void (struct ldb_context *) +ldb_set_timeout: int (struct ldb_context *, struct ldb_request *, int) +ldb_set_timeout_from_prev_req: int (struct ldb_context *, struct ldb_request *, struct ldb_request *) +ldb_set_utf8_default: void (struct ldb_context *) +ldb_set_utf8_fns: void (struct ldb_context *, void *, char *(*)(void *, void *, const char *, size_t)) +ldb_setup_wellknown_attributes: int (struct ldb_context *) +ldb_should_b64_encode: int (struct ldb_context *, const struct ldb_val *) +ldb_standard_syntax_by_name: const struct ldb_schema_syntax *(struct ldb_context *, const char *) +ldb_strerror: const char *(int) +ldb_string_to_time: time_t (const char *) +ldb_string_utc_to_time: time_t (const char *) +ldb_timestring: char *(TALLOC_CTX *, time_t) +ldb_timestring_utc: char *(TALLOC_CTX *, time_t) +ldb_transaction_cancel: int (struct ldb_context *) +ldb_transaction_cancel_noerr: int (struct ldb_context *) +ldb_transaction_commit: int (struct ldb_context *) +ldb_transaction_prepare_commit: int (struct ldb_context *) +ldb_transaction_start: int (struct ldb_context *) +ldb_unpack_data: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *) +ldb_unpack_data_flags: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *, unsigned int) +ldb_unpack_get_format: int (const struct ldb_val *, uint32_t *) +ldb_val_dup: struct ldb_val (TALLOC_CTX *, const struct ldb_val *) +ldb_val_equal_exact: int (const struct ldb_val *, const struct ldb_val *) +ldb_val_map_local: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) +ldb_val_map_remote: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) +ldb_val_string_cmp: int (const struct ldb_val *, const char *) +ldb_val_to_time: int (const struct ldb_val *, time_t *) +ldb_valid_attr_name: int (const char *) +ldb_vdebug: void (struct ldb_context *, enum ldb_debug_level, const char *, va_list) +ldb_wait: int (struct ldb_handle *, enum ldb_wait_type) diff --git a/lib/ldb/ABI/pyldb-util-2.3.2.sigs b/lib/ldb/ABI/pyldb-util-2.3.2.sigs new file mode 100644 index 000000000000..164a806b2ffc --- /dev/null +++ b/lib/ldb/ABI/pyldb-util-2.3.2.sigs @@ -0,0 +1,3 @@ +pyldb_Dn_FromDn: PyObject *(struct ldb_dn *) +pyldb_Object_AsDn: bool (TALLOC_CTX *, PyObject *, struct ldb_context *, struct ldb_dn **) +pyldb_check_type: bool (PyObject *, const char *) diff --git a/lib/ldb/wscript b/lib/ldb/wscript index 339da577e3b9..38f2d578c2e8 100644 --- a/lib/ldb/wscript +++ b/lib/ldb/wscript @@ -1,7 +1,8 @@ #!/usr/bin/env python APPNAME = 'ldb' -VERSION = '2.3.1' +# For Samba 4.14.x +VERSION = '2.3.2' import sys, os -- 2.25.1 From 4c7f2cd83967b91955dcef36daf6bdc14b07b2d4 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 11 May 2021 17:59:51 +0200 Subject: [PATCH 015/224] CVE-2020-25717 selftest: Pass down the machine account name to provision_ad_member Signed-off-by: Andreas Schneider Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit fbe68dcbb783409589cdefd8ee551c9971c51f08) Needed as preparation for CVE-2020-25717 --- selftest/target/Samba.pm | 1 + selftest/target/Samba3.pm | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/selftest/target/Samba.pm b/selftest/target/Samba.pm index 5a7efa9c2803..d48ac2c64a1a 100644 --- a/selftest/target/Samba.pm +++ b/selftest/target/Samba.pm @@ -588,6 +588,7 @@ sub get_interface($) addcsmb1 => 54, lclnt4dc2smb1 => 55, fipsdc => 56, + fipsadmember => 57, rootdnsforwarder => 64, diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index 4ec95e237a7c..e67347b7df94 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -646,6 +646,7 @@ sub provision_ad_member { my ($self, $prefix, + $machine_account, $dcvars, $trustvars_f, $trustvars_e, @@ -762,7 +763,7 @@ sub provision_ad_member prefix => $prefix, domain => $dcvars->{DOMAIN}, realm => $dcvars->{REALM}, - server => "LOCALADMEMBER", + server => $machine_account, password => "loCalMemberPass", extra_options => $member_options, resolv_conf => $dcvars->{RESOLV_CONF}); @@ -876,7 +877,11 @@ sub setup_ad_member print "PROVISIONING AD MEMBER..."; - return $self->provision_ad_member($prefix, $dcvars, $trustvars_f, $trustvars_e); + return $self->provision_ad_member($prefix, + "LOCALADMEMBER", + $dcvars, + $trustvars_f, + $trustvars_e); } sub setup_ad_member_rfc2307 @@ -1199,6 +1204,7 @@ sub setup_ad_member_fips print "PROVISIONING AD FIPS MEMBER..."; return $self->provision_ad_member($prefix, + "FIPSADMEMBER", $dcvars, $trustvars_f, $trustvars_e, -- 2.25.1 From 1b7fa57c7b0e55af9777545fb65b069cec81eaf8 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 10 Jun 2021 16:20:28 +0200 Subject: [PATCH 016/224] CVE-2020-25717 selftest: Only set netbios aliases for the ad_member env The provision_ad_member() function is reused by different setup_ad_member*() functions. Each environment needs to have unique netbios aliases as they are all in the same network. The aliases should only be set for the 'ad_member' environment. Signed-Off-By: Andreas Schneider Reviewed-by: Jeremy Allison Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Fri Jun 11 01:26:36 UTC 2021 on sn-devel-184 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit e165dcc770ec58c3749d653d6cb85f6ecf9479d6) --- selftest/target/Samba3.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index e67347b7df94..8b68fc0fe4fb 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -682,11 +682,17 @@ sub provision_ad_member $substitution_path = "$share_dir/D_$dcvars->{DOMAIN}/u_$dcvars->{DOMAIN}/alice/g_$dcvars->{DOMAIN}/domain users"; push(@dirs, $substitution_path); + + my $netbios_aliases = ""; + if ($machine_account eq "LOCALADMEMBER") { + $netbios_aliases = "netbios aliases = foo bar"; + } + my $member_options = " security = ads workgroup = $dcvars->{DOMAIN} realm = $dcvars->{REALM} - netbios aliases = foo bar + $netbios_aliases template homedir = /home/%D/%G/%U auth event notification = true password server = $dcvars->{SERVER} -- 2.25.1 From 2a14c2e56e62428f4ea5850a29191497f5770703 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Wed, 14 Apr 2021 10:05:59 +0200 Subject: [PATCH 017/224] CVE-2020-25717 auth3: Simplify check_samba4_security() First set up "server_info" in a local variable and once it's fully set up, assign it to the out parameter "pserver_info". Pointer dereferencing obfuscates the code for me. Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit 062a0c14c6ee0b74e7619af73747df59c5e67672) --- source3/auth/auth_samba4.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/source3/auth/auth_samba4.c b/source3/auth/auth_samba4.c index 0a7c67be2323..ac1413547473 100644 --- a/source3/auth/auth_samba4.c +++ b/source3/auth/auth_samba4.c @@ -108,11 +108,12 @@ static struct server_id *new_server_id_task(TALLOC_CTX *mem_ctx) * services the AD DC. It is tested via pdbtest. */ -static NTSTATUS check_samba4_security(const struct auth_context *auth_context, - void *my_private_data, - TALLOC_CTX *mem_ctx, - const struct auth_usersupplied_info *user_info, - struct auth_serversupplied_info **server_info) +static NTSTATUS check_samba4_security( + const struct auth_context *auth_context, + void *my_private_data, + TALLOC_CTX *mem_ctx, + const struct auth_usersupplied_info *user_info, + struct auth_serversupplied_info **pserver_info) { TALLOC_CTX *frame = talloc_stackframe(); struct netr_SamInfo3 *info3 = NULL; @@ -120,6 +121,7 @@ static NTSTATUS check_samba4_security(const struct auth_context *auth_context, struct auth_user_info_dc *user_info_dc; struct auth4_context *auth4_context; uint8_t authoritative = 0; + struct auth_serversupplied_info *server_info = NULL; nt_status = make_auth4_context_s4(auth_context, mem_ctx, &auth4_context); if (!NT_STATUS_IS_OK(nt_status)) { @@ -161,17 +163,19 @@ static NTSTATUS check_samba4_security(const struct auth_context *auth_context, } if (user_info->flags & USER_INFO_INFO3_AND_NO_AUTHZ) { - *server_info = make_server_info(mem_ctx); - if (*server_info == NULL) { + server_info = make_server_info(mem_ctx); + if (server_info == NULL) { nt_status = NT_STATUS_NO_MEMORY; goto done; } - (*server_info)->info3 = talloc_steal(*server_info, info3); - + server_info->info3 = talloc_move(server_info, &info3); } else { - nt_status = make_server_info_info3(mem_ctx, user_info->client.account_name, - user_info->mapped.domain_name, server_info, - info3); + nt_status = make_server_info_info3( + mem_ctx, + user_info->client.account_name, + user_info->mapped.domain_name, + &server_info, + info3); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(10, ("make_server_info_info3 failed: %s\n", nt_errstr(nt_status))); @@ -179,6 +183,7 @@ static NTSTATUS check_samba4_security(const struct auth_context *auth_context, } } + *pserver_info = server_info; nt_status = NT_STATUS_OK; done: -- 2.25.1 From 28b0afe967991a362427650afed1e7b27cef6091 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 13 Apr 2021 15:14:01 +0000 Subject: [PATCH 018/224] CVE-2020-25717 auth: Simplify DEBUG statements in make_auth3_context_for_ntlm() Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit 8536bf7fce41c43bbed25f7ed4ce5775a1b9c0d5) --- source3/auth/auth.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/source3/auth/auth.c b/source3/auth/auth.c index fa73c4e4db28..6d2a40f0f298 100644 --- a/source3/auth/auth.c +++ b/source3/auth/auth.c @@ -530,28 +530,28 @@ NTSTATUS make_auth3_context_for_ntlm(TALLOC_CTX *mem_ctx, struct auth_context **auth_context) { const char *methods = NULL; + const char *role = NULL; switch (lp_server_role()) { case ROLE_ACTIVE_DIRECTORY_DC: - DEBUG(5,("Making default auth method list for server role = " - "'active directory domain controller'\n")); + role = "'active directory domain controller'"; methods = "samba4"; break; case ROLE_DOMAIN_MEMBER: - DEBUG(5,("Making default auth method list for server role = 'domain member'\n")); + role = "'domain member'"; methods = "anonymous sam winbind sam_ignoredomain"; break; case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: - DEBUG(5,("Making default auth method list for DC\n")); + role = "'DC'"; methods = "anonymous sam winbind sam_ignoredomain"; break; case ROLE_STANDALONE: - DEBUG(5,("Making default auth method list for server role = 'standalone server', encrypt passwords = yes\n")); if (lp_encrypt_passwords()) { + role = "'standalone server', encrypt passwords = yes"; methods = "anonymous sam_ignoredomain"; } else { - DEBUG(5,("Making default auth method list for server role = 'standalone server', encrypt passwords = no\n")); + role = "'standalone server', encrypt passwords = no"; methods = "anonymous unix"; } break; @@ -560,6 +560,9 @@ NTSTATUS make_auth3_context_for_ntlm(TALLOC_CTX *mem_ctx, return NT_STATUS_UNSUCCESSFUL; } + DBG_INFO("Making default auth method list for server role = %s\n", + role); + return make_auth_context_specific(mem_ctx, auth_context, methods); } -- 2.25.1 From bcb77ae6242b645f153c689b034d5f4ef95eb221 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Wed, 14 Apr 2021 21:48:32 +0200 Subject: [PATCH 019/224] CVE-2020-25717 auth4: Make auth_anonymous pseudo-async Signed-off-by: Volker Lendecke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit 759573136876ef2b1b1c7484f99570d7de957e0d) --- source4/auth/ntlm/auth_anonymous.c | 66 ++++++++++++++++++++++++++---- source4/auth/ntlm/wscript_build | 2 +- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/source4/auth/ntlm/auth_anonymous.c b/source4/auth/ntlm/auth_anonymous.c index 83aeb431f5f4..a25aacaa1378 100644 --- a/source4/auth/ntlm/auth_anonymous.c +++ b/source4/auth/ntlm/auth_anonymous.c @@ -20,9 +20,11 @@ */ #include "includes.h" +#include #include "auth/auth.h" #include "auth/ntlm/auth_proto.h" #include "param/param.h" +#include "lib/util/tevent_ntstatus.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -84,19 +86,65 @@ static NTSTATUS anonymous_want_check(struct auth_method_context *ctx, * anonymou logons to be dealt with in one place. Non-anonymou logons 'fail' * and pass onto the next module. **/ -static NTSTATUS anonymous_check_password(struct auth_method_context *ctx, - TALLOC_CTX *mem_ctx, - const struct auth_usersupplied_info *user_info, - struct auth_user_info_dc **_user_info_dc, - bool *authoritative) + +struct anonymous_check_password_state { + struct auth_user_info_dc *user_info_dc; +}; + +static struct tevent_req *anonymous_check_password_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct auth_method_context *ctx, + const struct auth_usersupplied_info *user_info) +{ + struct tevent_req *req = NULL; + struct anonymous_check_password_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create( + mem_ctx, + &state, + struct anonymous_check_password_state); + if (req == NULL) { + return NULL; + } + + status = auth_anonymous_user_info_dc( + state, + lpcfg_netbios_name(ctx->auth_ctx->lp_ctx), + &state->user_info_dc); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS anonymous_check_password_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct auth_user_info_dc **interim_info, + bool *authoritative) { - return auth_anonymous_user_info_dc(mem_ctx, lpcfg_netbios_name(ctx->auth_ctx->lp_ctx), _user_info_dc); + struct anonymous_check_password_state *state = tevent_req_data( + req, struct anonymous_check_password_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + *interim_info = talloc_move(mem_ctx, &state->user_info_dc); + tevent_req_received(req); + return NT_STATUS_OK; } + static const struct auth_operations anonymous_auth_ops = { - .name = "anonymous", - .want_check = anonymous_want_check, - .check_password = anonymous_check_password + .name = "anonymous", + .want_check = anonymous_want_check, + .check_password_send = anonymous_check_password_send, + .check_password_recv = anonymous_check_password_recv, }; _PUBLIC_ NTSTATUS auth4_anonymous_init(TALLOC_CTX *ctx) diff --git a/source4/auth/ntlm/wscript_build b/source4/auth/ntlm/wscript_build index 04a760c3e495..6ea0c4d7e3a8 100644 --- a/source4/auth/ntlm/wscript_build +++ b/source4/auth/ntlm/wscript_build @@ -12,7 +12,7 @@ bld.SAMBA_MODULE('auth4_anonymous', source='auth_anonymous.c', subsystem='auth4', init_function='auth4_anonymous_init', - deps='talloc' + deps='tevent' ) -- 2.25.1 From 462bcaa4e24e83cbba7598de2b94d6ebaab2df75 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Wed, 14 Apr 2021 22:22:18 +0200 Subject: [PATCH 020/224] CVE-2020-25717 auth4: Make auth_developer pseudo-async This is a simpler approach to really just wrap the code. Signed-off-by: Volker Lendecke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit 43a1e42815718591faa8d526319b96d089a758fa) --- source4/auth/ntlm/auth_developer.c | 61 +++++++++++++++++++++++++++++- source4/auth/ntlm/wscript_build | 2 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/source4/auth/ntlm/auth_developer.c b/source4/auth/ntlm/auth_developer.c index 209786b63b2f..1823989c68d3 100644 --- a/source4/auth/ntlm/auth_developer.c +++ b/source4/auth/ntlm/auth_developer.c @@ -20,9 +20,11 @@ */ #include "includes.h" +#include #include "auth/auth.h" #include "auth/ntlm/auth_proto.h" #include "libcli/security/security.h" +#include "lib/util/tevent_ntstatus.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -137,10 +139,67 @@ static NTSTATUS name_to_ntstatus_check_password(struct auth_method_context *ctx, return nt_status; } +struct name_to_ntstatus_check_password_state { + struct auth_user_info_dc *user_info_dc; + bool authoritative; +}; + +static struct tevent_req *name_to_ntstatus_check_password_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct auth_method_context *ctx, + const struct auth_usersupplied_info *user_info) +{ + struct tevent_req *req = NULL; + struct name_to_ntstatus_check_password_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create( + mem_ctx, + &state, + struct name_to_ntstatus_check_password_state); + if (req == NULL) { + return NULL; + } + + status = name_to_ntstatus_check_password( + ctx, + state, + user_info, + &state->user_info_dc, + &state->authoritative); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS name_to_ntstatus_check_password_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct auth_user_info_dc **interim_info, + bool *authoritative) +{ + struct name_to_ntstatus_check_password_state *state = tevent_req_data( + req, struct name_to_ntstatus_check_password_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + *interim_info = talloc_move(mem_ctx, &state->user_info_dc); + *authoritative = state->authoritative; + tevent_req_received(req); + return NT_STATUS_OK; +} + static const struct auth_operations name_to_ntstatus_auth_ops = { .name = "name_to_ntstatus", .want_check = name_to_ntstatus_want_check, - .check_password = name_to_ntstatus_check_password + .check_password_send = name_to_ntstatus_check_password_send, + .check_password_recv = name_to_ntstatus_check_password_recv, }; _PUBLIC_ NTSTATUS auth4_developer_init(TALLOC_CTX *ctx) diff --git a/source4/auth/ntlm/wscript_build b/source4/auth/ntlm/wscript_build index 6ea0c4d7e3a8..1ee8d79563a1 100644 --- a/source4/auth/ntlm/wscript_build +++ b/source4/auth/ntlm/wscript_build @@ -28,7 +28,7 @@ bld.SAMBA_MODULE('auth4_developer', source='auth_developer.c', subsystem='auth4', init_function='auth4_developer_init', - deps='talloc' + deps='tevent' ) -- 2.25.1 From 66083c6de54c550af64c406d1f48ee4a71950bf5 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Wed, 14 Apr 2021 21:59:55 +0200 Subject: [PATCH 021/224] CVE-2020-25717 auth4: Make auth_unix pseudo-async Signed-off-by: Volker Lendecke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit a6f42ab8a778b9863990da3112c2e868cd006303) --- source4/auth/ntlm/auth_unix.c | 85 ++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/source4/auth/ntlm/auth_unix.c b/source4/auth/ntlm/auth_unix.c index 67cd5f3dc44c..cfe4f1a073f1 100644 --- a/source4/auth/ntlm/auth_unix.c +++ b/source4/auth/ntlm/auth_unix.c @@ -27,6 +27,7 @@ #include "lib/tsocket/tsocket.h" #include "../libcli/auth/pam_errors.h" #include "param/param.h" +#include "lib/util/tevent_ntstatus.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -713,46 +714,78 @@ static NTSTATUS authunix_want_check(struct auth_method_context *ctx, return NT_STATUS_OK; } -static NTSTATUS authunix_check_password(struct auth_method_context *ctx, - TALLOC_CTX *mem_ctx, - const struct auth_usersupplied_info *user_info, - struct auth_user_info_dc **user_info_dc, - bool *authoritative) +struct authunix_check_password_state { + struct auth_user_info_dc *user_info_dc; +}; + +static struct tevent_req *authunix_check_password_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct auth_method_context *ctx, + const struct auth_usersupplied_info *user_info) { - TALLOC_CTX *check_ctx; - NTSTATUS nt_status; - struct passwd *pwd; + struct tevent_req *req = NULL; + struct authunix_check_password_state *state = NULL; + struct passwd *pwd = NULL; + NTSTATUS status; - if (user_info->password_state != AUTH_PASSWORD_PLAIN) { - return NT_STATUS_INVALID_PARAMETER; + req = tevent_req_create( + mem_ctx, + &state, + struct authunix_check_password_state); + if (req == NULL) { + return NULL; } - check_ctx = talloc_named_const(mem_ctx, 0, "check_unix_password"); - if (check_ctx == NULL) { - return NT_STATUS_NO_MEMORY; + if (user_info->password_state != AUTH_PASSWORD_PLAIN) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); } - nt_status = check_unix_password(check_ctx, ctx->auth_ctx->lp_ctx, user_info, &pwd); - if (!NT_STATUS_IS_OK(nt_status)) { - talloc_free(check_ctx); - return nt_status; + status = check_unix_password( + state, ctx->auth_ctx->lp_ctx, user_info, &pwd); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); } - nt_status = authunix_make_user_info_dc(mem_ctx, lpcfg_netbios_name(ctx->auth_ctx->lp_ctx), - user_info, pwd, user_info_dc); - if (!NT_STATUS_IS_OK(nt_status)) { - talloc_free(check_ctx); - return nt_status; + status = authunix_make_user_info_dc( + state, + lpcfg_netbios_name(ctx->auth_ctx->lp_ctx), + user_info, + pwd, + &state->user_info_dc); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); } - talloc_free(check_ctx); + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS authunix_check_password_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct auth_user_info_dc **interim_info, + bool *authoritative) +{ + struct authunix_check_password_state *state = tevent_req_data( + req, struct authunix_check_password_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + *interim_info = talloc_move(mem_ctx, &state->user_info_dc); + tevent_req_received(req); return NT_STATUS_OK; } static const struct auth_operations unix_ops = { - .name = "unix", - .want_check = authunix_want_check, - .check_password = authunix_check_password + .name = "unix", + .want_check = authunix_want_check, + .check_password_send = authunix_check_password_send, + .check_password_recv = authunix_check_password_recv, }; _PUBLIC_ NTSTATUS auth4_unix_init(TALLOC_CTX *ctx) -- 2.25.1 From 93863d788aff73813082cc0626e83ed178b4b552 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Thu, 15 Apr 2021 10:04:21 +0200 Subject: [PATCH 022/224] CVE-2020-25717 auth4: Make auth_sam pseudo-async Signed-off-by: Volker Lendecke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit f852fb4cd4e2bcd676a9ea104c5bf00979771eed) --- source4/auth/ntlm/auth_sam.c | 69 ++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c index c5b271719371..a521bc94bc4a 100644 --- a/source4/auth/ntlm/auth_sam.c +++ b/source4/auth/ntlm/auth_sam.c @@ -36,6 +36,7 @@ #include "lib/messaging/irpc.h" #include "libcli/auth/libcli_auth.h" #include "libds/common/roles.h" +#include "lib/util/tevent_ntstatus.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -733,6 +734,68 @@ static NTSTATUS authsam_check_password_internals(struct auth_method_context *ctx return NT_STATUS_OK; } +struct authsam_check_password_state { + struct auth_user_info_dc *user_info_dc; + bool authoritative; +}; + +static struct tevent_req *authsam_check_password_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct auth_method_context *ctx, + const struct auth_usersupplied_info *user_info) +{ + struct tevent_req *req = NULL; + struct authsam_check_password_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create( + mem_ctx, &state, struct authsam_check_password_state); + if (req == NULL) { + return NULL; + } + /* + * authsam_check_password_internals() sets this to false in + * the rodc case, otherwise it leaves it untouched. Default to + * "we're authoritative". + */ + state->authoritative = true; + + status = authsam_check_password_internals( + ctx, + state, + user_info, + &state->user_info_dc, + &state->authoritative); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS authsam_check_password_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct auth_user_info_dc **interim_info, + bool *authoritative) +{ + struct authsam_check_password_state *state = tevent_req_data( + req, struct authsam_check_password_state); + NTSTATUS status; + + *authoritative = state->authoritative; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + *interim_info = talloc_move(mem_ctx, &state->user_info_dc); + tevent_req_received(req); + return NT_STATUS_OK; +} + static NTSTATUS authsam_ignoredomain_want_check(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx, const struct auth_usersupplied_info *user_info) @@ -888,14 +951,16 @@ static NTSTATUS authsam_get_user_info_dc_principal_wrapper(TALLOC_CTX *mem_ctx, static const struct auth_operations sam_ignoredomain_ops = { .name = "sam_ignoredomain", .want_check = authsam_ignoredomain_want_check, - .check_password = authsam_check_password_internals, + .check_password_send = authsam_check_password_send, + .check_password_recv = authsam_check_password_recv, .get_user_info_dc_principal = authsam_get_user_info_dc_principal_wrapper, }; static const struct auth_operations sam_ops = { .name = "sam", .want_check = authsam_want_check, - .check_password = authsam_check_password_internals, + .check_password_send = authsam_check_password_send, + .check_password_recv = authsam_check_password_recv, .get_user_info_dc_principal = authsam_get_user_info_dc_principal_wrapper, }; -- 2.25.1 From 2f6bd4f8c5c09c8431091ef01f257c59abefa7c4 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Wed, 14 Apr 2021 22:24:44 +0200 Subject: [PATCH 023/224] CVE-2020-25717 auth4: Remove sync check_password from auth_operations Remove complexity in the data structures, and pushes the async-ness one level down. Signed-off-by: Volker Lendecke Reviewed-by: Andrew Bartlett BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 (cherry picked from commit 254af19ba89b4c42e5f45ec731e6577d2fcc6736) --- source4/auth/auth.h | 4 ---- source4/auth/ntlm/auth.c | 44 ++++------------------------------------ 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/source4/auth/auth.h b/source4/auth/auth.h index 51895c9259f3..3f9fb1ae3cbc 100644 --- a/source4/auth/auth.h +++ b/source4/auth/auth.h @@ -61,10 +61,6 @@ struct auth_operations { /* Given the user supplied info, check a password */ - NTSTATUS (*check_password)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx, - const struct auth_usersupplied_info *user_info, - struct auth_user_info_dc **interim_info, - bool *authoritative); struct tevent_req *(*check_password_send)(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct auth_method_context *ctx, diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c index 75cf12c57429..e54eb7719f57 100644 --- a/source4/auth/ntlm/auth.c +++ b/source4/auth/ntlm/auth.c @@ -332,7 +332,6 @@ static void auth_check_password_next(struct tevent_req *req) struct auth_check_password_state *state = tevent_req_data(req, struct auth_check_password_state); struct tevent_req *subreq = NULL; - bool authoritative = true; NTSTATUS status; if (state->method == NULL) { @@ -357,47 +356,12 @@ static void auth_check_password_next(struct tevent_req *req) return; } - if (state->method->ops->check_password_send != NULL) { - subreq = state->method->ops->check_password_send(state, - state->ev, - state->method, - state->user_info); - if (tevent_req_nomem(subreq, req)) { - return; - } - tevent_req_set_callback(subreq, - auth_check_password_done, - req); - return; - } - - if (state->method->ops->check_password == NULL) { - tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); - return; - } - - status = state->method->ops->check_password(state->method, - state, - state->user_info, - &state->user_info_dc, - &authoritative); - if (!authoritative || - NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) { - DEBUG(11,("auth_check_password_send: " - "%s passes to the next method\n", - state->method->ops->name)); - state->method = state->method->next; - auth_check_password_next(req); - return; - } - - /* the backend has handled the request */ - - if (tevent_req_nterror(req, status)) { + subreq = state->method->ops->check_password_send( + state, state->ev, state->method, state->user_info); + if (tevent_req_nomem(subreq, req)) { return; } - - tevent_req_done(req); + tevent_req_set_callback(subreq, auth_check_password_done, req); } static void auth_check_password_done(struct tevent_req *subreq) -- 2.25.1 From e828c7db9dcbdf3098f9ad92961ac2fc6e2e9b73 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 2 Nov 2021 15:39:53 +0100 Subject: [PATCH 024/224] CVE-2020-25719 selftest/knownfail_mit_kdc: Add pointless knownfail to allow a later cherry-pick to apply cleanly BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 --- selftest/knownfail_mit_kdc | 1 + 1 file changed, 1 insertion(+) diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 0f845fb9b1ce..4fc68ffd854c 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -276,6 +276,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_ldap_service_ticket\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_get_ticket_for_host_service_of_machine_account\(ad_dc\) # +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_service_no_auth_data_required\(ad_dc\) # -- 2.25.1 From 78e84e3bbbea1c85a99d531e9c4566ea76584d17 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 21:48:13 +1200 Subject: [PATCH 025/224] CVE-2020-25722 selftest: Move self.assertRaisesLdbError() to samba.tests.TestCase This is easier to reason with regarding which cases should work and which cases should fail, avoiding issues where more success than expected would be OK because a self.fail() was missed in a try: block. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Jeremy Allison (cherry picked from commit 298515cac2f35082483c2b4e4b7dbfe4df1d2e0c) --- python/samba/tests/__init__.py | 25 +++++++++++++++++++ .../dsdb/tests/python/linked_attributes.py | 21 ---------------- source4/dsdb/tests/python/subtree_rename.py | 25 ------------------- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index c75f9c1ebe77..a33fb659283c 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -190,6 +190,31 @@ class TestCase(unittest.TestCase): self.fail(msg) + def assertRaisesLdbError(self, errcode, message, f, *args, **kwargs): + """Assert a function raises a particular LdbError.""" + try: + f(*args, **kwargs) + except ldb.LdbError as e: + (num, msg) = e.args + if num != errcode: + lut = {v: k for k, v in vars(ldb).items() + if k.startswith('ERR_') and isinstance(v, int)} + self.fail("%s, expected " + "LdbError %s, (%d) " + "got %s (%d) " + "%s" % (message, + lut.get(errcode), errcode, + lut.get(num), num, + msg)) + else: + lut = {v: k for k, v in vars(ldb).items() + if k.startswith('ERR_') and isinstance(v, int)} + self.fail("%s, expected " + "LdbError %s, (%d) " + "but we got success" % (message, + lut.get(errcode), + errcode)) + class LdbTestCase(TestCase): """Trivial test case for running tests against a LDB.""" diff --git a/source4/dsdb/tests/python/linked_attributes.py b/source4/dsdb/tests/python/linked_attributes.py index 533fa9437365..5c6bb7ebac81 100644 --- a/source4/dsdb/tests/python/linked_attributes.py +++ b/source4/dsdb/tests/python/linked_attributes.py @@ -166,27 +166,6 @@ class LATests(samba.tests.TestCase): attrs=['objectGUID']) return str(misc.GUID(res[0]['objectGUID'][0])) - def assertRaisesLdbError(self, errcode, msg, f, *args, **kwargs): - """Assert a function raises a particular LdbError.""" - try: - f(*args, **kwargs) - except ldb.LdbError as e: - (num, msg) = e.args - if num != errcode: - lut = {v: k for k, v in vars(ldb).items() - if k.startswith('ERR_') and isinstance(v, int)} - self.fail("%s, expected " - "LdbError %s, (%d) " - "got %s (%d)" % (msg, - lut.get(errcode), errcode, - lut.get(num), num)) - else: - lut = {v: k for k, v in vars(ldb).items() - if k.startswith('ERR_') and isinstance(v, int)} - self.fail("%s, expected " - "LdbError %s, (%d) " - "but we got success" % (msg, lut.get(errcode), errcode)) - def _test_la_backlinks(self, reveal=False): tag = 'backlinks' kwargs = {} diff --git a/source4/dsdb/tests/python/subtree_rename.py b/source4/dsdb/tests/python/subtree_rename.py index c4f6bc7a160b..e422c2e2b010 100644 --- a/source4/dsdb/tests/python/subtree_rename.py +++ b/source4/dsdb/tests/python/subtree_rename.py @@ -201,31 +201,6 @@ class SubtreeRenameTests(samba.tests.TestCase): attrs=['objectGUID']) return str(misc.GUID(res[0]['objectGUID'][0])) - def assertRaisesLdbError(self, errcode, message, f, *args, **kwargs): - """Assert a function raises a particular LdbError.""" - try: - f(*args, **kwargs) - except ldb.LdbError as e: - (num, msg) = e.args - if num != errcode: - lut = {v: k for k, v in vars(ldb).items() - if k.startswith('ERR_') and isinstance(v, int)} - self.fail("%s, expected " - "LdbError %s, (%d) " - "got %s (%d) " - "%s" % (message, - lut.get(errcode), errcode, - lut.get(num), num, - msg)) - else: - lut = {v: k for k, v in vars(ldb).items() - if k.startswith('ERR_') and isinstance(v, int)} - self.fail("%s, expected " - "LdbError %s, (%d) " - "but we got success" % (message, - lut.get(errcode), - errcode)) - def test_la_move_ou_tree(self): tag = 'move_tree' -- 2.25.1 From 10f6bf10ee7f54db32c8e7b6cdb08b865ca6c055 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 10:07:31 +1200 Subject: [PATCH 026/224] CVE-2020-25722 selftest: Modernise user_account_control.py tests use a common self.OU We set and use a single self.OU to ensure consistancy and reduce string duplication. Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit 8b078bbf8717b9407cdbc1588dd065164ab78e1b) --- .../dsdb/tests/python/user_account_control.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 2d62d4c32b19..cb614b165e59 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -90,7 +90,7 @@ class UserAccountControlTests(samba.tests.TestCase): def add_computer_ldap(self, computername, others=None, samdb=None): if samdb is None: samdb = self.samdb - dn = "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn) + dn = "CN=%s,%s" % (computername, self.OU) domainname = ldb.Dn(self.samdb, self.samdb.domain_dn()).canonical_str().replace("/", "") samaccountname = "%s$" % computername dnshostname = "%s.%s" % (computername, domainname) @@ -131,8 +131,9 @@ class UserAccountControlTests(samba.tests.TestCase): self.unpriv_user_pw = "samba123@" self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw) - delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) - delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn)) + self.OU = "OU=test_computer_ou1,%s" % (self.base_dn) + + delete_force(self.admin_samdb, self.OU, controls=["tree_delete:0"]) delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn)) self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw) @@ -151,27 +152,27 @@ class UserAccountControlTests(samba.tests.TestCase): self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) self.sd_utils = sd_utils.SDUtils(self.admin_samdb) + self.admin_samdb.create_ou(self.OU) - self.admin_samdb.create_ou("OU=test_computer_ou1," + self.base_dn) self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) self.add_computer_ldap("testcomputer-t") - self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd) + self.sd_utils.modify_sd_on_dn(self.OU, old_sd) self.computernames = ["testcomputer-0"] # Get the SD of the template account, then force it to match # what we expect for SeMachineAccountPrivilege accounts, so we # can confirm we created the accounts correctly - self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) + self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,%s" % (self.OU)) - self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) + self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,%s" % (self.OU)) for ace in self.sd_reference_modify.dacl.aces: if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED and ace.trustee == self.unpriv_user_sid: ace.access_mask = ace.access_mask | security.SEC_ADS_SELF_WRITE | security.SEC_ADS_WRITE_PROP @@ -191,9 +192,8 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) - - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) + self.sd_utils.dacl_add_ace(self.OU, mod) computername = self.computernames[0] sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)), @@ -276,9 +276,9 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) computername = self.computernames[0] self.add_computer_ldap(computername) @@ -392,9 +392,9 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) computername = self.computernames[0] self.add_computer_ldap(computername) @@ -446,9 +446,9 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) computername = self.computernames[0] self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]}) @@ -621,9 +621,9 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test @@ -637,7 +637,7 @@ class UserAccountControlTests(samba.tests.TestCase): for bit in bits: try: self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]}) - delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)) + delete_force(self.admin_samdb, "CN=%s,%s" % (computername, self.OU)) if bit in priv_bits: self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername)) @@ -659,9 +659,9 @@ class UserAccountControlTests(samba.tests.TestCase): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) - old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) + old_sd = self.sd_utils.read_sd_on_dn(self.OU) - self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) + self.sd_utils.dacl_add_ace(self.OU, mod) try: # When creating a new object, you can not ever set the primaryGroupID self.add_computer_ldap(computername, others={"primaryGroupID": [str(security.DOMAIN_RID_ADMINS)]}) -- 2.25.1 From 2cd6d51ac842d78ffdb7c64b2272dba7c3657a2c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 10:10:56 +1200 Subject: [PATCH 027/224] CVE-2020-25722 selftest: Use addCleanup rather than tearDown in user_account_control.py self.addCleanup() is called regardless of the test failure or error status and so is more reliable, particularly during development. Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit 8c455268165f0bbfce17407df2c1746a0e03f828) --- source4/dsdb/tests/python/user_account_control.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index cb614b165e59..81664079adfe 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -144,6 +144,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]) self.unpriv_user_dn = res[0].dn + self.addCleanup(self.admin_samdb.delete, self.unpriv_user_dn) self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp) @@ -153,6 +154,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.sd_utils = sd_utils.SDUtils(self.admin_samdb) self.admin_samdb.create_ou(self.OU) + self.addCleanup(self.admin_samdb.delete, self.OU, ["tree_delete:1"]) self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid) @@ -180,14 +182,6 @@ class UserAccountControlTests(samba.tests.TestCase): # Now reconnect without domain admin rights self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp) - def tearDown(self): - super(UserAccountControlTests, self).tearDown() - for computername in self.computernames: - delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)) - delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) - delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn)) - delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn)) - def test_add_computer_sd_cc(self): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) -- 2.25.1 From 7a6a8247efc1b252e4e7343b5d652796a61a0db4 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 13:03:15 +1200 Subject: [PATCH 028/224] CVE-2020-25722 pydsdb: Add API to return strings of known UF_ flags Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit fb6c0b9e2a10c9559d3e056bb020bd2c990da998) --- libds/common/flag_mapping.c | 50 +++++++++++++++++++++++++++++ libds/common/flag_mapping.h | 1 + libds/common/flags.h | 5 +++ python/samba/tests/dsdb_api.py | 57 ++++++++++++++++++++++++++++++++++ selftest/tests.py | 1 + source4/dsdb/pydsdb.c | 30 ++++++++++++++++++ 6 files changed, 144 insertions(+) create mode 100644 python/samba/tests/dsdb_api.py diff --git a/libds/common/flag_mapping.c b/libds/common/flag_mapping.c index ddc8ec5c1982..020922db6599 100644 --- a/libds/common/flag_mapping.c +++ b/libds/common/flag_mapping.c @@ -164,3 +164,53 @@ uint32_t ds_uf2prim_group_rid(uint32_t uf) return prim_group_rid; } + +#define FLAG(x) { .name = #x, .uf = x } +struct { + const char *name; + uint32_t uf; +} user_account_control_name_map[] = { + FLAG(UF_SCRIPT), + FLAG(UF_ACCOUNTDISABLE), + FLAG(UF_00000004), + FLAG(UF_HOMEDIR_REQUIRED), + FLAG(UF_LOCKOUT), + FLAG(UF_PASSWD_NOTREQD), + FLAG(UF_PASSWD_CANT_CHANGE), + FLAG(UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED), + + FLAG(UF_TEMP_DUPLICATE_ACCOUNT), + FLAG(UF_NORMAL_ACCOUNT), + FLAG(UF_00000400), + FLAG(UF_INTERDOMAIN_TRUST_ACCOUNT), + + FLAG(UF_WORKSTATION_TRUST_ACCOUNT), + FLAG(UF_SERVER_TRUST_ACCOUNT), + FLAG(UF_00004000), + FLAG(UF_00008000), + + FLAG(UF_DONT_EXPIRE_PASSWD), + FLAG(UF_MNS_LOGON_ACCOUNT), + FLAG(UF_SMARTCARD_REQUIRED), + FLAG(UF_TRUSTED_FOR_DELEGATION), + + FLAG(UF_NOT_DELEGATED), + FLAG(UF_USE_DES_KEY_ONLY), + FLAG(UF_DONT_REQUIRE_PREAUTH), + FLAG(UF_PASSWORD_EXPIRED), + FLAG(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION), + FLAG(UF_NO_AUTH_DATA_REQUIRED), + FLAG(UF_PARTIAL_SECRETS_ACCOUNT), + FLAG(UF_USE_AES_KEYS) +}; + +const char *dsdb_user_account_control_flag_bit_to_string(uint32_t uf) +{ + int i; + for (i=0; i < ARRAY_SIZE(user_account_control_name_map); i++) { + if (uf == user_account_control_name_map[i].uf) { + return user_account_control_name_map[i].name; + } + } + return NULL; +} diff --git a/libds/common/flag_mapping.h b/libds/common/flag_mapping.h index ae721da894a0..f08d5593af6d 100644 --- a/libds/common/flag_mapping.h +++ b/libds/common/flag_mapping.h @@ -31,5 +31,6 @@ uint32_t ds_uf2atype(uint32_t uf); uint32_t ds_gtype2atype(uint32_t gtype); enum lsa_SidType ds_atype_map(uint32_t atype); uint32_t ds_uf2prim_group_rid(uint32_t uf); +const char *dsdb_user_account_control_flag_bit_to_string(uint32_t uf); #endif /* __LIBDS_COMMON_FLAG_MAPPING_H__ */ diff --git a/libds/common/flags.h b/libds/common/flags.h index d436f2bafd81..75e04b0c488b 100644 --- a/libds/common/flags.h +++ b/libds/common/flags.h @@ -18,6 +18,8 @@ along with this program. If not, see . */ +/* Please keep this list in sync with the flag_mapping.c and pydsdb.c */ + /* User flags for "userAccountControl" */ #define UF_SCRIPT 0x00000001 /* NT or Lan Manager Login script must be executed */ #define UF_ACCOUNTDISABLE 0x00000002 @@ -53,6 +55,9 @@ #define UF_PARTIAL_SECRETS_ACCOUNT 0x04000000 #define UF_USE_AES_KEYS 0x08000000 +/* Please keep this list in sync with the flag_mapping.c and pydsdb.c */ + + #define UF_TRUST_ACCOUNT_MASK (\ UF_INTERDOMAIN_TRUST_ACCOUNT |\ UF_WORKSTATION_TRUST_ACCOUNT |\ diff --git a/python/samba/tests/dsdb_api.py b/python/samba/tests/dsdb_api.py new file mode 100644 index 000000000000..997407917af4 --- /dev/null +++ b/python/samba/tests/dsdb_api.py @@ -0,0 +1,57 @@ +# Unix SMB/CIFS implementation. Tests for dsdb +# Copyright (C) Andrew Bartlett 2021 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +"""Tests for samba.dsdb.""" + +from samba.tests import TestCase, DynamicTestCase +from samba.dsdb import user_account_control_flag_bit_to_string +import samba + + +@DynamicTestCase +class DsdbFlagTests(TestCase): + + @classmethod + def setUpDynamicTestCases(cls): + + for x in dir(samba.dsdb): + if x.startswith("UF_"): + cls.generate_dynamic_test("test", + x, + x, + getattr(samba.dsdb, x)) + + + def _test_with_args(self, uf_string, uf_bit): + self.assertEqual(user_account_control_flag_bit_to_string(uf_bit), + uf_string) + + + def test_not_a_flag(self): + self.assertRaises(KeyError, + user_account_control_flag_bit_to_string, + 0xabcdef) + + def test_too_long(self): + self.assertRaises(OverflowError, + user_account_control_flag_bit_to_string, + 0xabcdefffff) + + def test_way_too_long(self): + self.assertRaises(OverflowError, + user_account_control_flag_bit_to_string, + 0xabcdeffffffffffff) diff --git a/selftest/tests.py b/selftest/tests.py index 3645904801ac..af1e46061852 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -88,6 +88,7 @@ planpythontestsuite("none", "samba.tests.s3registry") planpythontestsuite("none", "samba.tests.s3windb") planpythontestsuite("none", "samba.tests.s3idmapdb") planpythontestsuite("none", "samba.tests.samba3sam") +planpythontestsuite("none", "samba.tests.dsdb_api") planpythontestsuite( "none", "wafsamba.tests.test_suite", extra_path=[os.path.join(samba4srcdir, "..", "buildtools"), diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index b68d76ada495..202767475046 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -33,6 +33,7 @@ #include "lib/util/dlinklist.h" #include "dsdb/kcc/garbage_collect_tombstones.h" #include "dsdb/kcc/scavenge_dns_records.h" +#include "libds/common/flag_mapping.h" #undef strcasecmp @@ -1401,6 +1402,30 @@ static PyObject *py_dsdb_load_udv_v2(PyObject *self, PyObject *args) return pylist; } +static PyObject *py_dsdb_user_account_control_flag_bit_to_string(PyObject *self, PyObject *args) +{ + const char *str; + long long uf; + if (!PyArg_ParseTuple(args, "L", &uf)) { + return NULL; + } + + if (uf > UINT32_MAX) { + return PyErr_Format(PyExc_OverflowError, "No UF_ flags are over UINT32_MAX"); + } + if (uf < 0) { + return PyErr_Format(PyExc_KeyError, "No UF_ flags are less then zero"); + } + + str = dsdb_user_account_control_flag_bit_to_string(uf); + if (str == NULL) { + return PyErr_Format(PyExc_KeyError, + "No such UF_ flag 0x%08x", + (unsigned int)uf); + } + return PyUnicode_FromString(str); +} + static PyMethodDef py_dsdb_methods[] = { { "_samdb_server_site_name", (PyCFunction)py_samdb_server_site_name, METH_VARARGS, "Get the server site name as a string"}, @@ -1480,6 +1505,11 @@ static PyMethodDef py_dsdb_methods[] = { "_dsdb_allocate_rid(samdb)" " -> RID" }, { "_dsdb_load_udv_v2", (PyCFunction)py_dsdb_load_udv_v2, METH_VARARGS, NULL }, + { "user_account_control_flag_bit_to_string", + (PyCFunction)py_dsdb_user_account_control_flag_bit_to_string, + METH_VARARGS, + "user_account_control_flag_bit_to_string(bit)" + " -> string name" }, {0} }; -- 2.25.1 From 230873599abd69bd2852357b8edeccc6efce787e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 14:37:06 +1200 Subject: [PATCH 029/224] CVE-2020-25722 selftest: Use @DynamicTestCase in user_account_control test_uac_bits_unrelated_modify() This is a nice easy example of how the test generation code works, and it combined nicely with the earlier patch to return string names from the UF_ constants. Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit 8701ce492fc3a209035b152961d8c17e801b082a) --- .../dsdb/tests/python/user_account_control.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 81664079adfe..4ef43502c8cf 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -27,7 +27,7 @@ from samba.samdb import SamDB from samba.dcerpc import samr, security, lsa from samba.credentials import Credentials from samba.ndr import ndr_unpack, ndr_pack -from samba.tests import delete_force +from samba.tests import delete_force, DynamicTestCase from samba import gensec, sd_utils from samba.credentials import DONT_USE_KERBEROS from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError @@ -41,6 +41,7 @@ from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQ UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \ UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \ UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS +from samba import dsdb parser = optparse.OptionParser("user_account_control.py [options] ") @@ -86,7 +87,15 @@ bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT]) +@DynamicTestCase class UserAccountControlTests(samba.tests.TestCase): + @classmethod + def setUpDynamicTestCases(cls): + for account_type in [UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT]: + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + cls.generate_dynamic_test("test_uac_bits_unrelated_modify", + account_type_str, account_type) + def add_computer_ldap(self, computername, others=None, samdb=None): if samdb is None: samdb = self.samdb @@ -436,7 +445,7 @@ class UserAccountControlTests(samba.tests.TestCase): else: self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) - def uac_bits_unrelated_modify_helper(self, account_type): + def _test_uac_bits_unrelated_modify_with_args(self, account_type): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) @@ -603,12 +612,6 @@ class UserAccountControlTests(samba.tests.TestCase): UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, "bit 0X%08x should have been removed" % bit) - def test_uac_bits_unrelated_modify_normal(self): - self.uac_bits_unrelated_modify_helper(UF_NORMAL_ACCOUNT) - - def test_uac_bits_unrelated_modify_workstation(self): - self.uac_bits_unrelated_modify_helper(UF_WORKSTATION_TRUST_ACCOUNT) - def test_uac_bits_add(self): computername = self.computernames[0] -- 2.25.1 From 59d4b3caece7602c01836f852a976c01eaece582 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 14:51:27 +1200 Subject: [PATCH 030/224] CVE-2020-25722 selftest: Replace internal loop in test_uac_bits_add() using @DynamicTestClass This generates a single test per bit which is easier to debug. Elsewhere we use this pattern where we want to be able to put some cases in a knownfail, which is otherwise not possible. Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit 60f1b6cf0ef0bf6736d8db9c53fa48fe9f3d8e75) --- .../dsdb/tests/python/user_account_control.py | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 4ef43502c8cf..1a396740df0d 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -96,6 +96,16 @@ class UserAccountControlTests(samba.tests.TestCase): cls.generate_dynamic_test("test_uac_bits_unrelated_modify", account_type_str, account_type) + for bit in bits: + try: + bit_str = dsdb.user_account_control_flag_bit_to_string(bit) + except KeyError: + bit_str = hex(bit) + + cls.generate_dynamic_test("test_uac_bits_add", + bit_str, bit, bit_str) + + def add_computer_ldap(self, computername, others=None, samdb=None): if samdb is None: samdb = self.samdb @@ -612,7 +622,7 @@ class UserAccountControlTests(samba.tests.TestCase): UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, "bit 0X%08x should have been removed" % bit) - def test_uac_bits_add(self): + def _test_uac_bits_add_with_args(self, bit, bit_str): computername = self.computernames[0] user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) @@ -631,24 +641,30 @@ class UserAccountControlTests(samba.tests.TestCase): priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION]) - for bit in bits: - try: - self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]}) - delete_force(self.admin_samdb, "CN=%s,%s" % (computername, self.OU)) - if bit in priv_bits: - self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername)) - - except LdbError as e4: - (enum, estr) = e4.args - if bit in invalid_bits: - self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername)) - # No point going on, try the next bit - continue - elif bit in priv_bits: - self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) - continue - else: - self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr)) + try: + self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]}) + delete_force(self.admin_samdb, "CN=%s,%s" % (computername, self.OU)) + if bit in priv_bits: + self.fail("Unexpectdly able to set userAccountControl bit 0x%08X (%s) on %s" + % (bit, bit_str, computername)) + + except LdbError as e4: + (enum, estr) = e4.args + if bit in invalid_bits: + self.assertEqual(enum, + ldb.ERR_OTHER, + "Invalid bit 0x%08X (%s) was able to be set on %s" + % (bit, + bit_str, + computername)) + elif bit in priv_bits: + self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) + else: + self.fail("Unable to set userAccountControl bit 0x%08X (%s) on %s: %s" + % (bit, + bit_str, + computername, + estr)) def test_primarygroupID_cc_add(self): computername = self.computernames[0] -- 2.25.1 From 7dfe2da951abf3d21022e06b3725cedb632095ff Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 14:54:39 +1200 Subject: [PATCH 031/224] CVE-2020-25722 selftest: Replace internal loop in test_uac_bits_set() using @DynamicTestClass This generates a single test per bit which is easier to debug. Elsewhere we use this pattern where we want to be able to put some cases in a knownfail, which is otherwise not possible. Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 (cherry picked from commit 17ae0319db53a7b88e7fb44a9e2fd4bf1d1daa0e) --- .../dsdb/tests/python/user_account_control.py | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 1a396740df0d..fd0ae38a3f99 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -105,6 +105,9 @@ class UserAccountControlTests(samba.tests.TestCase): cls.generate_dynamic_test("test_uac_bits_add", bit_str, bit, bit_str) + cls.generate_dynamic_test("test_uac_bits_set", + bit_str, bit, bit_str) + def add_computer_ldap(self, computername, others=None, samdb=None): if samdb is None: @@ -401,7 +404,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE) - def test_uac_bits_set(self): + def _test_uac_bits_set_with_args(self, bit, bit_str): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) @@ -435,25 +438,27 @@ class UserAccountControlTests(samba.tests.TestCase): invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) - for bit in bits: - m = ldb.Message() - m.dn = res[0].dn - m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD), - ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - if (bit in priv_bits): - self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn)) - except LdbError as e: - (enum, estr) = e.args - if bit in invalid_bits: - self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn)) - # No point going on, try the next bit - continue - elif (bit in priv_bits): - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) - else: - self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) + m = ldb.Message() + m.dn = res[0].dn + m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD), + ldb.FLAG_MOD_REPLACE, "userAccountControl") + try: + self.samdb.modify(m) + if (bit in priv_bits): + self.fail("Unexpectedly able to set userAccountControl bit 0x%08X (%s), on %s" + % (bit, bit_str, m.dn)) + except LdbError as e: + (enum, estr) = e.args + if bit in invalid_bits: + self.assertEqual(enum, + ldb.ERR_OTHER, + "was not able to set 0x%08X (%s) on %s" + % (bit, bit_str, m.dn)) + elif (bit in priv_bits): + self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + else: + self.fail("Unable to set userAccountControl bit 0x%08X (%s) on %s: %s" + % (bit, bit_str, m.dn, estr)) def _test_uac_bits_unrelated_modify_with_args(self, account_type): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) -- 2.25.1 From a5a4f5f1c6764f91b13f7930e816bd70f8fd0346 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 30 Aug 2021 18:17:47 +1200 Subject: [PATCH 032/224] CVE-2020-25722 selftest: Update user_account_control tests to pass against Windows 2019 This gets us closer to passing against Windows 2019, without making major changes to what was tested. More tests are needed, but it is important to get what was being tested tested again. Account types (eg UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT) are now required on all objects, this can't be omitted any more. Also for UF_NORMAL_ACCOUNT for these accounts without a password set |UF_PASSWD_NOTREQD must be included. Signed-off-by: Andrew Bartlett Reviewed-by: Alexander Bokovoy BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Autobuild-User(master): Andrew Bartlett Autobuild-Date(master): Wed Sep 15 08:49:11 UTC 2021 on sn-devel-184 (cherry picked from commit d12cb47724c2e8d19a28286d4c3ef72271a002fd) --- selftest/knownfail.d/user_account_control | 1 + .../dsdb/tests/python/user_account_control.py | 114 ++++++++++++++++-- 2 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 selftest/knownfail.d/user_account_control diff --git a/selftest/knownfail.d/user_account_control b/selftest/knownfail.d/user_account_control new file mode 100644 index 000000000000..ad3af6787080 --- /dev/null +++ b/selftest/knownfail.d/user_account_control @@ -0,0 +1 @@ +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_cc_normal_bare.ad_dc_default diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index fd0ae38a3f99..efb83b2dcfff 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -84,7 +84,7 @@ bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS, int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)] -account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT]) +account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_INTERDOMAIN_TRUST_ACCOUNT]) @DynamicTestCase @@ -108,6 +108,11 @@ class UserAccountControlTests(samba.tests.TestCase): cls.generate_dynamic_test("test_uac_bits_set", bit_str, bit, bit_str) + cls.generate_dynamic_test("test_uac_bits_add", + "UF_NORMAL_ACCOUNT_UF_PASSWD_NOTREQD", + UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD, + "UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD") + def add_computer_ldap(self, computername, others=None, samdb=None): if samdb is None: @@ -272,7 +277,7 @@ class UserAccountControlTests(samba.tests.TestCase): m = ldb.Message() m.dn = res[0].dn - m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), + m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") self.samdb.modify(m) @@ -334,9 +339,10 @@ class UserAccountControlTests(samba.tests.TestCase): (enum, estr) = e10.args self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + m = ldb.Message() m.dn = res[0].dn - m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), + m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") self.samdb.modify(m) @@ -351,6 +357,50 @@ class UserAccountControlTests(samba.tests.TestCase): (enum, estr) = e11.args self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + def test_add_computer_cc_normal_bare(self): + user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) + mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) + + old_sd = self.sd_utils.read_sd_on_dn(self.OU) + self.sd_utils.dacl_add_ace(self.OU, mod) + + computername = self.computernames[0] + sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)), + ldb.FLAG_MOD_ADD, + "nTSecurityDescriptor") + self.add_computer_ldap(computername, + others={"nTSecurityDescriptor": sd}) + + res = self.admin_samdb.search("%s" % self.base_dn, + expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + scope=SCOPE_SUBTREE, + attrs=["ntSecurityDescriptor"]) + + desc = res[0]["nTSecurityDescriptor"][0] + desc = ndr_unpack(security.descriptor, desc, allow_remaining=True) + + sddl = desc.as_sddl(self.domain_sid) + self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl) + + m = ldb.Message() + m.dn = res[0].dn + m["description"] = ldb.MessageElement( + ("A description"), ldb.FLAG_MOD_REPLACE, + "description") + self.samdb.modify(m) + + m = ldb.Message() + m.dn = res[0].dn + m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), + ldb.FLAG_MOD_REPLACE, "userAccountControl") + try: + self.samdb.modify(m) + self.fail("Unexpectedly able to set userAccountControl to be an Normal account without |UF_PASSWD_NOTREQD on %s" % m.dn) + except LdbError as e7: + (enum, estr) = e7.args + self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum) + + def test_admin_mod_uac(self): computername = self.computernames[0] self.add_computer_ldap(computername, samdb=self.admin_samdb) @@ -469,13 +519,23 @@ class UserAccountControlTests(samba.tests.TestCase): self.sd_utils.dacl_add_ace(self.OU, mod) computername = self.computernames[0] - self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]}) + if account_type == UF_WORKSTATION_TRUST_ACCOUNT: + self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]}) + else: + self.add_computer_ldap(computername) - res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + res = self.admin_samdb.search(self.OU, + expression=f"(cn={computername})", scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) - self.assertEqual(int(res[0]["userAccountControl"][0]), account_type) + self.assertEqual(len(res), 1) + + orig_uac = int(res[0]["userAccountControl"][0]) + if account_type == UF_WORKSTATION_TRUST_ACCOUNT: + self.assertEqual(orig_uac, account_type) + else: + self.assertEqual(orig_uac & UF_NORMAL_ACCOUNT, + account_type) m = ldb.Message() m.dn = res[0].dn @@ -504,7 +564,7 @@ class UserAccountControlTests(samba.tests.TestCase): # Reset this to the initial position, just to be sure m = ldb.Message() m.dn = res[0].dn - m["userAccountControl"] = ldb.MessageElement(str(account_type), + m["userAccountControl"] = ldb.MessageElement(str(orig_uac), ldb.FLAG_MOD_REPLACE, "userAccountControl") self.admin_samdb.modify(m) @@ -513,7 +573,11 @@ class UserAccountControlTests(samba.tests.TestCase): scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) - self.assertEqual(int(res[0]["userAccountControl"][0]), account_type) + if account_type == UF_WORKSTATION_TRUST_ACCOUNT: + self.assertEqual(orig_uac, account_type) + else: + self.assertEqual(orig_uac & UF_NORMAL_ACCOUNT, + account_type) m = ldb.Message() m.dn = res[0].dn @@ -521,6 +585,7 @@ class UserAccountControlTests(samba.tests.TestCase): ldb.FLAG_MOD_REPLACE, "userAccountControl") try: self.admin_samdb.modify(m) + if bit in invalid_bits: self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn)) @@ -534,6 +599,19 @@ class UserAccountControlTests(samba.tests.TestCase): self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) # No point going on, try the next bit continue + + elif (account_type == UF_NORMAL_ACCOUNT) \ + and (bit in account_types) \ + and (bit != account_type): + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + continue + + elif (account_type == UF_WORKSTATION_TRUST_ACCOUNT) \ + and (bit != UF_NORMAL_ACCOUNT) \ + and (bit != account_type): + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + continue + else: self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) @@ -637,17 +715,27 @@ class UserAccountControlTests(samba.tests.TestCase): self.sd_utils.dacl_add_ace(self.OU, mod) - invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) + invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT]) + # UF_NORMAL_ACCOUNT is invalid alone, needs UF_PASSWD_NOTREQD + unwilling_bits = set([UF_NORMAL_ACCOUNT]) + # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, UF_DONT_EXPIRE_PASSWD]) # These bits really are privileged priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, - UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION]) + UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, + UF_PARTIAL_SECRETS_ACCOUNT]) + + if bit not in account_types and ((bit & UF_NORMAL_ACCOUNT) == 0): + bit_add = bit|UF_WORKSTATION_TRUST_ACCOUNT + else: + bit_add = bit try: - self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]}) + + self.add_computer_ldap(computername, others={"userAccountControl": [str(bit_add)]}) delete_force(self.admin_samdb, "CN=%s,%s" % (computername, self.OU)) if bit in priv_bits: self.fail("Unexpectdly able to set userAccountControl bit 0x%08X (%s) on %s" @@ -664,6 +752,8 @@ class UserAccountControlTests(samba.tests.TestCase): computername)) elif bit in priv_bits: self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) + elif bit in unwilling_bits: + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) else: self.fail("Unable to set userAccountControl bit 0x%08X (%s) on %s: %s" % (bit, -- 2.25.1 From 0256910bd7d6c1430057130c9c42ca9413b2b470 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 16 Sep 2021 16:09:24 +1200 Subject: [PATCH 033/224] CVE-2020-25722 selftest: Use self.assertRaisesLdbError() in user_account_control.py test This changes most of the simple pattern with self.samdb.modify() to use the wrapper. Some other calls still need to be converted, while the complex decision tree tests should remain as-is for now. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Jeremy Allison Autobuild-User(master): Jeremy Allison Autobuild-Date(master): Mon Oct 4 21:55:43 UTC 2021 on sn-devel-184 (cherry picked from commit b45190bdac7bd9dcefd5ed88be4bd9a97a712664) --- .../dsdb/tests/python/user_account_control.py | 100 +++++++----------- 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index efb83b2dcfff..c9b50b83e9da 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -245,35 +245,27 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn) - except LdbError as e5: - (enum, estr) = e5.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl to be a DC on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT | samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to be an RODC on %s" % m.dn) - except LdbError as e6: - (enum, estr) = e6.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl to be a RODC on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn) - except LdbError as e7: - (enum, estr) = e7.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl to be a Workstation on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn @@ -285,13 +277,10 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["primaryGroupID"] = ldb.MessageElement(str(security.DOMAIN_RID_ADMINS), ldb.FLAG_MOD_REPLACE, "primaryGroupID") - try: - self.samdb.modify(m) - except LdbError as e8: - (enum, estr) = e8.args - self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum) - return - self.fail() + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + f"Unexpectedly able to set primaryGroupID on {m.dn}", + self.samdb.modify, m) + def test_mod_computer_cc(self): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) @@ -321,24 +310,17 @@ class UserAccountControlTests(samba.tests.TestCase): m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT | samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn) - except LdbError as e9: - (enum, estr) = e9.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl as RODC on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail() - except LdbError as e10: - (enum, estr) = e10.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) - + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl as DC on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn @@ -350,12 +332,10 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn) - except LdbError as e11: - (enum, estr) = e11.args - self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Unexpectedly able to set userAccountControl to be a workstation on {m.dn}", + self.samdb.modify, m) + def test_add_computer_cc_normal_bare(self): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) @@ -393,12 +373,11 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to be an Normal account without |UF_PASSWD_NOTREQD on %s" % m.dn) - except LdbError as e7: - (enum, estr) = e7.args - self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum) + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + f"Unexpectedly able to set userAccountControl to be an Normal " + "account without |UF_PASSWD_NOTREQD Unexpectedly able to " + "set userAccountControl to be a workstation on {m.dn}", + self.samdb.modify, m) def test_admin_mod_uac(self): @@ -420,12 +399,11 @@ class UserAccountControlTests(samba.tests.TestCase): UF_PARTIAL_SECRETS_ACCOUNT | UF_TRUSTED_FOR_DELEGATION), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.admin_samdb.modify(m) - self.fail("Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s" % m.dn) - except LdbError as e12: - (enum, estr) = e12.args - self.assertEqual(ldb.ERR_OTHER, enum) + self.assertRaisesLdbError(ldb.ERR_OTHER, + f"Unexpectedly able to set userAccountControl to " + "UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|" + "UF_TRUSTED_FOR_DELEGATION on {m.dn}", + self.admin_samdb.modify, m) m = ldb.Message() m.dn = res[0].dn @@ -835,14 +813,10 @@ class UserAccountControlTests(samba.tests.TestCase): m["primaryGroupID"] = ldb.MessageElement( [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE, "primaryGroupID") - try: - self.admin_samdb.modify(m) - # When creating a new object, you can not ever set the primaryGroupID - self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername) - except LdbError as e15: - (enum, estr) = e15.args - self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + f"Unexpectedly able to set primaryGroupID to be other than DCS on {m.dn}", + self.admin_samdb.modify, m) def test_primarygroupID_priv_user_modify(self): computername = self.computernames[0] -- 2.25.1 From 3e41a91105787f44310bc8f167e97ae21cb9a187 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 16:46:56 +1300 Subject: [PATCH 034/224] CVE-2020-17049 tests/krb5: Check account name and SID in PAC for S4U tests BUG: https://bugzilla.samba.org/show_bug.cgi?id=14642 Signed-off-by: Joseph Sutton Reviewed-by: Andreas Schneider Autobuild-User(master): Andreas Schneider Autobuild-Date(master): Mon Oct 25 09:23:35 UTC 2021 on sn-devel-184 (cherry picked from commit c174e9ebe715aad6910d53c1f427a0512c09d651) --- python/samba/tests/krb5/kdc_base_test.py | 4 ++++ python/samba/tests/krb5/raw_testcase.py | 26 ++++++++++++++++++++++++ python/samba/tests/krb5/s4u_tests.py | 12 +++++++++++ 3 files changed, 42 insertions(+) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index b24c6376ab0c..8ae9c24b0fc3 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1337,6 +1337,8 @@ class KDCBaseTest(RawKerberosTest): def get_tgt(self, creds, to_rodc=False, kdc_options=None, expected_flags=None, unexpected_flags=None, + expected_account_name=None, + expected_sid=None, pac_request=True, expect_pac=True, fresh=False): user_name = creds.get_username() cache_key = (user_name, to_rodc, kdc_options, pac_request) @@ -1386,6 +1388,8 @@ class KDCBaseTest(RawKerberosTest): expected_cname=cname, expected_srealm=realm, expected_sname=sname, + expected_account_name=expected_account_name, + expected_sid=expected_sid, expected_salt=salt, expected_flags=expected_flags, unexpected_flags=unexpected_flags, diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index f352615db1fd..fdf078ea788e 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -1984,6 +1984,8 @@ class RawKerberosTest(TestCaseInTempDir): expected_anon=False, expected_srealm=None, expected_sname=None, + expected_account_name=None, + expected_sid=None, expected_supported_etypes=None, expected_flags=None, unexpected_flags=None, @@ -2033,6 +2035,8 @@ class RawKerberosTest(TestCaseInTempDir): 'expected_anon': expected_anon, 'expected_srealm': expected_srealm, 'expected_sname': expected_sname, + 'expected_account_name': expected_account_name, + 'expected_sid': expected_sid, 'expected_supported_etypes': expected_supported_etypes, 'expected_flags': expected_flags, 'unexpected_flags': unexpected_flags, @@ -2078,6 +2082,8 @@ class RawKerberosTest(TestCaseInTempDir): expected_anon=False, expected_srealm=None, expected_sname=None, + expected_account_name=None, + expected_sid=None, expected_supported_etypes=None, expected_flags=None, unexpected_flags=None, @@ -2128,6 +2134,8 @@ class RawKerberosTest(TestCaseInTempDir): 'expected_anon': expected_anon, 'expected_srealm': expected_srealm, 'expected_sname': expected_sname, + 'expected_account_name': expected_account_name, + 'expected_sid': expected_sid, 'expected_supported_etypes': expected_supported_etypes, 'expected_flags': expected_flags, 'unexpected_flags': unexpected_flags, @@ -2561,6 +2569,9 @@ class RawKerberosTest(TestCaseInTempDir): f'expected: {expected_types} ' f'got: {buffer_types}') + expected_account_name = kdc_exchange_dict['expected_account_name'] + expected_sid = kdc_exchange_dict['expected_sid'] + for pac_buffer in pac.buffers: if pac_buffer.type == krb5pac.PAC_TYPE_CONSTRAINED_DELEGATION: expected_proxy_target = kdc_exchange_dict[ @@ -2584,6 +2595,17 @@ class RawKerberosTest(TestCaseInTempDir): self.assertEqual(account_name, pac_buffer.info.account_name) + elif pac_buffer.type == krb5pac.PAC_TYPE_LOGON_INFO: + logon_info = pac_buffer.info.info.info3.base + + if expected_account_name is not None: + self.assertEqual(expected_account_name, + str(logon_info.account_name)) + + if expected_sid is not None: + expected_rid = int(expected_sid.rsplit('-', 1)[1]) + self.assertEqual(expected_rid, logon_info.rid) + def generic_check_kdc_error(self, kdc_exchange_dict, callback_dict, @@ -3548,6 +3570,8 @@ class RawKerberosTest(TestCaseInTempDir): etypes, padata, kdc_options, + expected_account_name=None, + expected_sid=None, expected_flags=None, unexpected_flags=None, expected_supported_etypes=None, @@ -3580,6 +3604,8 @@ class RawKerberosTest(TestCaseInTempDir): expected_cname=expected_cname, expected_srealm=expected_srealm, expected_sname=expected_sname, + expected_account_name=expected_account_name, + expected_sid=expected_sid, expected_supported_etypes=expected_supported_etypes, ticket_decryption_key=ticket_decryption_key, generate_padata_fn=generate_padata_fn, diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index ea629d297067..593ef94c9103 100755 --- a/python/samba/tests/krb5/s4u_tests.py +++ b/python/samba/tests/krb5/s4u_tests.py @@ -238,6 +238,10 @@ class S4UKerberosTests(KDCBaseTest): client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[client_name]) + samdb = self.get_samdb() + client_dn = client_creds.get_dn() + sid = self.get_objectSid(samdb, client_dn) + service_name = service_creds.get_username()[:-1] service_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=['host', service_name]) @@ -279,6 +283,8 @@ class S4UKerberosTests(KDCBaseTest): expected_cname=client_cname, expected_srealm=realm, expected_sname=service_sname, + expected_account_name=client_name, + expected_sid=sid, expected_flags=expected_flags, unexpected_flags=unexpected_flags, ticket_decryption_key=service_decryption_key, @@ -438,6 +444,10 @@ class S4UKerberosTests(KDCBaseTest): account_type=self.AccountType.USER, opts=client_opts) + samdb = self.get_samdb() + client_dn = client_creds.get_dn() + sid = self.get_objectSid(samdb, client_dn) + service1_opts = kdc_dict.pop('service1_opts', {}) service2_opts = kdc_dict.pop('service2_opts', {}) @@ -552,6 +562,8 @@ class S4UKerberosTests(KDCBaseTest): expected_cname=client_cname, expected_srealm=service2_realm, expected_sname=service2_sname, + expected_account_name=client_username, + expected_sid=sid, expected_supported_etypes=service2_etypes, ticket_decryption_key=service2_decryption_key, check_error_fn=check_error_fn, -- 2.25.1 From 4b45463fb175fb64a232b8d44dcfa1693c323538 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 10 Aug 2021 22:31:02 +1200 Subject: [PATCH 035/224] CVE-2020-25722 dsdb: Tests for our known set of privileged attributes This, except for where we choose to disagree, does pass against Windows 2019. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14703 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14778 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14775 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 54 ++++ source4/dsdb/tests/python/priv_attrs.py | 388 ++++++++++++++++++++++++ source4/selftest/tests.py | 2 + 3 files changed, 444 insertions(+) create mode 100644 selftest/knownfail.d/priv_attr create mode 100644 source4/dsdb/tests/python/priv_attrs.py diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr new file mode 100644 index 000000000000..31b9cb23b442 --- /dev/null +++ b/selftest/knownfail.d/priv_attr @@ -0,0 +1,54 @@ +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-RODC_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-RODC_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-del-add_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-replace_CC_default_computer diff --git a/source4/dsdb/tests/python/priv_attrs.py b/source4/dsdb/tests/python/priv_attrs.py new file mode 100644 index 000000000000..ec2b13045e5a --- /dev/null +++ b/source4/dsdb/tests/python/priv_attrs.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# This tests the restrictions on userAccountControl that apply even if write access is permitted +# +# Copyright Samuel Cabrero 2014 +# Copyright Andrew Bartlett 2014 +# +# Licenced under the GPLv3 +# + +import optparse +import sys +import unittest +import samba +import samba.getopt as options +import samba.tests +import ldb +import base64 + +sys.path.insert(0, "bin/python") +from samba.tests.subunitrun import TestProgram, SubunitOptions +from samba.tests import DynamicTestCase +from samba.subunit.run import SubunitTestRunner +from samba.auth import system_session +from samba.samdb import SamDB +from samba.dcerpc import samr, security, lsa +from samba.credentials import Credentials +from samba.ndr import ndr_unpack, ndr_pack +from samba.tests import delete_force +from samba import gensec, sd_utils +from samba.credentials import DONT_USE_KERBEROS +from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError +from ldb import Message, MessageElement, Dn +from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, \ + UF_LOCKOUT, UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,\ + UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, UF_INTERDOMAIN_TRUST_ACCOUNT, \ + UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, \ + UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, \ + UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \ + UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \ + UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS + + +parser = optparse.OptionParser("user_account_control.py [options] ") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) +host = args[0] + +if "://" not in host: + ldaphost = "ldap://%s" % host +else: + ldaphost = host + start = host.rindex("://") + host = host.lstrip(start + 3) + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) +creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + + +""" +Check the combinations of: + +rodc kdc +a2d2 +useraccountcontrol (trusted for delegation) +sidHistory + +x + +add +modify(replace) +modify(add) + +x + +sd WP on add +cc default perms +admin created, WP to user + +x + +computer +user +""" + +attrs = {"sidHistory": + {"value": ndr_pack(security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)), + "priv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, + "msDS-AllowedToDelegateTo": + {"value": f"host/{host}", + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, + "userAccountControl-a2d-user": + {"attr": "userAccountControl", + "value": str(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION|UF_NORMAL_ACCOUNT), + "priv-error": ldb.ERR_UNWILLING_TO_PERFORM, + "unpriv-add-error": ldb.ERR_UNWILLING_TO_PERFORM, + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, + "userAccountControl-a2d-computer": + {"attr": "userAccountControl", + "value": str(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION|UF_WORKSTATION_TRUST_ACCOUNT), + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + "only-1": "computer"}, + "userAccountControl-DC": + {"attr": "userAccountControl", + "value": str(UF_SERVER_TRUST_ACCOUNT), + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + "only-2": "computer"}, + "userAccountControl-RODC": + {"attr": "userAccountControl", + "value": str(UF_PARTIAL_SECRETS_ACCOUNT|UF_WORKSTATION_TRUST_ACCOUNT), + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + "only-1": "computer"}, + "msDS-SecondaryKrbTgtNumber": + {"value": "65536", + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, + "primaryGroupID": + {"value": str(security.DOMAIN_RID_ADMINS), + "priv-error": ldb.ERR_UNWILLING_TO_PERFORM, + "unpriv-add-error": ldb.ERR_UNWILLING_TO_PERFORM, + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS} + } + + + +@DynamicTestCase +class PrivAttrsTests(samba.tests.TestCase): + + def get_creds(self, target_username, target_password): + creds_tmp = Credentials() + creds_tmp.set_username(target_username) + creds_tmp.set_password(target_password) + creds_tmp.set_domain(creds.get_domain()) + creds_tmp.set_realm(creds.get_realm()) + creds_tmp.set_workstation(creds.get_workstation()) + creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() + | gensec.FEATURE_SEAL) + creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop + return creds_tmp + + def assertGotLdbError(self, got, wanted): + if not self.strict_checking: + self.assertNotEqual(got, ldb.SUCCESS) + else: + self.assertEqual(got, wanted) + + def setUp(self): + super().setUp() + + strict_checking = samba.tests.env_get_var_value('STRICT_CHECKING', allow_missing=True) + if strict_checking is None: + strict_checking = '1' + self.strict_checking = bool(int(strict_checking)) + + self.admin_creds = creds + self.admin_samdb = SamDB(url=ldaphost, credentials=self.admin_creds, lp=lp) + self.domain_sid = security.dom_sid(self.admin_samdb.get_domain_sid()) + self.base_dn = self.admin_samdb.domain_dn() + + self.unpriv_user = "testuser1" + self.unpriv_user_pw = "samba123@" + self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw) + + self.admin_sd_utils = sd_utils.SDUtils(self.admin_samdb) + + self.test_ou_name = "OU=test_priv_attrs" + self.test_ou = self.test_ou_name + "," + self.base_dn + + delete_force(self.admin_samdb, self.test_ou, controls=["tree_delete:0"]) + + self.admin_samdb.create_ou(self.test_ou) + + expected_user_dn = f"CN={self.unpriv_user},{self.test_ou_name},{self.base_dn}" + + self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw, userou=self.test_ou_name) + res = self.admin_samdb.search(expected_user_dn, + scope=SCOPE_BASE, + attrs=["objectSid"]) + + self.assertEqual(1, len(res)) + + self.unpriv_user_dn = res[0].dn + self.addCleanup(delete_force, self.admin_samdb, self.unpriv_user_dn, controls=["tree_delete:0"]) + + self.unpriv_user_sid = self.admin_sd_utils.get_object_sid(self.unpriv_user_dn) + + self.unpriv_samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp) + + @classmethod + def setUpDynamicTestCases(cls): + for test_name in attrs.keys(): + for add_or_mod in ["add", "mod-del-add", "mod-replace"]: + for permission in ["admin-add", "CC"]: + for sd in ["default", "WP"]: + for objectclass in ["computer", "user"]: + tname = f"{test_name}_{add_or_mod}_{permission}_{sd}_{objectclass}" + targs = (test_name, + add_or_mod, + permission, + sd, + objectclass) + cls.generate_dynamic_test("test_priv_attr", + tname, + *targs) + + def add_computer_ldap(self, computername, others=None, samdb=None): + dn = "CN=%s,%s" % (computername, self.test_ou) + domainname = ldb.Dn(samdb, samdb.domain_dn()).canonical_str().replace("/", "") + samaccountname = "%s$" % computername + dnshostname = "%s.%s" % (computername, domainname) + msg_dict = { + "dn": dn, + "objectclass": "computer"} + if others is not None: + msg_dict = dict(list(msg_dict.items()) + list(others.items())) + + msg = ldb.Message.from_dict(samdb, msg_dict) + msg["sAMAccountName"] = samaccountname + + print("Adding computer account %s" % computername) + try: + samdb.add(msg) + except ldb.LdbError: + print(msg) + raise + return msg.dn + + def add_user_ldap(self, username, others=None, samdb=None): + dn = "CN=%s,%s" % (username, self.test_ou) + domainname = ldb.Dn(samdb, samdb.domain_dn()).canonical_str().replace("/", "") + samaccountname = "%s$" % username + msg_dict = { + "dn": dn, + "objectclass": "user"} + if others is not None: + msg_dict = dict(list(msg_dict.items()) + list(others.items())) + + msg = ldb.Message.from_dict(samdb, msg_dict) + msg["sAMAccountName"] = samaccountname + + print("Adding user account %s" % username) + try: + samdb.add(msg) + except ldb.LdbError: + print(msg) + raise + return msg.dn + + def add_thing_ldap(self, user, others, samdb, objectclass): + if objectclass == "user": + dn = self.add_user_ldap(user, others, samdb=samdb) + elif objectclass == "computer": + dn = self.add_computer_ldap(user, others, samdb=samdb) + return dn + + def _test_priv_attr_with_args(self, test_name, add_or_mod, permission, sd, objectclass): + user="privattrs" + if "attr" in attrs[test_name]: + attr = attrs[test_name]["attr"] + else: + attr = test_name + if add_or_mod == "add": + others = {attr: attrs[test_name]["value"]} + else: + others = {} + + if permission == "CC": + samdb = self.unpriv_samdb + # Set CC on container to allow user add + mod = "(OA;CI;CC;bf967aba-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid) + self.admin_sd_utils.dacl_add_ace(self.test_ou, mod) + mod = "(OA;CI;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid) + self.admin_sd_utils.dacl_add_ace(self.test_ou, mod) + + else: + samdb = self.admin_samdb + + if sd == "WP": + # Set SD to WP to the target user as part of add + sd = "O:%sG:DUD:(OA;CIID;RPWP;;;%s)(OA;;CR;00299570-246d-11d0-a768-00aa006e0529;;%s)" % (self.unpriv_user_sid, self.unpriv_user_sid, self.unpriv_user_sid) + tmp_desc = security.descriptor.from_sddl(sd, self.domain_sid) + others["ntSecurityDescriptor"] = ndr_pack(tmp_desc) + + if add_or_mod == "add": + + # only-1 and only-2 are due to windows behaviour + + if "only-1" in attrs[test_name] and \ + attrs[test_name]["only-1"] != objectclass: + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + self.fail(f"{test_name}: Unexpectedly able to set {attr} on new {objectclass} as ADMIN (should fail LDAP_OBJECT_CLASS_VIOLATION)") + except LdbError as e5: + (enum, estr) = e5.args + self.assertGotLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, enum) + elif permission == "CC": + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + self.fail(f"{test_name}: Unexpectedly able to set {attr} on new {objectclass}") + except LdbError as e5: + (enum, estr) = e5.args + if "unpriv-add-error" in attrs[test_name]: + self.assertGotLdbError(attrs[test_name]["unpriv-add-error"], \ + enum) + else: + self.assertGotLdbError(attrs[test_name]["unpriv-error"], \ + enum) + elif "only-2" in attrs[test_name] and \ + attrs[test_name]["only-2"] != objectclass: + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + self.fail(f"{test_name}: Unexpectedly able to set {attr} on new {objectclass} as ADMIN (should fail LDAP_OBJECT_CLASS_VIOLATION)") + except LdbError as e5: + (enum, estr) = e5.args + self.assertGotLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, enum) + elif "priv-error" in attrs[test_name]: + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + self.fail(f"{test_name}: Unexpectedly able to set {attr} on new {objectclass} as ADMIN") + except LdbError as e5: + (enum, estr) = e5.args + self.assertGotLdbError(attrs[test_name]["priv-error"], enum) + else: + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + except LdbError as e5: + (enum, estr) = e5.args + self.fail(f"Failed to add account {user} as objectclass {objectclass}") + else: + try: + dn = self.add_thing_ldap(user, others, samdb, objectclass) + except LdbError as e5: + (enum, estr) = e5.args + self.fail(f"Failed to add account {user} as objectclass {objectclass}") + + if add_or_mod == "add": + return + + m = ldb.Message() + m.dn = dn + + # Do modify + if add_or_mod == "mod-del-add": + m["0"] = ldb.MessageElement([], + ldb.FLAG_MOD_DELETE, + attr) + m["1"] = ldb.MessageElement(attrs[test_name]["value"], + ldb.FLAG_MOD_ADD, + attr) + else: + m["0"] = ldb.MessageElement(attrs[test_name]["value"], + ldb.FLAG_MOD_REPLACE, + attr) + + try: + self.unpriv_samdb.modify(m) + self.fail(f"{test_name}: Unexpectedly able to set {attr} on {m.dn}") + except LdbError as e5: + (enum, estr) = e5.args + if attr == "userAccountControl" and sd == "default": + # We get a different error if we try and swap between + # being a computer back to being a user when created with "Create child" permissions + if (int(attrs[test_name]["value"]) & UF_NORMAL_ACCOUNT) \ + and objectclass == "computer" and permission == "CC": + self.assertGotLdbError(ldb.ERR_UNWILLING_TO_PERFORM, enum) + return + self.assertGotLdbError(attrs[test_name]["unpriv-error"], enum) + + + + +runner = SubunitTestRunner() +rc = 0 +if not runner.run(unittest.makeSuite(PrivAttrsTests)).wasSuccessful(): + rc = 1 +sys.exit(rc) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index c2175486aaec..d770230ab1e2 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1071,6 +1071,8 @@ plantestsuite("samba4.sam.python(fl2008r2dc)", "fl2008r2dc", [python, os.path.jo plantestsuite("samba4.sam.python(ad_dc_default)", "ad_dc_default", [python, os.path.join(DSDB_PYTEST_DIR, "sam.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) plantestsuite("samba4.asq.python(ad_dc_default)", "ad_dc_default", [python, os.path.join(DSDB_PYTEST_DIR, "asq.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) plantestsuite("samba4.user_account_control.python(ad_dc_default)", "ad_dc_default", [python, os.path.join(DSDB_PYTEST_DIR, "user_account_control.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) +plantestsuite("samba4.priv_attrs.python(ad_dc_default)", "ad_dc_default", ["STRICT_CHECKING=0", python, os.path.join(DSDB_PYTEST_DIR, "priv_attrs.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) +plantestsuite("samba4.priv_attrs.strict.python(ad_dc_default)", "ad_dc_default", [python, os.path.join(DSDB_PYTEST_DIR, "priv_attrs.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) for env in ['ad_dc_default:local', 'schema_dc:local']: planoldpythontestsuite(env, "dsdb_schema_info", -- 2.25.1 From 9fcfa3691a2479eab2a80492f5fcd2234d2703f4 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 12 Aug 2021 11:10:09 +1200 Subject: [PATCH 036/224] CVE-2020-25722 dsdb: Move krbtgt password setup after the point of checking if any passwords are changed This allows the add of an RODC, before setting the password, to avoid this module, which helps isolate testing of security around the msDS-SecondaryKrbTgtNumber attribute. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14703 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 12 +- .../dsdb/samdb/ldb_modules/password_hash.c | 106 +++++++++--------- 2 files changed, 57 insertions(+), 61 deletions(-) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index 31b9cb23b442..ab6db192aae7 100644 --- a/selftest/knownfail.d/priv_attr +++ b/selftest/knownfail.d/priv_attr @@ -14,14 +14,10 @@ samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccoun samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_admin-add_default_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_user +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_computer +samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_computer samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_computer diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 9f38a31c8dda..fce3b27db9fc 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -2479,6 +2479,59 @@ static int setup_password_fields(struct setup_password_fields_io *io) return LDB_SUCCESS; } + if (io->u.is_krbtgt) { + size_t min = 196; + size_t max = 255; + size_t diff = max - min; + size_t len = max; + struct ldb_val *krbtgt_utf16 = NULL; + + if (!io->ac->pwd_reset) { + return dsdb_module_werror(io->ac->module, + LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS, + WERR_DS_ATT_ALREADY_EXISTS, + "Password change on krbtgt not permitted!"); + } + + if (io->n.cleartext_utf16 == NULL) { + return dsdb_module_werror(io->ac->module, + LDB_ERR_UNWILLING_TO_PERFORM, + WERR_DS_INVALID_ATTRIBUTE_SYNTAX, + "Password reset on krbtgt requires UTF16!"); + } + + /* + * Instead of taking the callers value, + * we just generate a new random value here. + * + * Include null termination in the array. + */ + if (diff > 0) { + size_t tmp; + + generate_random_buffer((uint8_t *)&tmp, sizeof(tmp)); + + tmp %= diff; + + len = min + tmp; + } + + krbtgt_utf16 = talloc_zero(io->ac, struct ldb_val); + if (krbtgt_utf16 == NULL) { + return ldb_oom(ldb); + } + + *krbtgt_utf16 = data_blob_talloc_zero(krbtgt_utf16, + (len+1)*2); + if (krbtgt_utf16->data == NULL) { + return ldb_oom(ldb); + } + krbtgt_utf16->length = len * 2; + generate_secret_buffer(krbtgt_utf16->data, + krbtgt_utf16->length); + io->n.cleartext_utf16 = krbtgt_utf16; + } + /* transform the old password (for password changes) */ ret = setup_given_passwords(io, &io->og); if (ret != LDB_SUCCESS) { @@ -3656,59 +3709,6 @@ static int setup_io(struct ph_context *ac, return ldb_operr(ldb); } - if (io->u.is_krbtgt) { - size_t min = 196; - size_t max = 255; - size_t diff = max - min; - size_t len = max; - struct ldb_val *krbtgt_utf16 = NULL; - - if (!ac->pwd_reset) { - return dsdb_module_werror(ac->module, - LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS, - WERR_DS_ATT_ALREADY_EXISTS, - "Password change on krbtgt not permitted!"); - } - - if (io->n.cleartext_utf16 == NULL) { - return dsdb_module_werror(ac->module, - LDB_ERR_UNWILLING_TO_PERFORM, - WERR_DS_INVALID_ATTRIBUTE_SYNTAX, - "Password reset on krbtgt requires UTF16!"); - } - - /* - * Instead of taking the callers value, - * we just generate a new random value here. - * - * Include null termination in the array. - */ - if (diff > 0) { - size_t tmp; - - generate_random_buffer((uint8_t *)&tmp, sizeof(tmp)); - - tmp %= diff; - - len = min + tmp; - } - - krbtgt_utf16 = talloc_zero(io->ac, struct ldb_val); - if (krbtgt_utf16 == NULL) { - return ldb_oom(ldb); - } - - *krbtgt_utf16 = data_blob_talloc_zero(krbtgt_utf16, - (len+1)*2); - if (krbtgt_utf16->data == NULL) { - return ldb_oom(ldb); - } - krbtgt_utf16->length = len * 2; - generate_secret_buffer(krbtgt_utf16->data, - krbtgt_utf16->length); - io->n.cleartext_utf16 = krbtgt_utf16; - } - if (existing_msg != NULL) { NTSTATUS status; -- 2.25.1 From 0d044cbc440ea0dceba09a7bbc66c2659986c6e2 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 13 Aug 2021 17:42:23 +1200 Subject: [PATCH 037/224] CVE-2020-25722 dsdb: Restrict the setting of privileged attributes during LDAP add/modify The remaining failures in the priv_attrs (not the strict one) test are due to missing objectclass constraints on the administrator which should be addressed, but are not a security issue. A better test for confirming constraints between objectclass and userAccountControl UF_NORMAL_ACCONT/UF_WORKSTATION_TRUST values would be user_account_control.py. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14703 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14778 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14775 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 24 ---- source4/dsdb/samdb/ldb_modules/samldb.c | 148 +++++++++++++++++++++--- 2 files changed, 129 insertions(+), 43 deletions(-) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index ab6db192aae7..c3a779010d9e 100644 --- a/selftest/knownfail.d/priv_attr +++ b/selftest/knownfail.d/priv_attr @@ -1,31 +1,7 @@ -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_user samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_computer samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-AllowedToDelegateTo_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_msDS-SecondaryKrbTgtNumber_add_CC_default_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index f7d65e1bb4f2..b2eb01e5885d 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1980,6 +1980,29 @@ static int samldb_check_user_account_control_invariants(struct samldb_ctx *ac, return ret; } +static int samldb_get_domain_secdesc(struct samldb_ctx *ac, + struct security_descriptor **domain_sd) +{ + const char * const sd_attrs[] = {"ntSecurityDescriptor", NULL}; + struct ldb_result *res; + struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module)); + int ret = dsdb_module_search_dn(ac->module, ac, &res, + domain_dn, + sd_attrs, + DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + if (res->count != 1) { + return ldb_module_operr(ac->module); + } + + return dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(ac->module), + ac, res->msgs[0], domain_sd); + +} + /** * Validate that the restriction in point 5 of MS-SAMR 3.1.1.8.10 userAccountControl is honoured * @@ -1992,12 +2015,8 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, size_t i; int ret = 0; bool need_acl_check = false; - struct ldb_result *res; - const char * const sd_attrs[] = {"ntSecurityDescriptor", NULL}; struct security_token *user_token; struct security_descriptor *domain_sd; - struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module)); - struct ldb_context *ldb = ldb_module_get_ctx(ac->module); const struct uac_to_guid { uint32_t uac; uint32_t priv_to_change_from; @@ -2083,21 +2102,7 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } - ret = dsdb_module_search_dn(ac->module, ac, &res, - domain_dn, - sd_attrs, - DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, - ac->req); - if (ret != LDB_SUCCESS) { - return ret; - } - if (res->count != 1) { - return ldb_module_operr(ac->module); - } - - ret = dsdb_get_sd_from_ldb_message(ldb, - ac, res->msgs[0], &domain_sd); - + ret = samldb_get_domain_secdesc(ac, &domain_sd); if (ret != LDB_SUCCESS) { return ret; } @@ -2159,6 +2164,8 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, return ldb_module_operr(ac->module); } if (map[i].guid) { + struct ldb_dn *domain_dn + = ldb_get_default_basedn(ldb_module_get_ctx(ac->module)); dsdb_acl_debug(domain_sd, acl_user_token(ac->module), domain_dn, true, @@ -3491,7 +3498,98 @@ static char *refer_if_rodc(struct ldb_context *ldb, struct ldb_request *req, return NULL; } +/* + * Restrict all access to sensitive attributes. + * + * We don't want to even inspect the values, so we can use the same + * routine for ADD and MODIFY. + * + */ + +static int samldb_check_sensitive_attributes(struct samldb_ctx *ac) +{ + struct ldb_message_element *el = NULL; + struct security_token *user_token = NULL; + int ret; + + if (dsdb_module_am_system(ac->module)) { + return LDB_SUCCESS; + } + + user_token = acl_user_token(ac->module); + if (user_token == NULL) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + el = ldb_msg_find_element(ac->msg, "sidHistory"); + if (el) { + /* + * sidHistory is restricted to the (not implemented + * yet in Samba) DsAddSidHistory call (direct LDB access is + * as SYSTEM so will bypass this). + * + * If you want to modify this, say to merge domains, + * directly modify the sam.ldb as root. + */ + ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), + "sidHistory " + "(entry %s) cannot be created " + "or changed over LDAP!", + ldb_dn_get_linearized(ac->msg->dn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + el = ldb_msg_find_element(ac->msg, "msDS-SecondaryKrbTgtNumber"); + if (el) { + struct security_descriptor *domain_sd; + /* + * msDS-SecondaryKrbTgtNumber allows the creator to + * become an RODC, this is trusted as an RODC + * account + */ + ret = samldb_get_domain_secdesc(ac, &domain_sd); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = acl_check_extended_right(ac, domain_sd, + user_token, + GUID_DRS_DS_INSTALL_REPLICA, + SEC_ADS_CONTROL_ACCESS, + NULL); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), + "msDS-SecondaryKrbTgtNumber " + "(entry %s) cannot be created " + "or changed without " + "DS-Install-Replica extended right!", + ldb_dn_get_linearized(ac->msg->dn)); + return ret; + } + } + + el = ldb_msg_find_element(ac->msg, "msDS-AllowedToDelegateTo"); + if (el) { + /* + * msDS-AllowedToDelegateTo is incredibly powerful, + * given that it allows a server to become ANY USER on + * the target server only listed by SPN so needs to be + * protected just as the userAccountControl + * UF_TRUSTED_FOR_DELEGATION is. + */ + + bool have_priv = security_token_has_privilege(user_token, + SEC_PRIV_ENABLE_DELEGATION); + if (have_priv == false) { + ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), + "msDS-AllowedToDelegateTo " + "(entry %s) cannot be created " + "or changed without SePrivEnableDelegation!", + ldb_dn_get_linearized(ac->msg->dn)); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + } + return LDB_SUCCESS; +} /* add */ static int samldb_add(struct ldb_module *module, struct ldb_request *req) { @@ -3538,6 +3636,12 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) return ldb_operr(ldb); } + ret = samldb_check_sensitive_attributes(ac); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + el = ldb_msg_find_element(ac->msg, "fSMORoleOwner"); if (el != NULL) { ret = samldb_fsmo_role_owner_check(ac); @@ -3745,6 +3849,12 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) return ldb_operr(ldb); } + ret = samldb_check_sensitive_attributes(ac); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + if (is_undelete == NULL) { el = ldb_msg_find_element(ac->msg, "primaryGroupID"); if (el != NULL) { -- 2.25.1 From 6f017bb43a34bdac543ed427bb39ce68fd41a37b Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 20:34:54 +1200 Subject: [PATCH 038/224] CVE-2020-25722 selftest: Extend priv_attrs test - work around UF_NORMAL_ACCOUNT rules on Windows 2019 (requires |UF_PASSWD_NOTREQD or a password) - extend to also cover the sensitive UF_TRUSTED_FOR_DELEGATION BUG: https://bugzilla.samba.org/show_bug.cgi?id=14703 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14778 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14775 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 16 ++-------- source4/dsdb/tests/python/priv_attrs.py | 40 +++++++++++++++---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index c3a779010d9e..4b85a8690892 100644 --- a/selftest/knownfail.d/priv_attr +++ b/selftest/knownfail.d/priv_attr @@ -1,7 +1,3 @@ -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer -samba4.priv_attrs.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer @@ -14,13 +10,5 @@ samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_use samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-RODC_add_CC_default_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_admin-add_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-del-add_CC_default_computer -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-replace_CC_default_computer +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-computer_add_CC_WP_user +samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-computer_add_CC_default_user diff --git a/source4/dsdb/tests/python/priv_attrs.py b/source4/dsdb/tests/python/priv_attrs.py index ec2b13045e5a..aa35dcc1317d 100644 --- a/source4/dsdb/tests/python/priv_attrs.py +++ b/source4/dsdb/tests/python/priv_attrs.py @@ -99,30 +99,47 @@ attrs = {"sidHistory": {"value": ndr_pack(security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)), "priv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, - "msDS-AllowedToDelegateTo": + + "msDS-AllowedToDelegateTo": {"value": f"host/{host}", "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, - "userAccountControl-a2d-user": + + "userAccountControl-a2d-user": {"attr": "userAccountControl", - "value": str(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION|UF_NORMAL_ACCOUNT), - "priv-error": ldb.ERR_UNWILLING_TO_PERFORM, - "unpriv-add-error": ldb.ERR_UNWILLING_TO_PERFORM, + "value": str(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION|UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, - "userAccountControl-a2d-computer": + + "userAccountControl-a2d-computer": {"attr": "userAccountControl", "value": str(UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION|UF_WORKSTATION_TRUST_ACCOUNT), "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, "only-1": "computer"}, - "userAccountControl-DC": + + # This flag makes many legitimate authenticated clients + # send a forwardable ticket-granting-ticket to the server + "userAccountControl-t4d-user": + {"attr": "userAccountControl", + "value": str(UF_TRUSTED_FOR_DELEGATION|UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, + + "userAccountControl-t4d-computer": + {"attr": "userAccountControl", + "value": str(UF_TRUSTED_FOR_DELEGATION|UF_WORKSTATION_TRUST_ACCOUNT), + "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + "only-1": "computer"}, + + "userAccountControl-DC": {"attr": "userAccountControl", "value": str(UF_SERVER_TRUST_ACCOUNT), "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, "only-2": "computer"}, - "userAccountControl-RODC": + + "userAccountControl-RODC": {"attr": "userAccountControl", "value": str(UF_PARTIAL_SECRETS_ACCOUNT|UF_WORKSTATION_TRUST_ACCOUNT), "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, "only-1": "computer"}, + "msDS-SecondaryKrbTgtNumber": {"value": "65536", "unpriv-error": ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS}, @@ -369,13 +386,6 @@ class PrivAttrsTests(samba.tests.TestCase): self.fail(f"{test_name}: Unexpectedly able to set {attr} on {m.dn}") except LdbError as e5: (enum, estr) = e5.args - if attr == "userAccountControl" and sd == "default": - # We get a different error if we try and swap between - # being a computer back to being a user when created with "Create child" permissions - if (int(attrs[test_name]["value"]) & UF_NORMAL_ACCOUNT) \ - and objectclass == "computer" and permission == "CC": - self.assertGotLdbError(ldb.ERR_UNWILLING_TO_PERFORM, enum) - return self.assertGotLdbError(attrs[test_name]["unpriv-error"], enum) -- 2.25.1 From f3a22da4559b0344c8f68c50c51920eba1e50e36 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 10:21:03 +1200 Subject: [PATCH 039/224] CVE-2020-25722 selftest: Test combinations of account type and objectclass for creating a user The idea here is to split out the restrictions seen on Windows 2019 at the schema level, as seen when acting as an administrator. These pass against Windows 2019 except for the account type swapping which is not wanted. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- .../user_account_control-uac_mod_lock | 11 ++ .../dsdb/tests/python/user_account_control.py | 165 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 selftest/knownfail.d/user_account_control-uac_mod_lock diff --git a/selftest/knownfail.d/user_account_control-uac_mod_lock b/selftest/knownfail.d/user_account_control-uac_mod_lock new file mode 100644 index 000000000000..a70534506f37 --- /dev/null +++ b/selftest/knownfail.d/user_account_control-uac_mod_lock @@ -0,0 +1,11 @@ +# We do not want user account control account type swapping, so we mark these as knownfail +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_computer_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_deladd +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index c9b50b83e9da..442fe7412200 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -91,6 +91,41 @@ account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_ class UserAccountControlTests(samba.tests.TestCase): @classmethod def setUpDynamicTestCases(cls): + for account_type in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + for objectclass in ["computer", "user"]: + test_name = f"{account_type_str}_{objectclass}" + cls.generate_dynamic_test("test_objectclass_uac_lock", + test_name, + account_type, + objectclass) + + for account_type in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + for account_type2 in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + for how in ["replace", "deladd"]: + account_type2_str = dsdb.user_account_control_flag_bit_to_string(account_type2) + test_name = f"{account_type_str}_{account_type2_str}_{how}" + cls.generate_dynamic_test("test_objectclass_uac_mod_lock", + test_name, + account_type, + account_type2, + how) + for objectclass in ["user", "computer"]: + for how in ["replace", "deladd"]: + test_name = f"{account_type_str}_{objectclass}_{how}" + cls.generate_dynamic_test("test_objectclass_mod_lock", + test_name, + account_type, + objectclass, + how) + for account_type in [UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT]: account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) cls.generate_dynamic_test("test_uac_bits_unrelated_modify", @@ -844,6 +879,136 @@ class UserAccountControlTests(samba.tests.TestCase): "primaryGroupID") self.admin_samdb.modify(m) + def _test_objectclass_uac_lock_with_args(self, + account_type, + objectclass): + name = "oc_uac_lock$" + dn = "CN=%s,%s" % (name, self.OU) + msg_dict = { + "dn": dn, + "objectclass": objectclass, + "samAccountName": name, + "userAccountControl": str(account_type | UF_PASSWD_NOTREQD)} + + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + + print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") + + if (objectclass == "user" \ + and account_type == UF_NORMAL_ACCOUNT): + self.admin_samdb.add(msg_dict) + elif objectclass == "computer": + self.admin_samdb.add(msg_dict) + else: + self.assertRaisesLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, + "Should have been unable to {account_type_str} on {objectclass}", + self.admin_samdb.add, msg_dict) + + def _test_objectclass_uac_mod_lock_with_args(self, + account_type, + account_type2, + how): + name = "uac_mod_lock$" + dn = "CN=%s,%s" % (name, self.OU) + if account_type == UF_NORMAL_ACCOUNT: + objectclass = "user" + else: + objectclass = "computer" + + msg_dict = { + "dn": dn, + "objectclass": objectclass, + "samAccountName": name, + "userAccountControl": str(account_type | UF_PASSWD_NOTREQD)} + + account_type_str \ + = dsdb.user_account_control_flag_bit_to_string(account_type) + account_type2_str \ + = dsdb.user_account_control_flag_bit_to_string(account_type2) + + print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") + + self.admin_samdb.add(msg_dict) + + m = ldb.Message() + m.dn = ldb.Dn(self.admin_samdb, dn) + if how == "replace": + m["userAccountControl"] = ldb.MessageElement(str(account_type2 | UF_PASSWD_NOTREQD), + ldb.FLAG_MOD_REPLACE, "userAccountControl") + elif how == "deladd": + m["0userAccountControl"] = ldb.MessageElement([], + ldb.FLAG_MOD_DELETE, "userAccountControl") + m["1userAccountControl"] = ldb.MessageElement(str(account_type2 | UF_PASSWD_NOTREQD), + ldb.FLAG_MOD_ADD, "userAccountControl") + else: + raise ValueError(f"{how} was not a valid argument") + + if (account_type in [UF_SERVER_TRUST_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT]) and \ + (account_type2 in [UF_SERVER_TRUST_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT]): + self.admin_samdb.modify(m) + elif (account_type == account_type2): + self.admin_samdb.modify(m) + else: + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + f"Should have been unable to change {account_type_str} to {account_type2_str}", + self.admin_samdb.modify, m) + + def _test_objectclass_mod_lock_with_args(self, + account_type, + objectclass, + how): + name = "uac_mod_lock$" + dn = "CN=%s,%s" % (name, self.OU) + if objectclass == "computer": + new_objectclass = ["top", + "person", + "organizationalPerson", + "user"] + elif objectclass == "user": + new_objectclass = ["top", + "person", + "organizationalPerson", + "user", + "computer"] + + msg_dict = { + "dn": dn, + "objectclass": objectclass, + "samAccountName": name, + "userAccountControl": str(account_type | UF_PASSWD_NOTREQD)} + + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + + print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") + + try: + self.admin_samdb.add(msg_dict) + if (objectclass == "user" \ + and account_type != UF_NORMAL_ACCOUNT): + self.fail("Able to create {account_type_str} on {objectclass}") + except LdbError as e: + (enum, estr) = e.args + self.assertEqual(enum, ldb.ERR_OBJECT_CLASS_VIOLATION) + + if objectclass == "user" and account_type != UF_NORMAL_ACCOUNT: + return + + m = ldb.Message() + m.dn = ldb.Dn(self.admin_samdb, dn) + if how == "replace": + m["objectclass"] = ldb.MessageElement(new_objectclass, + ldb.FLAG_MOD_REPLACE, "objectclass") + elif how == "adddel": + m["0objectclass"] = ldb.MessageElement([], + ldb.FLAG_MOD_DELETE, "objectclass") + m["1objectclass"] = ldb.MessageElement(new_objectclass, + ldb.FLAG_MOD_ADD, "objectclass") + + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + "Should have been unable Able to change objectclass of a {objectclass}", + self.admin_samdb.modify, m) runner = SubunitTestRunner() rc = 0 -- 2.25.1 From 980e5f33bc6afc45b6c2637fc5c98bf984b1b131 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 20 Sep 2021 12:35:51 +1200 Subject: [PATCH 040/224] CVE-2020-25722 selftest: allow for future failures in BindTests.test_virtual_email_account_style_bind This allows for any failures here to be handled via the knownfail system. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- auth/credentials/tests/bind.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/auth/credentials/tests/bind.py b/auth/credentials/tests/bind.py index 8bee6f96c62a..b6b65a56c757 100755 --- a/auth/credentials/tests/bind.py +++ b/auth/credentials/tests/bind.py @@ -90,7 +90,8 @@ class BindTests(samba.tests.TestCase): # this test to detect when the LDAP DN is being double-parsed # but must be in the user@realm style to allow the account to # be created - self.ldb.add_ldif(""" + try: + self.ldb.add_ldif(""" dn: """ + self.virtual_user_dn + """ cn: frednurk@""" + self.realm + """ displayName: Fred Nurk @@ -103,13 +104,21 @@ objectClass: person objectClass: top objectClass: user """) + except LdbError as e: + (num, msg) = e.args + self.fail(f"Failed to create e-mail user: {msg}") + self.addCleanup(delete_force, self.ldb, self.virtual_user_dn) - self.ldb.modify_ldif(""" + try: + self.ldb.modify_ldif(""" dn: """ + self.virtual_user_dn + """ changetype: modify replace: unicodePwd unicodePwd:: """ + base64.b64encode(u"\"P@ssw0rd\"".encode('utf-16-le')).decode('utf8') + """ """) + except LdbError as e: + (num, msg) = e.args + self.fail(f"Failed to set password on e-mail user: {msg}") self.ldb.enable_account('distinguishedName=%s' % self.virtual_user_dn) -- 2.25.1 From 692be67d0e97361faa22be48ff63052854d6215e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 20 Sep 2021 14:54:03 +1200 Subject: [PATCH 041/224] CVE-2020-25722 selftest: Catch possible errors in PasswordSettingsTestCase.test_pso_none_applied() This allows future patches to restrict changing the account type without triggering an error. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- .../dsdb/tests/python/password_settings.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/source4/dsdb/tests/python/password_settings.py b/source4/dsdb/tests/python/password_settings.py index fcb671690c34..e1c49d7bffb2 100644 --- a/source4/dsdb/tests/python/password_settings.py +++ b/source4/dsdb/tests/python/password_settings.py @@ -594,19 +594,27 @@ class PasswordSettingsTestCase(PasswordTestCase): dummy_pso.apply_to(user.dn) self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) - # now clear the ADS_UF_NORMAL_ACCOUNT flag for the user, which should - # mean a resultant PSO is no longer returned (we're essentially turning - # the user into a DC here, which is a little overkill but tests - # behaviour as per the Windows specification) - self.set_attribute(user.dn, "userAccountControl", - str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT), - operation=FLAG_MOD_REPLACE) + try: + # now clear the ADS_UF_NORMAL_ACCOUNT flag for the user, which should + # mean a resultant PSO is no longer returned (we're essentially turning + # the user into a DC here, which is a little overkill but tests + # behaviour as per the Windows specification) + self.set_attribute(user.dn, "userAccountControl", + str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT), + operation=FLAG_MOD_REPLACE) + except ldb.LdbError as e: + (num, msg) = e.args + self.fail("Failed to change user into a workstation: {msg}") self.assertIsNone(user.get_resultant_PSO()) - # reset it back to a normal user account - self.set_attribute(user.dn, "userAccountControl", - str(dsdb.UF_NORMAL_ACCOUNT), - operation=FLAG_MOD_REPLACE) + try: + # reset it back to a normal user account + self.set_attribute(user.dn, "userAccountControl", + str(dsdb.UF_NORMAL_ACCOUNT), + operation=FLAG_MOD_REPLACE) + except ldb.LdbError as e: + (num, msg) = e.args + self.fail("Failed to change user back into a user: {msg}") self.assertTrue(user.get_resultant_PSO() == dummy_pso.dn) # no PSO should be returned if RID is equal to DOMAIN_USER_RID_KRBTGT -- 2.25.1 From ea3f38495bed2314697203e768a55667958b08a9 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 17 Sep 2021 13:41:40 +1200 Subject: [PATCH 042/224] CVE-2020-25722 selftest: Catch errors from samdb.modify() in user_account_control tests This will allow these to be listed in a knownfail shortly. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- .../dsdb/tests/python/user_account_control.py | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 442fe7412200..a22a72f12daa 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -306,7 +306,11 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") - self.samdb.modify(m) + try: + self.samdb.modify(m) + except LdbError as e: + (enum, estr) = e.args + self.fail(f"got {estr} setting userAccountControl to UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD") m = ldb.Message() m.dn = res[0].dn @@ -361,7 +365,11 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") - self.samdb.modify(m) + try: + self.samdb.modify(m) + except LdbError as e: + (enum, estr) = e.args + self.fail(f"got {estr} setting userAccountControl to UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD") m = ldb.Message() m.dn = res[0].dn @@ -458,7 +466,11 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE), ldb.FLAG_MOD_REPLACE, "userAccountControl") - self.admin_samdb.modify(m) + try: + self.admin_samdb.modify(m) + except LdbError as e: + (enum, estr) = e.args + self.fail(f"got {estr} setting userAccountControl to UF_ACCOUNTDISABLE (as admin)") res = self.admin_samdb.search("%s" % self.base_dn, expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, @@ -579,7 +591,11 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(orig_uac), ldb.FLAG_MOD_REPLACE, "userAccountControl") - self.admin_samdb.modify(m) + try: + self.admin_samdb.modify(m) + except LdbError as e: + (enum, estr) = e.args + self.fail(f"got {estr} resetting userAccountControl to initial value {orig_uac:#08x}") res = self.admin_samdb.search("%s" % self.base_dn, expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, @@ -898,7 +914,12 @@ class UserAccountControlTests(samba.tests.TestCase): and account_type == UF_NORMAL_ACCOUNT): self.admin_samdb.add(msg_dict) elif objectclass == "computer": - self.admin_samdb.add(msg_dict) + try: + self.admin_samdb.add(msg_dict) + except ldb.LdbError as e: + (num, msg) = e.args + self.fail("Failed to create {objectclass} account " + "with {account_type_string}") else: self.assertRaisesLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, "Should have been unable to {account_type_str} on {objectclass}", -- 2.25.1 From 130c11d1823233ccb698c89232f0599c83d7da2e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 16 Sep 2021 08:46:42 +1200 Subject: [PATCH 043/224] CVE-2020-25722 dsdb: objectclass computer becomes UF_WORKSTATION_TRUST by default There are a lot of knownfail entries added with this commit. These all need to be addressed and removed in subsequent commits which will restructure the tests to pass within this new reality. This default applies even to users with administrator rights, as changing the default based on permissions would break to many assumptions. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 42 +++++++++++++++++++ source4/dsdb/samdb/ldb_modules/samldb.c | 27 +++++++++--- 2 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 selftest/knownfail.d/uac_objectclass_restrict diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict new file mode 100644 index 000000000000..a076f9cfedb9 --- /dev/null +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -0,0 +1,42 @@ +# Knownfail entries due to restricting the creation of computer/user +# accounts (in terms of userAccountControl) that do not match the objectclass +# +# All these tests need to be fixed and the entries here removed + +^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_isCriticalSystemObject\(fl2008r2dc\) +^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_userAccountControl\(fl2008r2dc\) +^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) +^samba4.ldap.python\(ad_dc_default\).__main__.BasicTests.test_all\(ad_dc_default\) +^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_isCriticalSystemObject\(ad_dc_default\) +^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_userAccountControl\(ad_dc_default\) +^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_sd_cc\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_admin_mod_uac\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_computer_cc\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x10000000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x20000000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x40000000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x80000000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00000004\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00000400\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00004000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00008000\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_ACCOUNTDISABLE\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_DONT_EXPIRE_PASSWD\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_DONT_REQUIRE_PREAUTH\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_HOMEDIR_REQUIRED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_LOCKOUT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_MNS_LOGON_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NORMAL_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NOT_DELEGATED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NO_AUTH_DATA_REQUIRED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWD_CANT_CHANGE\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWD_NOTREQD\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWORD_EXPIRED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SCRIPT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SMARTCARD_REQUIRED\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_AES_KEYS\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_DES_KEY_ONLY\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index b2eb01e5885d..070f9da1ea99 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1416,19 +1416,33 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) switch(ac->type) { case SAMLDB_TYPE_USER: { + bool is_computer_objectclass; bool uac_generated = false, uac_add_flags = false; - + uint32_t default_user_account_control = UF_NORMAL_ACCOUNT; /* Step 1.2: Default values */ ret = dsdb_user_obj_set_defaults(ldb, ac->msg, ac->req); if (ret != LDB_SUCCESS) return ret; + is_computer_objectclass + = (samdb_find_attribute(ldb, + ac->msg, + "objectclass", + "computer") + != NULL); + + if (is_computer_objectclass) { + default_user_account_control + = UF_WORKSTATION_TRUST_ACCOUNT; + } + + /* On add operations we might need to generate a * "userAccountControl" (if it isn't specified). */ el = ldb_msg_find_element(ac->msg, "userAccountControl"); if ((el == NULL) && (ac->req->operation == LDB_ADD)) { ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg, "userAccountControl", - UF_NORMAL_ACCOUNT); + default_user_account_control); if (ret != LDB_SUCCESS) { return ret; } @@ -1447,11 +1461,14 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) raw_uac = user_account_control; /* * "userAccountControl" = 0 or missing one of - * the types means "UF_NORMAL_ACCOUNT". See - * MS-SAMR 3.1.1.8.10 point 8 + * the types means "UF_NORMAL_ACCOUNT" + * or "UF_WORKSTATION_TRUST_ACCOUNT" (if a computer). + * See MS-SAMR 3.1.1.8.10 point 8 */ if ((user_account_control & UF_ACCOUNT_TYPE_MASK) == 0) { - user_account_control = UF_NORMAL_ACCOUNT | user_account_control; + user_account_control + = default_user_account_control + | user_account_control; uac_generated = true; } -- 2.25.1 From 83625565fa3f7510c3ba88943aa3474e72f2434e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 15:42:08 +1300 Subject: [PATCH 044/224] CVE-2020-25722 dsdb: Improve privileged and unprivileged tests for objectclass/doller/UAC This helps ensure we cover off all the cases that matter for objectclass/trailing-doller/userAccountControl BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_dollar_lock | 1 + selftest/knownfail.d/uac_objectclass_restrict | 18 +- .../user_account_control-uac_mod_lock | 11 -- .../dsdb/tests/python/user_account_control.py | 172 +++++++++++++----- 4 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 selftest/knownfail.d/uac_dollar_lock delete mode 100644 selftest/knownfail.d/user_account_control-uac_mod_lock diff --git a/selftest/knownfail.d/uac_dollar_lock b/selftest/knownfail.d/uac_dollar_lock new file mode 100644 index 000000000000..8c70c859fa4a --- /dev/null +++ b/selftest/knownfail.d/uac_dollar_lock @@ -0,0 +1 @@ +^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_cc_plain \ No newline at end of file diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index a076f9cfedb9..bb0787c1a48b 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -13,6 +13,22 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_sd_cc\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_admin_mod_uac\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_computer_cc\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_computer_replace\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_NORMAL_ACCOUNT_computer_cc_plain\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_NORMAL_ACCOUNT_computer_cc_withdollar\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_SERVER_TRUST_ACCOUNT_user_cc_plain\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_SERVER_TRUST_ACCOUNT_user_cc_withdollar\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_deladd_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_replace_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace_wp\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x10000000\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x20000000\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x40000000\(ad_dc_default\) @@ -38,5 +54,3 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SMARTCARD_REQUIRED\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_AES_KEYS\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_DES_KEY_ONLY\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) diff --git a/selftest/knownfail.d/user_account_control-uac_mod_lock b/selftest/knownfail.d/user_account_control-uac_mod_lock deleted file mode 100644 index a70534506f37..000000000000 --- a/selftest/knownfail.d/user_account_control-uac_mod_lock +++ /dev/null @@ -1,11 +0,0 @@ -# We do not want user account control account type swapping, so we mark these as knownfail -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_computer_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_deladd -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index a22a72f12daa..dabbac6373a5 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -91,32 +91,43 @@ account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_ class UserAccountControlTests(samba.tests.TestCase): @classmethod def setUpDynamicTestCases(cls): + for priv in [(True, "priv"), (False, "cc")]: + for account_type in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + for objectclass in ["computer", "user"]: + for name in [("oc_uac_lock$", "withdollar"), \ + ("oc_uac_lock", "plain")]: + test_name = f"{account_type_str}_{objectclass}_{priv[1]}_{name[1]}" + cls.generate_dynamic_test("test_objectclass_uac_dollar_lock", + test_name, + account_type, + objectclass, + name[0], + priv[0]) + + for priv in [(True, "priv"), (False, "wp")]: + for account_type in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + for account_type2 in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT]: + for how in ["replace", "deladd"]: + account_type2_str = dsdb.user_account_control_flag_bit_to_string(account_type2) + test_name = f"{account_type_str}_{account_type2_str}_{how}_{priv[1]}" + cls.generate_dynamic_test("test_objectclass_uac_mod_lock", + test_name, + account_type, + account_type2, + how, + priv[0]) for account_type in [UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT]: account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) - for objectclass in ["computer", "user"]: - test_name = f"{account_type_str}_{objectclass}" - cls.generate_dynamic_test("test_objectclass_uac_lock", - test_name, - account_type, - objectclass) - - for account_type in [UF_NORMAL_ACCOUNT, - UF_WORKSTATION_TRUST_ACCOUNT, - UF_SERVER_TRUST_ACCOUNT]: - account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) - for account_type2 in [UF_NORMAL_ACCOUNT, - UF_WORKSTATION_TRUST_ACCOUNT, - UF_SERVER_TRUST_ACCOUNT]: - for how in ["replace", "deladd"]: - account_type2_str = dsdb.user_account_control_flag_bit_to_string(account_type2) - test_name = f"{account_type_str}_{account_type2_str}_{how}" - cls.generate_dynamic_test("test_objectclass_uac_mod_lock", - test_name, - account_type, - account_type2, - how) for objectclass in ["user", "computer"]: for how in ["replace", "deladd"]: test_name = f"{account_type_str}_{objectclass}_{how}" @@ -895,10 +906,11 @@ class UserAccountControlTests(samba.tests.TestCase): "primaryGroupID") self.admin_samdb.modify(m) - def _test_objectclass_uac_lock_with_args(self, - account_type, - objectclass): - name = "oc_uac_lock$" + def _test_objectclass_uac_dollar_lock_with_args(self, + account_type, + objectclass, + name, + priv): dn = "CN=%s,%s" % (name, self.OU) msg_dict = { "dn": dn, @@ -910,25 +922,57 @@ class UserAccountControlTests(samba.tests.TestCase): print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") - if (objectclass == "user" \ - and account_type == UF_NORMAL_ACCOUNT): - self.admin_samdb.add(msg_dict) - elif objectclass == "computer": - try: - self.admin_samdb.add(msg_dict) - except ldb.LdbError as e: - (num, msg) = e.args - self.fail("Failed to create {objectclass} account " - "with {account_type_string}") + if priv: + samdb = self.admin_samdb else: - self.assertRaisesLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, - "Should have been unable to {account_type_str} on {objectclass}", - self.admin_samdb.add, msg_dict) + user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) + mod = "(OA;;CC;;;%s)" % str(user_sid) + + self.sd_utils.dacl_add_ace(self.OU, mod) + samdb = self.samdb + + enum = ldb.SUCCESS + try: + samdb.add(msg_dict) + except ldb.LdbError as e: + (enum, msg) = e.args + + if (account_type == UF_SERVER_TRUST_ACCOUNT + and objectclass != "computer"): + self.assertEqual(enum, ldb.ERR_OBJECT_CLASS_VIOLATION) + return + + if priv == False and account_type == UF_SERVER_TRUST_ACCOUNT: + self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) + return + + if (objectclass == "user" + and account_type != UF_NORMAL_ACCOUNT): + self.assertEqual(enum, ldb.ERR_OBJECT_CLASS_VIOLATION) + return + + if (not priv and objectclass == "computer" + and account_type == UF_NORMAL_ACCOUNT): + self.assertEqual(enum, ldb.ERR_OBJECT_CLASS_VIOLATION) + return + + if priv and account_type == UF_NORMAL_ACCOUNT: + self.assertEqual(enum, 0) + return + + if (priv == False and + account_type != UF_NORMAL_ACCOUNT and + name[-1] != '$'): + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + return + + self.assertEqual(enum, 0) def _test_objectclass_uac_mod_lock_with_args(self, account_type, account_type2, - how): + how, + priv): name = "uac_mod_lock$" dn = "CN=%s,%s" % (name, self.OU) if account_type == UF_NORMAL_ACCOUNT: @@ -949,10 +993,25 @@ class UserAccountControlTests(samba.tests.TestCase): print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") + if priv: + samdb = self.admin_samdb + else: + samdb = self.samdb + + user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) + + # Create the object as admin self.admin_samdb.add(msg_dict) + # We want to test what the underlying rules for non-admins + # regardless of security descriptors are, so set this very, + # dangerously, broadly + mod = "(OA;;WP;;;%s)" % str(user_sid) + + self.sd_utils.dacl_add_ace(dn, mod) + m = ldb.Message() - m.dn = ldb.Dn(self.admin_samdb, dn) + m.dn = ldb.Dn(samdb, dn) if how == "replace": m["userAccountControl"] = ldb.MessageElement(str(account_type2 | UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") @@ -964,17 +1023,36 @@ class UserAccountControlTests(samba.tests.TestCase): else: raise ValueError(f"{how} was not a valid argument") - if (account_type in [UF_SERVER_TRUST_ACCOUNT, - UF_WORKSTATION_TRUST_ACCOUNT]) and \ + if (account_type == account_type2): + samdb.modify(m) + elif (account_type == UF_NORMAL_ACCOUNT) and \ + (account_type2 == UF_SERVER_TRUST_ACCOUNT) and not priv: + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Should have been unable to change {account_type_str} to {account_type2_str}", + samdb.modify, m) + elif (account_type == UF_NORMAL_ACCOUNT) and \ + (account_type2 == UF_SERVER_TRUST_ACCOUNT) and priv: + self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + f"Should have been unable to change {account_type_str} to {account_type2_str}", + samdb.modify, m) + elif (account_type == UF_WORKSTATION_TRUST_ACCOUNT) and \ + (account_type2 == UF_SERVER_TRUST_ACCOUNT) and not priv: + self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + f"Should have been unable to change {account_type_str} to {account_type2_str}", + samdb.modify, m) + elif priv: + samdb.modify(m) + elif (account_type in [UF_SERVER_TRUST_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT]) and \ (account_type2 in [UF_SERVER_TRUST_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT]): - self.admin_samdb.modify(m) + samdb.modify(m) elif (account_type == account_type2): - self.admin_samdb.modify(m) + samdb.modify(m) else: - self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + self.assertRaisesLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, f"Should have been unable to change {account_type_str} to {account_type2_str}", - self.admin_samdb.modify, m) + samdb.modify, m) def _test_objectclass_mod_lock_with_args(self, account_type, -- 2.25.1 From 2ccb0f254be42e3270d3d861da9f343a52147a5e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 23:33:32 +1300 Subject: [PATCH 045/224] CVE-2020-25722 dsdb: Add tests for modifying objectClass, userAccountControl and sAMAccountName BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14889 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/uac_mod_lock | 46 ++++++ .../dsdb/tests/python/user_account_control.py | 150 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 selftest/knownfail.d/uac_mod_lock diff --git a/selftest/knownfail.d/uac_mod_lock b/selftest/knownfail.d/uac_mod_lock new file mode 100644 index 000000000000..6f45fa505546 --- /dev/null +++ b/selftest/knownfail.d/uac_mod_lock @@ -0,0 +1,46 @@ +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_None_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index dabbac6373a5..1633998ada42 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -124,6 +124,46 @@ class UserAccountControlTests(samba.tests.TestCase): account_type2, how, priv[0]) + + for objectclass in ["computer", "user"]: + account_types = [UF_NORMAL_ACCOUNT] + if objectclass == "computer": + account_types.append(UF_WORKSTATION_TRUST_ACCOUNT) + account_types.append(UF_SERVER_TRUST_ACCOUNT) + + for account_type in account_types: + account_type_str = ( + dsdb.user_account_control_flag_bit_to_string( + account_type)) + for account_type2 in [UF_NORMAL_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT, + UF_PARTIAL_SECRETS_ACCOUNT, + None]: + if account_type2 is None: + account_type2_str = None + else: + account_type2_str = ( + dsdb.user_account_control_flag_bit_to_string( + account_type2)) + + for objectclass2 in ["computer", "user", None]: + for name2 in [("oc_uac_lock", "remove_dollar"), + (None, "keep_dollar")]: + test_name = (f"{priv[1]}_{objectclass}_" + f"{account_type_str}_to_" + f"{objectclass2}_" + f"{account_type2_str}_" + f"{name2[1]}") + cls.generate_dynamic_test("test_mod_lock", + test_name, + objectclass, + objectclass2, + account_type, + account_type2, + name2[0], + priv[0]) + for account_type in [UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT]: @@ -968,6 +1008,116 @@ class UserAccountControlTests(samba.tests.TestCase): self.assertEqual(enum, 0) + def _test_mod_lock_with_args(self, + objectclass, + objectclass2, + account_type, + account_type2, + name2, + priv): + name = "oc_uac_lock$" + + dn = "CN=%s,%s" % (name, self.OU) + msg_dict = { + "dn": dn, + "objectclass": objectclass, + "samAccountName": name, + "userAccountControl": str(account_type | UF_PASSWD_NOTREQD)} + + account_type_str = dsdb.user_account_control_flag_bit_to_string( + account_type) + + print(f"Adding account {name} as {account_type_str} " + f"with objectclass {objectclass}") + + # Create the object as admin + self.admin_samdb.add(msg_dict) + + if priv: + samdb = self.admin_samdb + else: + samdb = self.samdb + + user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) + + # We want to test what the underlying rules for non-admins regardless + # of security descriptors are, so set this very, dangerously, broadly + mod = f"(OA;;WP;;;{user_sid})" + + self.sd_utils.dacl_add_ace(dn, mod) + + msg = "Modifying account" + if name2 is not None: + msg += f" to {name2}" + if account_type2 is not None: + account_type2_str = dsdb.user_account_control_flag_bit_to_string( + account_type2) + msg += f" as {account_type2_str}" + else: + account_type2_str = None + if objectclass2 is not None: + msg += f" with objectClass {objectclass2}" + print(msg) + + msg = ldb.Message(ldb.Dn(samdb, dn)) + if objectclass2 is not None: + msg["objectClass"] = ldb.MessageElement(objectclass2, + ldb.FLAG_MOD_REPLACE, + "objectClass") + if name2 is not None: + msg["sAMAccountName"] = ldb.MessageElement(name2, + ldb.FLAG_MOD_REPLACE, + "sAMAccountName") + if account_type2 is not None: + msg["userAccountControl"] = ldb.MessageElement( + str(account_type2 | UF_PASSWD_NOTREQD), + ldb.FLAG_MOD_REPLACE, + "userAccountControl") + enum = ldb.SUCCESS + try: + samdb.modify(msg) + except ldb.LdbError as e: + enum, _ = e.args + + # Setting userAccountControl to be an RODC is not allowed. + if account_type2 == UF_PARTIAL_SECRETS_ACCOUNT: + self.assertEqual(enum, ldb.ERR_OTHER) + return + + # Unprivileged users cannot change userAccountControl. The exception is + # changing a non-normal account to UF_WORKSTATION_TRUST_ACCOUNT, which + # is allowed. + if (not priv + and account_type2 is not None + and account_type != account_type2 + and (account_type == UF_NORMAL_ACCOUNT + or account_type2 != UF_WORKSTATION_TRUST_ACCOUNT)): + self.assertIn(enum, [ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + ldb.ERR_OBJECT_CLASS_VIOLATION]) + return + + # A non-computer account cannot have UF_SERVER_TRUST_ACCOUNT. + if objectclass == "user" and account_type2 == UF_SERVER_TRUST_ACCOUNT: + self.assertIn(enum, [ldb.ERR_UNWILLING_TO_PERFORM, + ldb.ERR_OBJECT_CLASS_VIOLATION]) + return + + # The objectClass is not allowed to change. + if objectclass2 is not None and objectclass != objectclass2: + self.assertIn(enum, [ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_UNWILLING_TO_PERFORM]) + return + + # Unprivileged users cannot remove the trailing dollar from a computer + # account. + if not priv and objectclass == "computer" and ( + name2 is not None and name2[-1] != "$"): + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + return + + self.assertEqual(enum, 0) + return + def _test_objectclass_uac_mod_lock_with_args(self, account_type, account_type2, -- 2.25.1 From e229b124d8ced27202756ff761b7ae269d5218fa Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 16:07:46 +1300 Subject: [PATCH 046/224] CVE-2020-25722 dsdb: Prohibit mismatch between UF_ account types and objectclass. There are a lot of knownfail entries added with this commit. These all need to be addressed and removed in subsequent commits which will restructure the tests to pass within this new reality. The restriction is not applied to users with administrator rights, as this breaks a lot of tests and provides no security benefit. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 6 - selftest/knownfail.d/uac_mod_lock | 6 - selftest/knownfail.d/uac_objectclass_restrict | 35 ++-- source4/dsdb/samdb/ldb_modules/samldb.c | 153 ++++++++++++++---- 4 files changed, 145 insertions(+), 55 deletions(-) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index 4b85a8690892..e0d6104cec9c 100644 --- a/selftest/knownfail.d/priv_attr +++ b/selftest/knownfail.d/priv_attr @@ -6,9 +6,3 @@ samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sid samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_computer samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_admin-add_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-RODC_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-RODC_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-computer_add_CC_default_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-computer_add_CC_WP_user -samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-computer_add_CC_default_user diff --git a/selftest/knownfail.d/uac_mod_lock b/selftest/knownfail.d/uac_mod_lock index 6f45fa505546..a84a236c355b 100644 --- a/selftest/knownfail.d/uac_mod_lock +++ b/selftest/knownfail.d/uac_mod_lock @@ -24,16 +24,10 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_SERVER_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_SERVER_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index bb0787c1a48b..040c4eb219d7 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -10,6 +10,16 @@ ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_isCriticalSystemObject\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_userAccountControl\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_WP_user\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_default_user\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_default_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-del-add_CC_default_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_mod-replace_CC_default_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_add_CC_WP_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_add_CC_default_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_mod-del-add_CC_default_computer\(ad_dc_default\) +^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_mod-replace_CC_default_computer\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_sd_cc\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_admin_mod_uac\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_computer_cc\(ad_dc_default\) @@ -17,18 +27,6 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_NORMAL_ACCOUNT_computer_cc_plain\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_NORMAL_ACCOUNT_computer_cc_withdollar\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_SERVER_TRUST_ACCOUNT_user_cc_plain\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_SERVER_TRUST_ACCOUNT_user_cc_withdollar\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_deladd_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_WORKSTATION_TRUST_ACCOUNT_replace_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_SERVER_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_deladd_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_UF_NORMAL_ACCOUNT_replace_wp\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x10000000\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x20000000\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x40000000\(ad_dc_default\) @@ -54,3 +52,16 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SMARTCARD_REQUIRED\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_AES_KEYS\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_DES_KEY_ONLY\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_deladd_priv\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_deladd_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_replace_priv\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_replace_wp\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT_UF_PASSWD_NOTREQD\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_TRUSTED_FOR_DELEGATION\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION\(ad_dc_default\) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 070f9da1ea99..c46aa13cf8a1 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1368,7 +1368,8 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac, struct dom_sid *sid, uint32_t req_uac, uint32_t user_account_control, - uint32_t user_account_control_old); + uint32_t user_account_control_old, + bool is_computer_objectclass); /* * "Objectclass" trigger (MS-SAMR 3.1.1.8.1) @@ -1487,21 +1488,12 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) ret = samldb_check_user_account_control_rules(ac, NULL, raw_uac, user_account_control, - 0); + 0, + is_computer_objectclass); if (ret != LDB_SUCCESS) { return ret; } - /* Workstation and (read-only) DC objects do need objectclass "computer" */ - if ((samdb_find_attribute(ldb, ac->msg, - "objectclass", "computer") == NULL) && - (user_account_control & - (UF_SERVER_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT))) { - ldb_set_errstring(ldb, - "samldb: Requested account type does need objectclass 'computer'!"); - return LDB_ERR_OBJECT_CLASS_VIOLATION; - } - /* add "sAMAccountType" attribute */ ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL); if (ret != LDB_SUCCESS) { @@ -1997,6 +1989,106 @@ static int samldb_check_user_account_control_invariants(struct samldb_ctx *ac, return ret; } +/* + * It would be best if these rules apply, always, but for now they + * apply only to non-admins + */ +static int samldb_check_user_account_control_objectclass_invariants( + struct samldb_ctx *ac, + uint32_t user_account_control, + uint32_t user_account_control_old, + bool is_computer_objectclass) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + + uint32_t old_ufa = user_account_control_old & UF_ACCOUNT_TYPE_MASK; + uint32_t new_ufa = user_account_control & UF_ACCOUNT_TYPE_MASK; + + uint32_t old_rodc = user_account_control_old & UF_PARTIAL_SECRETS_ACCOUNT; + uint32_t new_rodc = user_account_control & UF_PARTIAL_SECRETS_ACCOUNT; + + bool is_admin; + struct security_token *user_token + = acl_user_token(ac->module); + if (user_token == NULL) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + is_admin + = security_token_has_builtin_administrators(user_token); + + + /* + * We want to allow changes to (eg) disable an account + * that was created wrong, only checking the + * objectclass if the account type changes. + */ + if (old_ufa == new_ufa && old_rodc == new_rodc) { + return LDB_SUCCESS; + } + + switch (new_ufa) { + case UF_NORMAL_ACCOUNT: + if (is_computer_objectclass && !is_admin) { + ldb_asprintf_errstring(ldb, + "%08X: samldb: UF_NORMAL_ACCOUNT " + "requires objectclass 'user' not 'computer'!", + W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + break; + + case UF_INTERDOMAIN_TRUST_ACCOUNT: + if (is_computer_objectclass) { + ldb_asprintf_errstring(ldb, + "%08X: samldb: UF_INTERDOMAIN_TRUST_ACCOUNT " + "requires objectclass 'user' not 'computer'!", + W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + break; + + case UF_WORKSTATION_TRUST_ACCOUNT: + if (!is_computer_objectclass) { + /* + * Modify of a user account account into a + * workstation without objectclass computer + * as an admin is still permitted, but not + * to make an RODC + */ + if (is_admin + && ac->req->operation == LDB_MODIFY + && new_rodc == 0) { + break; + } + ldb_asprintf_errstring(ldb, + "%08X: samldb: UF_WORKSTATION_TRUST_ACCOUNT " + "requires objectclass 'computer'!", + W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + break; + + case UF_SERVER_TRUST_ACCOUNT: + if (!is_computer_objectclass) { + ldb_asprintf_errstring(ldb, + "%08X: samldb: UF_SERVER_TRUST_ACCOUNT " + "requires objectclass 'computer'!", + W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + break; + + default: + ldb_asprintf_errstring(ldb, + "%08X: samldb: invalid userAccountControl[0x%08X]", + W_ERROR_V(WERR_INVALID_PARAMETER), + user_account_control); + return LDB_ERR_OTHER; + } + return LDB_SUCCESS; +} + static int samldb_get_domain_secdesc(struct samldb_ctx *ac, struct security_descriptor **domain_sd) { @@ -2196,7 +2288,8 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac, struct dom_sid *sid, uint32_t req_uac, uint32_t user_account_control, - uint32_t user_account_control_old) + uint32_t user_account_control_old, + bool is_computer_objectclass) { int ret; struct dsdb_control_password_user_account_control *uac = NULL; @@ -2205,6 +2298,14 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac, if (ret != LDB_SUCCESS) { return ret; } + ret = samldb_check_user_account_control_objectclass_invariants(ac, + user_account_control, + user_account_control_old, + is_computer_objectclass); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samldb_check_user_account_control_acl(ac, sid, user_account_control, user_account_control_old); if (ret != LDB_SUCCESS) { return ret; @@ -2266,7 +2367,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) "objectSid", NULL }; - bool is_computer = false; + bool is_computer_objectclass = false; bool old_is_critical = false; bool new_is_critical = false; @@ -2321,7 +2422,10 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) "lockoutTime", 0); old_is_critical = ldb_msg_find_attr_as_bool(res->msgs[0], "isCriticalSystemObject", 0); - /* When we do not have objectclass "computer" we cannot switch to a (read-only) DC */ + /* + * When we do not have objectclass "computer" we cannot + * switch to a workstation or (RO)DC + */ el = ldb_msg_find_element(res->msgs[0], "objectClass"); if (el == NULL) { return ldb_operr(ldb); @@ -2329,7 +2433,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) computer_val = data_blob_string_const("computer"); val = ldb_msg_find_val(el, &computer_val); if (val != NULL) { - is_computer = true; + is_computer_objectclass = true; } old_ufa = old_uac & UF_ACCOUNT_TYPE_MASK; @@ -2354,7 +2458,8 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) ret = samldb_check_user_account_control_rules(ac, sid, raw_uac, new_uac, - old_uac); + old_uac, + is_computer_objectclass); if (ret != LDB_SUCCESS) { return ret; } @@ -2376,25 +2481,11 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) case UF_WORKSTATION_TRUST_ACCOUNT: new_is_critical = false; if (new_uac & UF_PARTIAL_SECRETS_ACCOUNT) { - if (!is_computer) { - ldb_asprintf_errstring(ldb, - "%08X: samldb: UF_PARTIAL_SECRETS_ACCOUNT " - "requires objectclass 'computer'!", - W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } new_is_critical = true; } break; case UF_SERVER_TRUST_ACCOUNT: - if (!is_computer) { - ldb_asprintf_errstring(ldb, - "%08X: samldb: UF_SERVER_TRUST_ACCOUNT " - "requires objectclass 'computer'!", - W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } new_is_critical = true; break; -- 2.25.1 From 5af6e1b4b4797fcb78837e88b304d68a95a07b6b Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 28 Oct 2021 14:47:30 +1300 Subject: [PATCH 047/224] CVE-2020-25722 selftest/priv_attrs: Mention that these knownfails are OK (for now) BUG: https://bugzilla.samba.org/show_bug.cgi?id=14775 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/priv_attr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index e0d6104cec9c..5d3713eafe39 100644 --- a/selftest/knownfail.d/priv_attr +++ b/selftest/knownfail.d/priv_attr @@ -1,3 +1,8 @@ +# These priv_attrs tests would be good to fix, but are not fatal as +# the testsuite is run twice, once with and once without STRICT_CHECKING=0 +# +# These knownfails show that we can improve our error matching against Windows. +# samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_computer samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_WP_user samba4.priv_attrs.strict.python\(.*\).__main__.PrivAttrsTests.test_priv_attr_sidHistory_add_CC_default_computer -- 2.25.1 From cb6098dbe15b3462d7cd0afb9fea1346e5d294d6 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 16:18:51 +1300 Subject: [PATCH 048/224] CVE-2020-25722 selftest: Adapt selftest to restriction on swapping account types This makes many of our tests pass again. We do not pass against Windows 2019 on all as this does not have this restriction at this time. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 29 +--------- .../dsdb/tests/python/user_account_control.py | 54 +++++++++++++------ 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 040c4eb219d7..32d8a99f950c 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -27,31 +27,7 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x10000000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x20000000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x40000000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_0x80000000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00000004\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00000400\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00004000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_00008000\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_ACCOUNTDISABLE\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_DONT_EXPIRE_PASSWD\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_DONT_REQUIRE_PREAUTH\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_HOMEDIR_REQUIRED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_LOCKOUT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_MNS_LOGON_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NORMAL_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NOT_DELEGATED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_NO_AUTH_DATA_REQUIRED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWD_CANT_CHANGE\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWD_NOTREQD\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_PASSWORD_EXPIRED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SCRIPT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SMARTCARD_REQUIRED\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_AES_KEYS\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_USE_DES_KEY_ONLY\(ad_dc_default\) +^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SERVER_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) @@ -62,6 +38,3 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT_UF_PASSWD_NOTREQD\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_TRUSTED_FOR_DELEGATION\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 1633998ada42..7a7cfd40b72e 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -219,6 +219,23 @@ class UserAccountControlTests(samba.tests.TestCase): print("Adding computer account %s" % computername) samdb.add(msg) + def add_user_ldap(self, username, others=None, samdb=None): + if samdb is None: + samdb = self.samdb + dn = "CN=%s,%s" % (username, self.OU) + samaccountname = "%s" % username + msg_dict = { + "dn": dn, + "objectclass": "user"} + if others is not None: + msg_dict = dict(list(msg_dict.items()) + list(others.items())) + + msg = ldb.Message.from_dict(self.samdb, msg_dict) + msg["sAMAccountName"] = samaccountname + + print("Adding user account %s" % username) + samdb.add(msg) + def get_creds(self, target_username, target_password): creds_tmp = Credentials() creds_tmp.set_username(target_username) @@ -532,17 +549,21 @@ class UserAccountControlTests(samba.tests.TestCase): def _test_uac_bits_set_with_args(self, bit, bit_str): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) - mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) + # Allow the creation of any children and write to any + # attributes (this is not a test of ACLs, this is a test of + # non-ACL userAccountControl rules + mod = f"(OA;CI;WP;;;{user_sid})(OA;;CC;;;{user_sid})" old_sd = self.sd_utils.read_sd_on_dn(self.OU) self.sd_utils.dacl_add_ace(self.OU, mod) + # We want to start with UF_NORMAL_ACCOUNT, so we make a user computername = self.computernames[0] - self.add_computer_ldap(computername) + self.add_user_ldap(computername) res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + expression="(&(objectClass=user)(cn=%s))" % computername, scope=SCOPE_SUBTREE, attrs=[]) @@ -588,7 +609,11 @@ class UserAccountControlTests(samba.tests.TestCase): def _test_uac_bits_unrelated_modify_with_args(self, account_type): user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) - mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) + + # Allow the creation of any children and write to any + # attributes (this is not a test of ACLs, this is a test of + # non-ACL userAccountControl rules + mod = f"(OA;CI;WP;;;{user_sid})(OA;;CC;;;{user_sid})" old_sd = self.sd_utils.read_sd_on_dn(self.OU) @@ -596,22 +621,19 @@ class UserAccountControlTests(samba.tests.TestCase): computername = self.computernames[0] if account_type == UF_WORKSTATION_TRUST_ACCOUNT: - self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]}) - else: self.add_computer_ldap(computername) + else: + self.add_user_ldap(computername) res = self.admin_samdb.search(self.OU, - expression=f"(cn={computername})", + expression=f"(&(objectclass=user)(cn={computername}))", scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) self.assertEqual(len(res), 1) orig_uac = int(res[0]["userAccountControl"][0]) - if account_type == UF_WORKSTATION_TRUST_ACCOUNT: - self.assertEqual(orig_uac, account_type) - else: - self.assertEqual(orig_uac & UF_NORMAL_ACCOUNT, - account_type) + self.assertEqual(orig_uac & account_type, + account_type) m = ldb.Message() m.dn = res[0].dn @@ -649,7 +671,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.fail(f"got {estr} resetting userAccountControl to initial value {orig_uac:#08x}") res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + expression="(&(objectClass=user)(cn=%s))" % computername, scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) @@ -696,7 +718,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + expression="(&(objectClass=user)(cn=%s))" % computername, scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) @@ -726,7 +748,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + expression="(&(objectClass=user)(cn=%s))" % computername, scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) @@ -767,7 +789,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) res = self.admin_samdb.search("%s" % self.base_dn, - expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, + expression="(&(objectClass=user)(cn=%s))" % computername, scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) -- 2.25.1 From 8cc63130bffb72f535716538564bb53869db3e82 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 22 Sep 2021 11:28:05 +1200 Subject: [PATCH 049/224] CVE-2020-25722 dsdb: samldb_objectclass_trigger() is only called on ADD, so remove indentation This makes the code less indented and simpler to understand. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- source4/dsdb/samdb/ldb_modules/samldb.c | 187 ++++++++++++------------ 1 file changed, 93 insertions(+), 94 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index c46aa13cf8a1..fbf3365a416e 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1374,9 +1374,9 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac, /* * "Objectclass" trigger (MS-SAMR 3.1.1.8.1) * - * Has to be invoked on "add" and "modify" operations on "user", "computer" and + * Has to be invoked on "add" operations on "user", "computer" and * "group" objects. - * ac->msg contains the "add"/"modify" message + * ac->msg contains the "add" * ac->type contains the object type (main objectclass) */ static int samldb_objectclass_trigger(struct samldb_ctx *ac) @@ -1417,6 +1417,8 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) switch(ac->type) { case SAMLDB_TYPE_USER: { + uint32_t raw_uac; + uint32_t user_account_control; bool is_computer_objectclass; bool uac_generated = false, uac_add_flags = false; uint32_t default_user_account_control = UF_NORMAL_ACCOUNT; @@ -1440,7 +1442,7 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) /* On add operations we might need to generate a * "userAccountControl" (if it isn't specified). */ el = ldb_msg_find_element(ac->msg, "userAccountControl"); - if ((el == NULL) && (ac->req->operation == LDB_ADD)) { + if (el == NULL) { ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg, "userAccountControl", default_user_account_control); @@ -1452,114 +1454,111 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) } el = ldb_msg_find_element(ac->msg, "userAccountControl"); - if (el != NULL) { - uint32_t raw_uac; - uint32_t user_account_control; - /* Step 1.3: "userAccountControl" -> "sAMAccountType" mapping */ - user_account_control = ldb_msg_find_attr_as_uint(ac->msg, - "userAccountControl", - 0); - raw_uac = user_account_control; - /* - * "userAccountControl" = 0 or missing one of - * the types means "UF_NORMAL_ACCOUNT" - * or "UF_WORKSTATION_TRUST_ACCOUNT" (if a computer). - * See MS-SAMR 3.1.1.8.10 point 8 - */ - if ((user_account_control & UF_ACCOUNT_TYPE_MASK) == 0) { - user_account_control - = default_user_account_control - | user_account_control; - uac_generated = true; - } + SMB_ASSERT(el != NULL); - /* - * As per MS-SAMR 3.1.1.8.10 these flags have not to be set - */ - if ((user_account_control & UF_LOCKOUT) != 0) { - user_account_control &= ~UF_LOCKOUT; - uac_generated = true; - } - if ((user_account_control & UF_PASSWORD_EXPIRED) != 0) { - user_account_control &= ~UF_PASSWORD_EXPIRED; - uac_generated = true; - } + /* Step 1.3: "userAccountControl" -> "sAMAccountType" mapping */ + user_account_control = ldb_msg_find_attr_as_uint(ac->msg, + "userAccountControl", + 0); + raw_uac = user_account_control; + /* + * "userAccountControl" = 0 or missing one of + * the types means "UF_NORMAL_ACCOUNT" + * or "UF_WORKSTATION_TRUST_ACCOUNT" (if a computer). + * See MS-SAMR 3.1.1.8.10 point 8 + */ + if ((user_account_control & UF_ACCOUNT_TYPE_MASK) == 0) { + user_account_control + = default_user_account_control + | user_account_control; + uac_generated = true; + } + + /* + * As per MS-SAMR 3.1.1.8.10 these flags have not to be set + */ + if ((user_account_control & UF_LOCKOUT) != 0) { + user_account_control &= ~UF_LOCKOUT; + uac_generated = true; + } + if ((user_account_control & UF_PASSWORD_EXPIRED) != 0) { + user_account_control &= ~UF_PASSWORD_EXPIRED; + uac_generated = true; + } - ret = samldb_check_user_account_control_rules(ac, NULL, - raw_uac, - user_account_control, - 0, - is_computer_objectclass); + ret = samldb_check_user_account_control_rules(ac, NULL, + raw_uac, + user_account_control, + 0, + is_computer_objectclass); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* add "sAMAccountType" attribute */ + ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* "isCriticalSystemObject" might be set */ + if (user_account_control & + (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { + ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", + "TRUE"); if (ret != LDB_SUCCESS) { return ret; } - - /* add "sAMAccountType" attribute */ - ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL); + el2 = ldb_msg_find_element(ac->msg, + "isCriticalSystemObject"); + el2->flags = LDB_FLAG_MOD_REPLACE; + } else if (user_account_control & UF_WORKSTATION_TRUST_ACCOUNT) { + ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", + "FALSE"); if (ret != LDB_SUCCESS) { return ret; } + el2 = ldb_msg_find_element(ac->msg, + "isCriticalSystemObject"); + el2->flags = LDB_FLAG_MOD_REPLACE; + } - /* "isCriticalSystemObject" might be set */ - if (user_account_control & - (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { - ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", - "TRUE"); - if (ret != LDB_SUCCESS) { - return ret; - } - el2 = ldb_msg_find_element(ac->msg, - "isCriticalSystemObject"); - el2->flags = LDB_FLAG_MOD_REPLACE; - } else if (user_account_control & UF_WORKSTATION_TRUST_ACCOUNT) { - ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", - "FALSE"); - if (ret != LDB_SUCCESS) { - return ret; - } - el2 = ldb_msg_find_element(ac->msg, - "isCriticalSystemObject"); - el2->flags = LDB_FLAG_MOD_REPLACE; - } - - /* Step 1.4: "userAccountControl" -> "primaryGroupID" mapping */ - if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) { - uint32_t rid; + /* Step 1.4: "userAccountControl" -> "primaryGroupID" mapping */ + if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) { + uint32_t rid; - ret = dsdb_user_obj_set_primary_group_id(ldb, ac->msg, user_account_control, &rid); + ret = dsdb_user_obj_set_primary_group_id(ldb, ac->msg, user_account_control, &rid); + if (ret != LDB_SUCCESS) { + return ret; + } + /* + * Older AD deployments don't know about the + * RODC group + */ + if (rid == DOMAIN_RID_READONLY_DCS) { + ret = samldb_prim_group_tester(ac, rid); if (ret != LDB_SUCCESS) { return ret; } - /* - * Older AD deployments don't know about the - * RODC group - */ - if (rid == DOMAIN_RID_READONLY_DCS) { - ret = samldb_prim_group_tester(ac, rid); - if (ret != LDB_SUCCESS) { - return ret; - } - } } + } - /* Step 1.5: Add additional flags when needed */ - /* Obviously this is done when the "userAccountControl" - * has been generated here (tested against Windows - * Server) */ - if (uac_generated) { - if (uac_add_flags) { - user_account_control |= UF_ACCOUNTDISABLE; - user_account_control |= UF_PASSWD_NOTREQD; - } - - ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg, - "userAccountControl", - user_account_control); - if (ret != LDB_SUCCESS) { - return ret; - } + /* Step 1.5: Add additional flags when needed */ + /* Obviously this is done when the "userAccountControl" + * has been generated here (tested against Windows + * Server) */ + if (uac_generated) { + if (uac_add_flags) { + user_account_control |= UF_ACCOUNTDISABLE; + user_account_control |= UF_PASSWD_NOTREQD; } + ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg, + "userAccountControl", + user_account_control); + if (ret != LDB_SUCCESS) { + return ret; + } } break; } -- 2.25.1 From 9646496c7584e1b926b625a95dc4061a22bf4491 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 22 Sep 2021 11:29:02 +1200 Subject: [PATCH 050/224] CVE-2020-25722 dsdb: Add restrictions on computer accounts without a trailing $ BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_dollar_lock | 1 - selftest/knownfail.d/uac_mod_lock | 12 -- source4/dsdb/samdb/ldb_modules/samldb.c | 171 +++++++++++++++++++++--- 3 files changed, 154 insertions(+), 30 deletions(-) delete mode 100644 selftest/knownfail.d/uac_dollar_lock diff --git a/selftest/knownfail.d/uac_dollar_lock b/selftest/knownfail.d/uac_dollar_lock deleted file mode 100644 index 8c70c859fa4a..000000000000 --- a/selftest/knownfail.d/uac_dollar_lock +++ /dev/null @@ -1 +0,0 @@ -^samba4.user_account_control.python\(.*\).__main__.UserAccountControlTests.test_objectclass_uac_dollar_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_cc_plain \ No newline at end of file diff --git a/selftest/knownfail.d/uac_mod_lock b/selftest/knownfail.d/uac_mod_lock index a84a236c355b..068a47c6e616 100644 --- a/selftest/knownfail.d/uac_mod_lock +++ b/selftest/knownfail.d/uac_mod_lock @@ -20,21 +20,9 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_None_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_SERVER_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_None_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_SERVER_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_None_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index fbf3365a416e..c953985ebbc3 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -71,6 +71,13 @@ struct samldb_ctx { /* used for add operations */ enum samldb_add_type type; + /* + * should we apply the need_trailing_dollar restriction to + * samAccountName + */ + + bool need_trailing_dollar; + /* the resulting message */ struct ldb_message *msg; @@ -235,12 +242,86 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr, static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac) { - int ret = samldb_unique_attr_check(ac, "samAccountName", NULL, - ldb_get_default_basedn( - ldb_module_get_ctx(ac->module))); - if (ret == LDB_ERR_OBJECT_CLASS_VIOLATION) { + int ret = 0; + bool is_admin; + struct security_token *user_token = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + struct ldb_message_element *el = dsdb_get_single_valued_attr(ac->msg, "samAccountName", + ac->req->operation); + if (el == NULL || el->num_values == 0) { + ldb_asprintf_errstring(ldb, + "%08X: samldb: 'samAccountName' can't be deleted/empty!", + W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION)); + if (ac->req->operation == LDB_ADD) { + return LDB_ERR_CONSTRAINT_VIOLATION; + } else { + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + + ret = samldb_unique_attr_check(ac, "samAccountName", NULL, + ldb_get_default_basedn( + ldb_module_get_ctx(ac->module))); + + /* + * Error code munging to try and match what must be some quite + * strange code-paths in Windows + */ + if (ret == LDB_ERR_CONSTRAINT_VIOLATION + && ac->req->operation == LDB_MODIFY) { + ret = LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS; + } else if (ret == LDB_ERR_OBJECT_CLASS_VIOLATION) { ret = LDB_ERR_CONSTRAINT_VIOLATION; } + + if (ret != LDB_SUCCESS) { + return ret; + } + + if (!ac->need_trailing_dollar) { + return LDB_SUCCESS; + } + + /* This does not permit a single $ */ + if (el->values[0].length < 2) { + ldb_asprintf_errstring(ldb, + "%08X: samldb: 'samAccountName' " + "can't just be one character!", + W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + user_token = acl_user_token(ac->module); + if (user_token == NULL) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + + is_admin + = security_token_has_builtin_administrators(user_token); + + if (is_admin) { + /* + * Administrators are allowed to select strange names. + * This is poor practice but not prevented. + */ + return false; + } + + if (el->values[0].data[el->values[0].length - 1] != '$') { + ldb_asprintf_errstring(ldb, + "%08X: samldb: 'samAccountName' " + "must have a trailing $!", + W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + if (el->values[0].data[el->values[0].length - 2] == '$') { + ldb_asprintf_errstring(ldb, + "%08X: samldb: 'samAccountName' " + "must not have a double trailing $!", + W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + return ret; } @@ -557,17 +638,31 @@ static int samldb_schema_add_handle_mapiid(struct samldb_ctx *ac) } /* sAMAccountName handling */ -static int samldb_generate_sAMAccountName(struct ldb_context *ldb, +static int samldb_generate_sAMAccountName(struct samldb_ctx *ac, struct ldb_message *msg) { + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); char *name; - /* Format: $000000-000000000000 */ + /* + * This is currently a Samba-only behaviour, to add a trailing + * $ even for the generated accounts. + */ + + if (ac->need_trailing_dollar) { + /* Format: $000000-00000000000$ */ + name = talloc_asprintf(msg, "$%.6X-%.6X%.5X$", + (unsigned int)generate_random(), + (unsigned int)generate_random(), + (unsigned int)generate_random()); + } else { + /* Format: $000000-000000000000 */ - name = talloc_asprintf(msg, "$%.6X-%.6X%.6X", - (unsigned int)generate_random(), - (unsigned int)generate_random(), - (unsigned int)generate_random()); + name = talloc_asprintf(msg, "$%.6X-%.6X%.6X", + (unsigned int)generate_random(), + (unsigned int)generate_random(), + (unsigned int)generate_random()); + } if (name == NULL) { return ldb_oom(ldb); } @@ -576,11 +671,10 @@ static int samldb_generate_sAMAccountName(struct ldb_context *ldb, static int samldb_check_sAMAccountName(struct samldb_ctx *ac) { - struct ldb_context *ldb = ldb_module_get_ctx(ac->module); int ret; if (ldb_msg_find_element(ac->msg, "sAMAccountName") == NULL) { - ret = samldb_generate_sAMAccountName(ldb, ac->msg); + ret = samldb_generate_sAMAccountName(ac, ac->msg); if (ret != LDB_SUCCESS) { return ret; } @@ -1495,6 +1589,20 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) return ret; } + /* + * Require, for non-admin modifications, a trailing $ + * for either objectclass=computer or a trust account + * type in userAccountControl + */ + if ((user_account_control + & UF_TRUST_ACCOUNT_MASK) != 0) { + ac->need_trailing_dollar = true; + } + + if (is_computer_objectclass) { + ac->need_trailing_dollar = true; + } + /* add "sAMAccountType" attribute */ ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL); if (ret != LDB_SUCCESS) { @@ -4010,12 +4118,41 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) el = ldb_msg_find_element(ac->msg, "sAMAccountName"); if (el != NULL) { + uint32_t user_account_control; + struct ldb_result *res = NULL; + const char * const attrs[] = { "userAccountControl", + "objectclass", + NULL }; + ret = dsdb_module_search_dn(ac->module, + ac, + &res, + ac->msg->dn, + attrs, + DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, + ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + + user_account_control + = ldb_msg_find_attr_as_uint(res->msgs[0], + "userAccountControl", + 0); + + if ((user_account_control + & UF_TRUST_ACCOUNT_MASK) != 0) { + ac->need_trailing_dollar = true; + + } else if (samdb_find_attribute(ldb, + res->msgs[0], + "objectclass", + "computer") + != NULL) { + ac->need_trailing_dollar = true; + } + ret = samldb_sam_accountname_valid_check(ac); - /* - * Other errors are checked for elsewhere, we just - * want to prevent duplicates - */ - if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + if (ret != LDB_SUCCESS) { return ret; } } -- 2.25.1 From 443c0c38dcdd50410966265e7d6fb755ae0e1f19 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 11:57:22 +1300 Subject: [PATCH 051/224] CVE-2020-25722 selftest: Adapt sam.py test_isCriticalSystemObject to new UF_WORKSTATION_TRUST_ACCOUNT default Objects with objectclass computer now have UF_WORKSTATION_TRUST_ACCOUNT by default and so this test must adapt. The changes to this test passes against Windows 2019 except for the new behaviour around the UF_WORKSTATION_TRUST_ACCOUNT default. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- .../knownfail.d/sam-isCriticalSystemObject | 1 + selftest/knownfail.d/uac_objectclass_restrict | 2 -- source4/dsdb/tests/python/sam.py | 36 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 selftest/knownfail.d/sam-isCriticalSystemObject diff --git a/selftest/knownfail.d/sam-isCriticalSystemObject b/selftest/knownfail.d/sam-isCriticalSystemObject new file mode 100644 index 000000000000..a6351a81907b --- /dev/null +++ b/selftest/knownfail.d/sam-isCriticalSystemObject @@ -0,0 +1 @@ +^samba4.sam.python\(.*\).__main__.SamTests.test_isCriticalSystemObject_user \ No newline at end of file diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 32d8a99f950c..d093c631bd37 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -3,11 +3,9 @@ # # All these tests need to be fixed and the entries here removed -^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_isCriticalSystemObject\(fl2008r2dc\) ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_userAccountControl\(fl2008r2dc\) ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) ^samba4.ldap.python\(ad_dc_default\).__main__.BasicTests.test_all\(ad_dc_default\) -^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_isCriticalSystemObject\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_userAccountControl\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_WP_user\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 41c348bce5f1..42eed194eced 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2926,6 +2926,39 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_isCriticalSystemObject_user(self): + """Test the isCriticalSystemObject behaviour""" + print("Testing isCriticalSystemObject behaviour\n") + + # Add tests (of a user) + + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": "user"}) + + res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertTrue("isCriticalSystemObject" not in res1[0]) + + # Modification tests + m = Message() + + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertTrue("isCriticalSystemObject" in res1[0]) + self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "FALSE") + + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_isCriticalSystemObject(self): """Test the isCriticalSystemObject behaviour""" print("Testing isCriticalSystemObject behaviour\n") @@ -2940,7 +2973,8 @@ class SamTests(samba.tests.TestCase): scope=SCOPE_BASE, attrs=["isCriticalSystemObject"]) self.assertTrue(len(res1) == 1) - self.assertTrue("isCriticalSystemObject" not in res1[0]) + self.assertTrue("isCriticalSystemObject" in res1[0]) + self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "FALSE") delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) -- 2.25.1 From 2ac0705f9649d7e5b17c53c1e6cd4dfd320447bf Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 13:02:42 +1300 Subject: [PATCH 052/224] CVE-2020-25722 samdb: Fill in isCriticalSystemObject on any account type change BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/sam-isCriticalSystemObject | 1 - source4/dsdb/samdb/ldb_modules/samldb.c | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 selftest/knownfail.d/sam-isCriticalSystemObject diff --git a/selftest/knownfail.d/sam-isCriticalSystemObject b/selftest/knownfail.d/sam-isCriticalSystemObject deleted file mode 100644 index a6351a81907b..000000000000 --- a/selftest/knownfail.d/sam-isCriticalSystemObject +++ /dev/null @@ -1 +0,0 @@ -^samba4.sam.python\(.*\).__main__.SamTests.test_isCriticalSystemObject_user \ No newline at end of file diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index c953985ebbc3..ad12ee6dba3b 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2626,8 +2626,14 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) el->flags = LDB_FLAG_MOD_REPLACE; } - /* "isCriticalSystemObject" might be set/changed */ - if (old_is_critical != new_is_critical) { + /* + * "isCriticalSystemObject" might be set/changed + * + * Even a change from UF_NORMAL_ACCOUNT (implicitly FALSE) to + * UF_WORKSTATION_TRUST_ACCOUNT (actually FALSE) triggers + * creating the attribute. + */ + if (old_is_critical != new_is_critical || old_atype != new_atype) { ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", new_is_critical ? "TRUE": "FALSE"); if (ret != LDB_SUCCESS) { -- 2.25.1 From cf36f1636368853be2c826f770bccca2ef010ab8 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 14:03:05 +1300 Subject: [PATCH 053/224] CVE-2020-25722 selftest: Split test_userAccountControl into unit tests The parts that create and delete a single object can be safely split out into an individual test. At this point the parts that fail against Windows 2019 are: error: __main__.SamTests.test_userAccountControl_computer_add_normal [ _ldb.LdbError: (53, 'LDAP error 53 LDAP_UNWILLING_TO_PERFORM - <0000052D: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n> <>') error: __main__.SamTests.test_userAccountControl_computer_modify [ _ldb.LdbError: (53, 'LDAP error 53 LDAP_UNWILLING_TO_PERFORM - <0000052D: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n> <>') error: __main__.SamTests.test_userAccountControl_user_add_0_uac [ _ldb.LdbError: (53, 'LDAP error 53 LDAP_UNWILLING_TO_PERFORM - <0000052D: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n> <>') error: __main__.SamTests.test_userAccountControl_user_add_normal [ _ldb.LdbError: (53, 'LDAP error 53 LDAP_UNWILLING_TO_PERFORM - <0000052D: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n> <>') error: __main__.SamTests.test_userAccountControl_user_modify [ _ldb.LdbError: (53, 'LDAP error 53 LDAP_UNWILLING_TO_PERFORM - <0000052D: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n> <>') BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 6 ++++-- source4/dsdb/tests/python/sam.py | 21 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index d093c631bd37..ac7befffb1b4 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -3,10 +3,12 @@ # # All these tests need to be fixed and the entries here removed -^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_userAccountControl\(fl2008r2dc\) +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_add_0_uac +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_add_trust +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_modify ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) ^samba4.ldap.python\(ad_dc_default\).__main__.BasicTests.test_all\(ad_dc_default\) -^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_userAccountControl\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_WP_user\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_default_user\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 42eed194eced..8d8c352c7939 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -1885,7 +1885,7 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - def test_userAccountControl(self): + def test_userAccountControl_user_add_0_uac(self): """Test the userAccountControl behaviour""" print("Testing userAccountControl behaviour\n") @@ -1913,12 +1913,15 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_PASSWD_NOTREQD == 0) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_normal(self): + """Test the userAccountControl behaviour""" ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, "objectclass": "user", "userAccountControl": str(UF_NORMAL_ACCOUNT)}) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_normal_pwnotreq(self): ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, "objectclass": "user", @@ -1933,6 +1936,7 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_normal_pwnotreq_lockout_expired(self): ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, "objectclass": "user", @@ -1952,6 +1956,7 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_temp_dup(self): try: ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, @@ -1963,6 +1968,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OTHER) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_server(self): try: ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, @@ -1974,6 +1980,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_workstation(self): try: ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, @@ -1984,6 +1991,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_rodc(self): try: ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, @@ -1994,6 +2002,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + def test_userAccountControl_user_add_trust(self): try: ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, @@ -2007,6 +2016,7 @@ class SamTests(samba.tests.TestCase): # Modify operation + def test_userAccountControl_user_modify(self): ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, "objectclass": "user"}) @@ -2177,6 +2187,7 @@ class SamTests(samba.tests.TestCase): (num, _) = e69.args self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + def test_userAccountControl_computer_add_0_uac(self): # With a computer object # Add operation @@ -2201,12 +2212,14 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_PASSWD_NOTREQD == 0) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_normal(self): ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, "objectclass": "computer", "userAccountControl": str(UF_NORMAL_ACCOUNT)}) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_normal_pwnotreqd(self): ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, "objectclass": "computer", @@ -2221,6 +2234,7 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_normal_pwnotreqd_lockout_expired(self): ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, "objectclass": "computer", @@ -2240,6 +2254,7 @@ class SamTests(samba.tests.TestCase): self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_temp_dup(self): try: ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, @@ -2251,6 +2266,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OTHER) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_server(self): ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, "objectclass": "computer", @@ -2263,6 +2279,7 @@ class SamTests(samba.tests.TestCase): ATYPE_WORKSTATION_TRUST) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_workstation(self): try: ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, @@ -2273,6 +2290,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_add_trust(self): try: ldb.add({ "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, @@ -2284,6 +2302,7 @@ class SamTests(samba.tests.TestCase): self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_userAccountControl_computer_modify(self): # Modify operation ldb.add({ -- 2.25.1 From 814cf893d12ba65283c092b94b85f6c371ded426 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:06:14 +1300 Subject: [PATCH 054/224] CVE-2020-25722 selftest: Adjust sam.py test_userAccountControl_computer_add_trust to new reality We now enforce that a trust account must be a user. These can not be added over LDAP anyway, and our C code in the RPC server gets this right in any case. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 1 - source4/dsdb/tests/python/sam.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index ac7befffb1b4..0fc2f4d47a21 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -4,7 +4,6 @@ # All these tests need to be fixed and the entries here removed ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_add_0_uac -^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_add_trust ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_modify ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 8d8c352c7939..76970b3c6793 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2299,7 +2299,7 @@ class SamTests(samba.tests.TestCase): self.fail() except LdbError as e72: (num, _) = e72.args - self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) def test_userAccountControl_computer_modify(self): -- 2.25.1 From f4f8cff823b7742b1ebb45093f877d1a8227844b Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:14:28 +1300 Subject: [PATCH 055/224] CVE-2020-25722 selftest: New objects of objectclass=computer are workstations by default now BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 1 - source4/dsdb/tests/python/sam.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 0fc2f4d47a21..295818d6a1bf 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -3,7 +3,6 @@ # # All these tests need to be fixed and the entries here removed -^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_add_0_uac ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_modify ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 76970b3c6793..c8d02fd91ddf 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2207,7 +2207,7 @@ class SamTests(samba.tests.TestCase): attrs=["sAMAccountType", "userAccountControl"]) self.assertTrue(len(res1) == 1) self.assertEqual(int(res1[0]["sAMAccountType"][0]), - ATYPE_NORMAL_ACCOUNT) + ATYPE_WORKSTATION_TRUST) self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0) self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_PASSWD_NOTREQD == 0) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) @@ -2315,7 +2315,7 @@ class SamTests(samba.tests.TestCase): attrs=["sAMAccountType", "userAccountControl"]) self.assertTrue(len(res1) == 1) self.assertEqual(int(res1[0]["sAMAccountType"][0]), - ATYPE_NORMAL_ACCOUNT) + ATYPE_WORKSTATION_TRUST) self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE != 0) # As computer you can switch from a normal account to a workstation -- 2.25.1 From 2580aaaf2c574a0af11c96c486be3a7d801d494a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:19:19 +1300 Subject: [PATCH 056/224] CVE-2020-25722 selftest: Adapt sam.py test to userAccountControl/objectclass restrictions BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 2 -- source4/dsdb/tests/python/sam.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 295818d6a1bf..0971c13c2f03 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -3,8 +3,6 @@ # # All these tests need to be fixed and the entries here removed -^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify -^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_modify ^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) ^samba4.ldap.python\(ad_dc_default\).__main__.BasicTests.test_all\(ad_dc_default\) ^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index c8d02fd91ddf..8a44889b63f2 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2135,7 +2135,7 @@ class SamTests(samba.tests.TestCase): self.fail() except LdbError as e67: (num, _) = e67.args - self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) m = Message() m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) @@ -2154,7 +2154,7 @@ class SamTests(samba.tests.TestCase): self.fail() except LdbError as e68: (num, _) = e68.args - self.assertEqual(num, ERR_UNWILLING_TO_PERFORM) + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["sAMAccountType"]) @@ -2502,7 +2502,7 @@ class SamTests(samba.tests.TestCase): self.fail() except LdbError as e76: (num, _) = e76.args - self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) # "primaryGroupID" does not change if account type remains the same -- 2.25.1 From da64c74941aab20c6a52486969f7bb8cc78b762c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:42:46 +1300 Subject: [PATCH 057/224] CVE-2020-25722 selftest: adapt ldap.py/sam.py test_all tests to new default computer behaviour Objects of objectclass computer are computers by default now and this changes the sAMAccountType and primaryGroupID as well as userAccountControl BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 3 --- source4/dsdb/tests/python/ldap.py | 13 +++++++------ source4/dsdb/tests/python/sam.py | 4 +++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 0971c13c2f03..7328ca17d808 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -3,9 +3,6 @@ # # All these tests need to be fixed and the entries here removed -^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_users_groups\(fl2008r2dc\) -^samba4.ldap.python\(ad_dc_default\).__main__.BasicTests.test_all\(ad_dc_default\) -^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_users_groups\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_WP_user\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-DC_add_CC_default_user\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-a2d-user_add_CC_WP_computer\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/ldap.py b/source4/dsdb/tests/python/ldap.py index 9c836c04267f..161c2f8b3c22 100755 --- a/source4/dsdb/tests/python/ldap.py +++ b/source4/dsdb/tests/python/ldap.py @@ -48,6 +48,7 @@ from samba.dsdb import (UF_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST, SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE, SYSTEM_FLAG_CONFIG_ALLOW_RENAME, SYSTEM_FLAG_CONFIG_ALLOW_MOVE, SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE) +from samba.dcerpc.security import DOMAIN_RID_DOMAIN_MEMBERS from samba.ndr import ndr_pack, ndr_unpack from samba.dcerpc import security, lsa @@ -2018,9 +2019,9 @@ delete: description self.assertTrue("objectGUID" in res[0]) self.assertTrue("whenCreated" in res[0]) self.assertEqual(str(res[0]["objectCategory"][0]), ("CN=Computer,%s" % ldb.get_schema_basedn())) - self.assertEqual(int(res[0]["primaryGroupID"][0]), 513) - self.assertEqual(int(res[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT) - self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) + self.assertEqual(int(res[0]["primaryGroupID"][0]), DOMAIN_RID_DOMAIN_MEMBERS) + self.assertEqual(int(res[0]["sAMAccountType"][0]), ATYPE_WORKSTATION_TRUST) + self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) delete_force(self.ldb, "cn=ldaptestcomputer3,cn=computers," + self.base_dn) @@ -2499,9 +2500,9 @@ member: cn=ldaptestuser2,cn=users,""" + self.base_dn + """ self.assertTrue("objectGUID" in res[0]) self.assertTrue("whenCreated" in res[0]) self.assertEqual(str(res[0]["objectCategory"]), ("CN=Computer,%s" % ldb.get_schema_basedn())) - self.assertEqual(int(res[0]["primaryGroupID"][0]), 513) - self.assertEqual(int(res[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT) - self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) + self.assertEqual(int(res[0]["primaryGroupID"][0]), DOMAIN_RID_DOMAIN_MEMBERS) + self.assertEqual(int(res[0]["sAMAccountType"][0]), ATYPE_WORKSTATION_TRUST) + self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) self.assertEqual(str(res[0]["memberOf"][0]).upper(), ("CN=ldaptestgroup2,CN=Users," + self.base_dn).upper()) self.assertEqual(len(res[0]["memberOf"]), 1) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 8a44889b63f2..cff9b4345f4e 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -291,7 +291,9 @@ class SamTests(samba.tests.TestCase): ldb.add({ "dn": "cn=ldaptestuser,cn=users," + self.base_dn, - "objectclass": "computer"}) + "objectclass": "computer", + "userAccountControl": str(UF_NORMAL_ACCOUNT | + UF_PASSWD_NOTREQD)}) res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["primaryGroupID"]) -- 2.25.1 From 9aef36b0003e616c8ac5b2f72dc0d045adeae652 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 22:40:06 +1300 Subject: [PATCH 058/224] CVE-2020-25722 selftest: Allow self.assertRaisesLdbError() to take a list of errors to match with BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- python/samba/tests/__init__.py | 30 ++++++++++++------- selftest/knownfail.d/uac_objectclass_restrict | 2 -- .../dsdb/tests/python/user_account_control.py | 5 ++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index a33fb659283c..60c6329c4eb3 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -21,6 +21,7 @@ from __future__ import print_function import os import tempfile import warnings +import collections import ldb import samba from samba import param @@ -196,23 +197,32 @@ class TestCase(unittest.TestCase): f(*args, **kwargs) except ldb.LdbError as e: (num, msg) = e.args - if num != errcode: + if isinstance(errcode, collections.abc.Container): + found = num in errcode + else: + found = num == errcode + if not found: lut = {v: k for k, v in vars(ldb).items() if k.startswith('ERR_') and isinstance(v, int)} - self.fail("%s, expected " - "LdbError %s, (%d) " - "got %s (%d) " - "%s" % (message, - lut.get(errcode), errcode, - lut.get(num), num, - msg)) + if isinstance(errcode, collections.abc.Container): + errcode_name = ' '.join(lut.get(x) for x in errcode) + else: + errcode_name = lut.get(errcode) + self.fail(f"{message}, expected " + f"LdbError {errcode_name}, {errcode} " + f"got {lut.get(num)} ({num}) " + f"{msg}") else: lut = {v: k for k, v in vars(ldb).items() if k.startswith('ERR_') and isinstance(v, int)} + if isinstance(errcode, collections.abc.Container): + errcode_name = ' '.join(lut.get(x) for x in errcode) + else: + errcode_name = lut.get(errcode) self.fail("%s, expected " - "LdbError %s, (%d) " + "LdbError %s, (%s) " "but we got success" % (message, - lut.get(errcode), + errcode_name, errcode)) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 7328ca17d808..ac6f4857bf49 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -20,8 +20,6 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_SERVER_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_set_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_deladd_priv\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 7a7cfd40b72e..ed68a683e69f 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -594,6 +594,9 @@ class UserAccountControlTests(samba.tests.TestCase): if (bit in priv_bits): self.fail("Unexpectedly able to set userAccountControl bit 0x%08X (%s), on %s" % (bit, bit_str, m.dn)) + if (bit in account_types and bit != UF_NORMAL_ACCOUNT): + self.fail("Unexpectedly able to set userAccountControl bit 0x%08X (%s), on %s" + % (bit, bit_str, m.dn)) except LdbError as e: (enum, estr) = e.args if bit in invalid_bits: @@ -601,6 +604,8 @@ class UserAccountControlTests(samba.tests.TestCase): ldb.ERR_OTHER, "was not able to set 0x%08X (%s) on %s" % (bit, bit_str, m.dn)) + elif (bit in account_types): + self.assertIn(enum, [ldb.ERR_OBJECT_CLASS_VIOLATION, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS]) elif (bit in priv_bits): self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) else: -- 2.25.1 From ffe9732a1c253cd58fffd3ef3346959bc9cd573e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 22:54:52 +1300 Subject: [PATCH 059/224] CVE-2020-25722 selftest/user_account_control: Allow a broader set of possible errors This favors a test that confirms we got an error over getting exactly the right error, at least for now. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 4 ---- selftest/knownfail.d/user_account_control | 1 - source4/dsdb/tests/python/user_account_control.py | 12 ++++++++---- 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 selftest/knownfail.d/user_account_control diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index ac6f4857bf49..1d72442f8a8e 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -22,10 +22,6 @@ ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_deladd_priv\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_deladd_wp\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_replace_priv\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_uac_mod_lock_UF_NORMAL_ACCOUNT_UF_SERVER_TRUST_ACCOUNT_replace_wp\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT_UF_PASSWD_NOTREQD\(ad_dc_default\) diff --git a/selftest/knownfail.d/user_account_control b/selftest/knownfail.d/user_account_control deleted file mode 100644 index ad3af6787080..000000000000 --- a/selftest/knownfail.d/user_account_control +++ /dev/null @@ -1 +0,0 @@ -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_cc_normal_bare.ad_dc_default diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index ed68a683e69f..f99f370679b8 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -484,7 +484,8 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), ldb.FLAG_MOD_REPLACE, "userAccountControl") - self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + self.assertRaisesLdbError([ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_UNWILLING_TO_PERFORM], f"Unexpectedly able to set userAccountControl to be an Normal " "account without |UF_PASSWD_NOTREQD Unexpectedly able to " "set userAccountControl to be a workstation on {m.dn}", @@ -1204,12 +1205,14 @@ class UserAccountControlTests(samba.tests.TestCase): samdb.modify(m) elif (account_type == UF_NORMAL_ACCOUNT) and \ (account_type2 == UF_SERVER_TRUST_ACCOUNT) and not priv: - self.assertRaisesLdbError(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + self.assertRaisesLdbError([ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS], f"Should have been unable to change {account_type_str} to {account_type2_str}", samdb.modify, m) elif (account_type == UF_NORMAL_ACCOUNT) and \ (account_type2 == UF_SERVER_TRUST_ACCOUNT) and priv: - self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + self.assertRaisesLdbError([ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_UNWILLING_TO_PERFORM], f"Should have been unable to change {account_type_str} to {account_type2_str}", samdb.modify, m) elif (account_type == UF_WORKSTATION_TRUST_ACCOUNT) and \ @@ -1282,7 +1285,8 @@ class UserAccountControlTests(samba.tests.TestCase): m["1objectclass"] = ldb.MessageElement(new_objectclass, ldb.FLAG_MOD_ADD, "objectclass") - self.assertRaisesLdbError(ldb.ERR_UNWILLING_TO_PERFORM, + self.assertRaisesLdbError([ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_UNWILLING_TO_PERFORM], "Should have been unable Able to change objectclass of a {objectclass}", self.admin_samdb.modify, m) -- 2.25.1 From e52e6d29676caee375ce1d14faec9af1e5b472f8 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 23:41:23 +1300 Subject: [PATCH 060/224] CVE-2020-25722 selftest/user_account_control: more work to cope with UAC/objectclass defaults and lock This new restriction breaks a large number of assumptions in the tests, like that you can remove some UF_ flags, because it turns out doing so will make the 'computer' a 'user' again, and this will fail. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- selftest/knownfail.d/uac_objectclass_restrict | 6 --- .../dsdb/tests/python/user_account_control.py | 46 ++++++++++++------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index 1d72442f8a8e..c4d4507c833b 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -14,14 +14,8 @@ ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_mod-del-add_CC_default_computer\(ad_dc_default\) ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_mod-replace_CC_default_computer\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_sd_cc\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_admin_mod_uac\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_computer_cc\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_NORMAL_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_unrelated_modify_UF_WORKSTATION_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_INTERDOMAIN_TRUST_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_uac_bits_add_UF_NORMAL_ACCOUNT_UF_PASSWD_NOTREQD\(ad_dc_default\) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index f99f370679b8..6a0ef73b29ad 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -433,11 +433,10 @@ class UserAccountControlTests(samba.tests.TestCase): m.dn = res[0].dn m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD), ldb.FLAG_MOD_REPLACE, "userAccountControl") - try: - self.samdb.modify(m) - except LdbError as e: - (enum, estr) = e.args - self.fail(f"got {estr} setting userAccountControl to UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD") + + self.assertRaisesLdbError(ldb.ERR_OBJECT_CLASS_VIOLATION, + f"Unexpectedly able to set userAccountControl as to UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD on {m.dn}", + self.samdb.modify, m) m = ldb.Message() m.dn = res[0].dn @@ -501,7 +500,7 @@ class UserAccountControlTests(samba.tests.TestCase): scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) - self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_NORMAL_ACCOUNT | + self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_WORKSTATION_TRUST_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)) @@ -681,11 +680,9 @@ class UserAccountControlTests(samba.tests.TestCase): scope=SCOPE_SUBTREE, attrs=["userAccountControl"]) - if account_type == UF_WORKSTATION_TRUST_ACCOUNT: - self.assertEqual(orig_uac, account_type) - else: - self.assertEqual(orig_uac & UF_NORMAL_ACCOUNT, - account_type) + self.assertEqual(len(res), 1) + reset_uac = int(res[0]["userAccountControl"][0]) + self.assertEqual(orig_uac, reset_uac) m = ldb.Message() m.dn = res[0].dn @@ -704,14 +701,16 @@ class UserAccountControlTests(samba.tests.TestCase): # No point going on, try the next bit continue elif bit in super_priv_bits: - self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) + self.assertIn(enum, (ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, + ldb.ERR_OBJECT_CLASS_VIOLATION)) # No point going on, try the next bit continue elif (account_type == UF_NORMAL_ACCOUNT) \ and (bit in account_types) \ and (bit != account_type): - self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + self.assertIn(enum, (ldb.ERR_UNWILLING_TO_PERFORM, + ldb.ERR_OBJECT_CLASS_VIOLATION)) continue elif (account_type == UF_WORKSTATION_TRUST_ACCOUNT) \ @@ -789,7 +788,10 @@ class UserAccountControlTests(samba.tests.TestCase): except LdbError as e3: (enum, estr) = e3.args - if bit in priv_to_remove_bits: + if account_type == UF_WORKSTATION_TRUST_ACCOUNT: + # Because removing any bit would change the account back to a user, which is locked by objectclass + self.assertIn(enum, (ldb.ERR_OBJECT_CLASS_VIOLATION, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)) + elif bit in priv_to_remove_bits: self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) else: self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) @@ -808,7 +810,7 @@ class UserAccountControlTests(samba.tests.TestCase): self.assertEqual(int(res[0]["userAccountControl"][0]), bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, "bit 0X%08x should not have been removed" % bit) - else: + elif account_type != UF_WORKSTATION_TRUST_ACCOUNT: self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, "bit 0X%08x should have been removed" % bit) @@ -859,9 +861,19 @@ class UserAccountControlTests(samba.tests.TestCase): bit_str, computername)) elif bit in priv_bits: - self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) + if bit == UF_INTERDOMAIN_TRUST_ACCOUNT: + self.assertIn(enum, (ldb.ERR_OBJECT_CLASS_VIOLATION, + ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)) + else: + self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) elif bit in unwilling_bits: - self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + # This can fail early as user in a computer is not permitted as non-admin + self.assertIn(enum, (ldb.ERR_UNWILLING_TO_PERFORM, + ldb.ERR_OBJECT_CLASS_VIOLATION)) + elif bit & UF_NORMAL_ACCOUNT: + # This can fail early as user in a computer is not permitted as non-admin + self.assertIn(enum, (ldb.ERR_UNWILLING_TO_PERFORM, + ldb.ERR_OBJECT_CLASS_VIOLATION)) else: self.fail("Unable to set userAccountControl bit 0x%08X (%s) on %s: %s" % (bit, -- 2.25.1 From f08901ef62e7783df3d65c55300ba24fba3f8775 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 27 Sep 2021 11:20:19 +1300 Subject: [PATCH 061/224] CVE-2020-25721 krb5pac: Add new buffers for samAccountName and objectSID These appear when PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID is set. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14835 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- librpc/idl/krb5pac.idl | 18 ++++++++++++++++-- librpc/ndr/ndr_krb5pac.c | 4 ++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/librpc/idl/krb5pac.idl b/librpc/idl/krb5pac.idl index 515150ab9cdf..ed488dee4251 100644 --- a/librpc/idl/krb5pac.idl +++ b/librpc/idl/krb5pac.idl @@ -86,15 +86,29 @@ interface krb5pac } PAC_CONSTRAINED_DELEGATION; typedef [bitmap32bit] bitmap { - PAC_UPN_DNS_FLAG_CONSTRUCTED = 0x00000001 + PAC_UPN_DNS_FLAG_CONSTRUCTED = 0x00000001, + PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID = 0x00000002 } PAC_UPN_DNS_FLAGS; + typedef struct { + [value(2*strlen_m(samaccountname))] uint16 samaccountname_size; + [relative_short,subcontext(0),subcontext_size(samaccountname_size),flag(NDR_ALIGN8|STR_NOTERM|NDR_REMAINING)] string *samaccountname; + [value(ndr_size_dom_sid(objectsid, ndr->flags))] uint16 objectsid_size; + [relative_short,subcontext(0),subcontext_size(objectsid_size)] dom_sid *objectsid; + } PAC_UPN_DNS_INFO_SAM_NAME_AND_SID; + + typedef [nodiscriminant] union { + [case(PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID)] PAC_UPN_DNS_INFO_SAM_NAME_AND_SID sam_name_and_sid; + [default]; + } PAC_UPN_DNS_INFO_EX; + typedef struct { [value(2*strlen_m(upn_name))] uint16 upn_name_size; [relative_short,subcontext(0),subcontext_size(upn_name_size),flag(NDR_ALIGN8|STR_NOTERM|NDR_REMAINING)] string *upn_name; [value(2*strlen_m(dns_domain_name))] uint16 dns_domain_name_size; [relative_short,subcontext(0),subcontext_size(dns_domain_name_size),flag(NDR_ALIGN8|STR_NOTERM|NDR_REMAINING)] string *dns_domain_name; PAC_UPN_DNS_FLAGS flags; + [switch_is(flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID)] PAC_UPN_DNS_INFO_EX ex; } PAC_UPN_DNS_INFO; typedef [public] struct { @@ -142,7 +156,7 @@ interface krb5pac typedef [public,nopush,nopull] struct { PAC_TYPE type; - [value(_ndr_size_PAC_INFO(info, type, 0))] uint32 _ndr_size; + [value(_ndr_size_PAC_INFO(info, type, LIBNDR_FLAG_ALIGN8))] uint32 _ndr_size; /* * We need to have two subcontexts to get the padding right, * the outer subcontext uses NDR_ROUND(_ndr_size, 8), while diff --git a/librpc/ndr/ndr_krb5pac.c b/librpc/ndr/ndr_krb5pac.c index a9ae2c4a789c..57b28df9e528 100644 --- a/librpc/ndr/ndr_krb5pac.c +++ b/librpc/ndr/ndr_krb5pac.c @@ -41,7 +41,7 @@ enum ndr_err_code ndr_push_PAC_BUFFER(struct ndr_push *ndr, int ndr_flags, const if (ndr_flags & NDR_SCALARS) { NDR_CHECK(ndr_push_align(ndr, 4)); NDR_CHECK(ndr_push_PAC_TYPE(ndr, NDR_SCALARS, r->type)); - NDR_CHECK(ndr_push_uint32(ndr, NDR_SCALARS, _ndr_size_PAC_INFO(r->info,r->type,0))); + NDR_CHECK(ndr_push_uint32(ndr, NDR_SCALARS, _ndr_size_PAC_INFO(r->info,r->type,LIBNDR_FLAG_ALIGN8))); { uint32_t _flags_save_PAC_INFO = ndr->flags; ndr_set_flags(&ndr->flags, LIBNDR_FLAG_ALIGN8); @@ -59,7 +59,7 @@ enum ndr_err_code ndr_push_PAC_BUFFER(struct ndr_push *ndr, int ndr_flags, const { struct ndr_push *_ndr_info_pad; struct ndr_push *_ndr_info; - size_t _ndr_size = _ndr_size_PAC_INFO(r->info, r->type, 0); + size_t _ndr_size = _ndr_size_PAC_INFO(r->info, r->type, LIBNDR_FLAG_ALIGN8); NDR_CHECK(ndr_push_subcontext_start(ndr, &_ndr_info_pad, 0, NDR_ROUND(_ndr_size, 8))); NDR_CHECK(ndr_push_subcontext_start(_ndr_info_pad, &_ndr_info, 0, _ndr_size)); NDR_CHECK(ndr_push_set_switch_value(_ndr_info, r->info, r->type)); -- 2.25.1 From c126c4916b8a95f26b02e402b69c55b2261548d3 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 20 Oct 2021 15:48:35 +1300 Subject: [PATCH 062/224] CVE-2020-25718 tests/krb5: Allow tests accounts to replicate to RODC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/rodc_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/samba/tests/krb5/rodc_tests.py b/python/samba/tests/krb5/rodc_tests.py index 302ae865cf18..0e252d90262f 100755 --- a/python/samba/tests/krb5/rodc_tests.py +++ b/python/samba/tests/krb5/rodc_tests.py @@ -41,11 +41,13 @@ class RodcKerberosTests(KDCBaseTest): user_creds = self.get_cached_creds( account_type=self.AccountType.USER, opts={ + 'allowed_replication': True, 'revealed_to_rodc': True }) target_creds = self.get_cached_creds( account_type=self.AccountType.COMPUTER, opts={ + 'allowed_replication': True, 'revealed_to_rodc': True }) -- 2.25.1 From 82cb9266dae924338454d0b1ddeb1aa1d1b97c36 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 14:59:01 +1300 Subject: [PATCH 063/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Modify get_service_ticket() to use _generic_kdc_exchange() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 8ae9c24b0fc3..c129883e7cde 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1275,7 +1275,7 @@ class KDCBaseTest(RawKerberosTest): expected_flags=None, unexpected_flags=None, fresh=False): user_name = tgt.cname['name-string'][0] - target_name = target_creds.get_username() + target_name = target_creds.get_username()[:-1] cache_key = (user_name, target_name, service, to_rodc, kdc_options) if not fresh: @@ -1288,40 +1288,40 @@ class KDCBaseTest(RawKerberosTest): if kdc_options is None: kdc_options = '0' - kdc_options = krb5_asn1.KDCOptions(kdc_options) - - key = tgt.session_key - ticket = tgt.ticket + kdc_options = str(krb5_asn1.KDCOptions(kdc_options)) - cname = tgt.cname - realm = tgt.crealm - - target_name = target_creds.get_username()[:-1] sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[service, target_name]) + srealm = target_creds.get_realm() - rep, enc_part = self.tgs_req(cname, sname, realm, ticket, key, etype, - to_rodc=to_rodc, - service_creds=target_creds, - kdc_options=kdc_options, - expected_flags=expected_flags, - unexpected_flags=unexpected_flags) + authenticator_subkey = self.RandomKey(kcrypto.Enctype.AES256) - service_ticket = rep['ticket'] + decryption_key = self.TicketDecryptionKey_from_creds(target_creds) - ticket_etype = service_ticket['enc-part']['etype'] - target_key = self.TicketDecryptionKey_from_creds(target_creds, - etype=ticket_etype) + kdc_exchange_dict = self.tgs_exchange_dict( + expected_crealm=tgt.crealm, + expected_cname=tgt.cname, + expected_srealm=srealm, + expected_sname=sname, + expected_supported_etypes=target_creds.tgs_supported_enctypes, + expected_flags=expected_flags, + unexpected_flags=unexpected_flags, + ticket_decryption_key=decryption_key, + check_rep_fn=self.generic_check_kdc_rep, + check_kdc_private_fn=self.generic_check_kdc_private, + tgt=tgt, + authenticator_subkey=authenticator_subkey, + kdc_options=kdc_options, + to_rodc=to_rodc) - session_key = self.EncryptionKey_import(enc_part['key']) + rep = self._generic_kdc_exchange(kdc_exchange_dict, + cname=None, + realm=srealm, + sname=sname, + etypes=etype) + self.check_tgs_reply(rep) - service_ticket_creds = KerberosTicketCreds(service_ticket, - session_key, - crealm=realm, - cname=cname, - srealm=realm, - sname=sname, - decryption_key=target_key) + service_ticket_creds = kdc_exchange_dict['rep_ticket_creds'] if to_rodc: krbtgt_creds = self.get_rodc_krbtgt_creds() -- 2.25.1 From 20f5105a219815c22a1aa1cabdd664685ab6ad41 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:00:38 +1300 Subject: [PATCH 064/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Add pac_request parameter to get_service_ticket() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index c129883e7cde..813af767dbd1 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1273,10 +1273,11 @@ class KDCBaseTest(RawKerberosTest): def get_service_ticket(self, tgt, target_creds, service='host', to_rodc=False, kdc_options=None, expected_flags=None, unexpected_flags=None, - fresh=False): + pac_request=True, expect_pac=True, fresh=False): user_name = tgt.cname['name-string'][0] target_name = target_creds.get_username()[:-1] - cache_key = (user_name, target_name, service, to_rodc, kdc_options) + cache_key = (user_name, target_name, service, to_rodc, kdc_options, + pac_request) if not fresh: ticket = self.tkt_cache.get(cache_key) @@ -1312,6 +1313,8 @@ class KDCBaseTest(RawKerberosTest): tgt=tgt, authenticator_subkey=authenticator_subkey, kdc_options=kdc_options, + pac_request=pac_request, + expect_pac=expect_pac, to_rodc=to_rodc) rep = self._generic_kdc_exchange(kdc_exchange_dict, @@ -1329,6 +1332,7 @@ class KDCBaseTest(RawKerberosTest): krbtgt_creds = self.get_krbtgt_creds() krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) self.verify_ticket(service_ticket_creds, krbtgt_key, + expect_pac=expect_pac, expect_ticket_checksum=self.tkt_sig_support) self.tkt_cache[cache_key] = service_ticket_creds -- 2.25.1 From f42f71eb93822702ddb1b84c780af067095678d7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 8 Oct 2021 15:40:09 +1300 Subject: [PATCH 065/224] CVE-2020-25722 tests/krb5: Allow creating server accounts BUG: https://bugzilla.samba.org/show_bug.cgi?id=14776 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 813af767dbd1..a0da89041c4f 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -38,12 +38,14 @@ from samba.dsdb import ( DS_DOMAIN_FUNCTION_2000, DS_DOMAIN_FUNCTION_2008, DS_GUID_COMPUTERS_CONTAINER, + DS_GUID_DOMAIN_CONTROLLERS_CONTAINER, DS_GUID_USERS_CONTAINER, UF_WORKSTATION_TRUST_ACCOUNT, UF_NO_AUTH_DATA_REQUIRED, UF_NORMAL_ACCOUNT, UF_NOT_DELEGATED, UF_PARTIAL_SECRETS_ACCOUNT, + UF_SERVER_TRUST_ACCOUNT, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION ) from samba.join import DCJoinContext @@ -94,6 +96,7 @@ class KDCBaseTest(RawKerberosTest): class AccountType(Enum): USER = auto() COMPUTER = auto() + SERVER = auto() @classmethod def setUpClass(cls): @@ -245,6 +248,8 @@ class KDCBaseTest(RawKerberosTest): if ou is None: if account_type is account_type.COMPUTER: guid = DS_GUID_COMPUTERS_CONTAINER + elif account_type is account_type.SERVER: + guid = DS_GUID_DOMAIN_CONTROLLERS_CONTAINER else: guid = DS_GUID_USERS_CONTAINER @@ -265,6 +270,8 @@ class KDCBaseTest(RawKerberosTest): account_name += '$' if account_type is self.AccountType.COMPUTER: account_control |= UF_WORKSTATION_TRUST_ACCOUNT + elif account_type is self.AccountType.SERVER: + account_control |= UF_SERVER_TRUST_ACCOUNT else: self.fail() -- 2.25.1 From 8f172454a3151375f33d21db3c6f072fe92ee982 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 30 Sep 2021 16:53:22 +1300 Subject: [PATCH 066/224] CVE-2020-25719 tests/krb5: Add is_tgt() helper method BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index fdf078ea788e..055209fd09df 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -3086,8 +3086,7 @@ class RawKerberosTest(TestCaseInTempDir): def verify_ticket(self, ticket, krbtgt_key, expect_pac=True, expect_ticket_checksum=True): # Check if the ticket is a TGT. - sname = ticket.ticket['sname'] - is_tgt = self.is_tgs(sname) + is_tgt = self.is_tgt(ticket) # Decrypt the ticket. @@ -3506,6 +3505,10 @@ class RawKerberosTest(TestCaseInTempDir): name = principal['name-string'][0] return name in ('krbtgt', b'krbtgt') + def is_tgt(self, ticket): + sname = ticket.ticket['sname'] + return self.is_tgs(sname) + def get_empty_pac(self): return self.AuthorizationData_create(AD_WIN2K_PAC, bytes(1)) -- 2.25.1 From e161aa7d0495cc83553a0e7685263b419eca806d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 15:02:10 +1300 Subject: [PATCH 067/224] CVE-2020-25719 tests/krb5: Add method to get unique username for test accounts BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index a0da89041c4f..e85574c51cb5 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -711,8 +711,7 @@ class KDCBaseTest(RawKerberosTest): rodc_dn = self.get_server_dn(rodc_samdb) - user_name = self.account_base + str(self.account_id) - type(self).account_id += 1 + user_name = self.get_new_username() if name_prefix is not None: user_name = name_prefix + user_name if name_suffix is not None: @@ -821,6 +820,12 @@ class KDCBaseTest(RawKerberosTest): return creds + def get_new_username(self): + user_name = self.account_base + str(self.account_id) + type(self).account_id += 1 + + return user_name + def get_client_creds(self, allow_missing_password=False, allow_missing_keys=True): -- 2.25.1 From 5cc221f009b1e0b76f9033c57582fddd4193d0eb Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 20 Oct 2021 15:48:20 +1300 Subject: [PATCH 068/224] MS CVE-2020-17049 tests/krb5: Allow tests to pass if ticket signature checksum type is wrong BUG: https://bugzilla.samba.org/show_bug.cgi?id=14642 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 055209fd09df..62e1b9867dd8 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -2375,6 +2375,13 @@ class RawKerberosTest(TestCaseInTempDir): krbtgt_creds = self.get_krbtgt_creds() krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) + krbtgt_keys = [krbtgt_key] + if not self.strict_checking: + krbtgt_key_rc4 = self.TicketDecryptionKey_from_creds( + krbtgt_creds, + etype=kcrypto.Enctype.RC4) + krbtgt_keys.append(krbtgt_key_rc4) + expect_pac = kdc_exchange_dict['expect_pac'] ticket_session_key = None @@ -2522,7 +2529,7 @@ class RawKerberosTest(TestCaseInTempDir): self.assertIsNotNone(ticket_decryption_key) if ticket_decryption_key is not None: - self.verify_ticket(ticket_creds, krbtgt_key, expect_pac=expect_pac, + self.verify_ticket(ticket_creds, krbtgt_keys, expect_pac=expect_pac, expect_ticket_checksum=expect_ticket_checksum or self.tkt_sig_support) @@ -3083,7 +3090,7 @@ class RawKerberosTest(TestCaseInTempDir): ticket_blob) self.assertEqual(expected_checksum, checksum) - def verify_ticket(self, ticket, krbtgt_key, expect_pac=True, + def verify_ticket(self, ticket, krbtgt_keys, expect_pac=True, expect_ticket_checksum=True): # Check if the ticket is a TGT. is_tgt = self.is_tgt(ticket) @@ -3171,6 +3178,16 @@ class RawKerberosTest(TestCaseInTempDir): kdc_checksum, kdc_ctype = checksums[ krb5pac.PAC_TYPE_KDC_CHECKSUM] + + if isinstance(krbtgt_keys, collections.abc.Container): + if self.strict_checking: + krbtgt_key = krbtgt_keys[0] + else: + krbtgt_key = next(key for key in krbtgt_keys + if key.ctype == kdc_ctype) + else: + krbtgt_key = krbtgt_keys + krbtgt_key.verify_rodc_checksum(KU_NON_KERB_CKSUM_SALT, server_checksum, kdc_ctype, -- 2.25.1 From 409273f1fda8dac9518e43b39f76d1b7b573e30f Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 16:46:23 +1300 Subject: [PATCH 069/224] CVE-2020-25721 tests/krb5: Check PAC buffer types when STRICT_CHECKING=0 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14835 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 62e1b9867dd8..8e55790272a7 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -1102,13 +1102,14 @@ class RawKerberosTest(TestCaseInTempDir): f"unexpected in {v}") def assertSequenceElementsEqual(self, expected, got, *, - require_strict=None): - if self.strict_checking: + require_strict=None, + require_ordered=True): + if self.strict_checking and require_ordered: self.assertEqual(expected, got) else: fail_msg = f'expected: {expected} got: {got}' - if require_strict is not None: + if not self.strict_checking and require_strict is not None: fail_msg += f' (ignoring: {require_strict})' expected = (x for x in expected if x not in require_strict) got = (x for x in got if x not in require_strict) @@ -2569,12 +2570,16 @@ class RawKerberosTest(TestCaseInTempDir): if not self.is_tgs(expected_sname): expected_types.append(krb5pac.PAC_TYPE_TICKET_CHECKSUM) - if self.strict_checking: - buffer_types = [pac_buffer.type - for pac_buffer in pac.buffers] - self.assertCountEqual(expected_types, buffer_types, - f'expected: {expected_types} ' - f'got: {buffer_types}') + require_strict = {krb5pac.PAC_TYPE_CLIENT_CLAIMS_INFO} + if not self.tkt_sig_support: + require_strict.add(krb5pac.PAC_TYPE_TICKET_CHECKSUM) + + buffer_types = [pac_buffer.type + for pac_buffer in pac.buffers] + self.assertSequenceElementsEqual( + expected_types, buffer_types, + require_ordered=False, + require_strict=require_strict) expected_account_name = kdc_exchange_dict['expected_account_name'] expected_sid = kdc_exchange_dict['expected_sid'] -- 2.25.1 From de25388338bfd57325689af79901a0c0b36d3fc4 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 22 Oct 2021 11:37:31 +1300 Subject: [PATCH 070/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Refactor create_ccache_with_user() to take credentials of target service This allows us to use get_tgt() and get_service_ticket() to obtain tickets, which simplifies the logic. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 43 ++++++------------------ python/samba/tests/krb5/test_ccache.py | 2 +- python/samba/tests/krb5/test_ldap.py | 7 ++-- python/samba/tests/krb5/test_rpc.py | 7 ++-- python/samba/tests/krb5/test_smb.py | 7 ++-- 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index e85574c51cb5..e77a940f4117 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1283,11 +1283,13 @@ class KDCBaseTest(RawKerberosTest): return rep, enc_part def get_service_ticket(self, tgt, target_creds, service='host', + target_name=None, to_rodc=False, kdc_options=None, expected_flags=None, unexpected_flags=None, pac_request=True, expect_pac=True, fresh=False): user_name = tgt.cname['name-string'][0] - target_name = target_creds.get_username()[:-1] + if target_name is None: + target_name = target_creds.get_username()[:-1] cache_key = (user_name, target_name, service, to_rodc, kdc_options, pac_request) @@ -1669,51 +1671,28 @@ class KDCBaseTest(RawKerberosTest): return cachefile - def create_ccache_with_user(self, user_credentials, mach_name, - service="host"): + def create_ccache_with_user(self, user_credentials, mach_credentials, + service="host", target_name=None): # Obtain a service ticket authorising the user and place it into a # newly created credentials cache file. user_name = user_credentials.get_username() realm = user_credentials.get_realm() - # Do the initial AS-REQ, should get a pre-authentication required - # response - etype = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5) cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[user_name]) - sname = self.PrincipalName_create(name_type=NT_SRV_HST, - names=["krbtgt", realm]) - - rep = self.as_req(cname, sname, realm, etype) - self.check_pre_authentication(rep) - # Do the next AS-REQ - padata = self.get_enc_timestamp_pa_data(user_credentials, rep) - key = self.get_as_rep_key(user_credentials, rep) - rep = self.as_req(cname, sname, realm, etype, padata=[padata]) - self.check_as_reply(rep) + tgt = self.get_tgt(user_credentials) # Request a ticket to the host service on the machine account - ticket = rep['ticket'] - enc_part = self.get_as_rep_enc_data(key, rep) - key = self.EncryptionKey_import(enc_part['key']) - cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, - names=[user_name]) - sname = self.PrincipalName_create(name_type=NT_SRV_HST, - names=[service, mach_name]) - - (rep, enc_part) = self.tgs_req( - cname, sname, realm, ticket, key, etype) - self.check_tgs_reply(rep) - key = self.EncryptionKey_import(enc_part['key']) - - # Check the contents of the pac, and the ticket - ticket = rep['ticket'] + ticket = self.get_service_ticket(tgt, mach_credentials, + service=service, + target_name=target_name) # Write the ticket into a credentials cache file that can be ingested # by the main credentials code. - cachefile = self.create_ccache(cname, ticket, enc_part) + cachefile = self.create_ccache(cname, ticket.ticket, + ticket.encpart_private) # Create a credentials object to reference the credentials cache. creds = Credentials() diff --git a/python/samba/tests/krb5/test_ccache.py b/python/samba/tests/krb5/test_ccache.py index 6a2b78398ac3..040ae5cc9a1f 100755 --- a/python/samba/tests/krb5/test_ccache.py +++ b/python/samba/tests/krb5/test_ccache.py @@ -67,7 +67,7 @@ class CcacheTests(KDCBaseTest): # ticket, to ensure that the krbtgt ticket doesn't also need to be # stored. (creds, cachefile) = self.create_ccache_with_user(user_credentials, - mach_name) + mach_credentials) # Authenticate in-process to the machine account using the user's # cached credentials. diff --git a/python/samba/tests/krb5/test_ldap.py b/python/samba/tests/krb5/test_ldap.py index 95b2d24221a0..7d9ffebe2985 100755 --- a/python/samba/tests/krb5/test_ldap.py +++ b/python/samba/tests/krb5/test_ldap.py @@ -53,13 +53,16 @@ class LdapTests(KDCBaseTest): # Create the user account. (user_credentials, _) = self.create_account(samdb, user_name) + mach_credentials = self.get_dc_creds() + # Talk to the KDC to obtain the service ticket, which gets placed into # the cache. The machine account name has to match the name in the # ticket, to ensure that the krbtgt ticket doesn't also need to be # stored. (creds, cachefile) = self.create_ccache_with_user(user_credentials, - mach_name, - service) + mach_credentials, + service, + mach_name) # Authenticate in-process to the machine account using the user's # cached credentials. diff --git a/python/samba/tests/krb5/test_rpc.py b/python/samba/tests/krb5/test_rpc.py index 40ac6df7a350..ef8dd4dcbf5b 100755 --- a/python/samba/tests/krb5/test_rpc.py +++ b/python/samba/tests/krb5/test_rpc.py @@ -50,13 +50,16 @@ class RpcTests(KDCBaseTest): # Create the user account. (user_credentials, _) = self.create_account(samdb, user_name) + mach_credentials = self.get_dc_creds() + # Talk to the KDC to obtain the service ticket, which gets placed into # the cache. The machine account name has to match the name in the # ticket, to ensure that the krbtgt ticket doesn't also need to be # stored. (creds, cachefile) = self.create_ccache_with_user(user_credentials, - mach_name, - service) + mach_credentials, + service, + mach_name) # Authenticate in-process to the machine account using the user's # cached credentials. diff --git a/python/samba/tests/krb5/test_smb.py b/python/samba/tests/krb5/test_smb.py index eebc9a9d4feb..1e70ed322bfc 100755 --- a/python/samba/tests/krb5/test_smb.py +++ b/python/samba/tests/krb5/test_smb.py @@ -55,13 +55,16 @@ class SmbTests(KDCBaseTest): # Create the user account. (user_credentials, _) = self.create_account(samdb, user_name) + mach_credentials = self.get_dc_creds() + # Talk to the KDC to obtain the service ticket, which gets placed into # the cache. The machine account name has to match the name in the # ticket, to ensure that the krbtgt ticket doesn't also need to be # stored. (creds, cachefile) = self.create_ccache_with_user(user_credentials, - mach_name, - service) + mach_credentials, + service, + mach_name) # Set the Kerberos 5 credentials cache environment variable. This is # required because the codepath that gets run (gse_krb5) looks for it -- 2.25.1 From c7f52328cf2f45326563e627c174baf690d04069 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 22 Oct 2021 11:37:37 +1300 Subject: [PATCH 071/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Allow create_ccache_with_user() to return a ticket without a PAC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index e77a940f4117..aed4c427ab08 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1672,7 +1672,7 @@ class KDCBaseTest(RawKerberosTest): return cachefile def create_ccache_with_user(self, user_credentials, mach_credentials, - service="host", target_name=None): + service="host", target_name=None, pac=True): # Obtain a service ticket authorising the user and place it into a # newly created credentials cache file. @@ -1689,6 +1689,9 @@ class KDCBaseTest(RawKerberosTest): service=service, target_name=target_name) + if not pac: + ticket = self.modified_ticket(ticket, exclude_pac=True) + # Write the ticket into a credentials cache file that can be ingested # by the main credentials code. cachefile = self.create_ccache(cname, ticket.ticket, -- 2.25.1 From b254d02974296277e41a1f95054510cd4cb3b216 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 15:45:00 +1300 Subject: [PATCH 072/224] CVE-2020-25722 tests/krb5: Add KDC tests for 3-part SPNs BUG: https://bugzilla.samba.org/show_bug.cgi?id=14776 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 1 + python/samba/tests/krb5/spn_tests.py | 212 +++++++++++++++++++++++ python/samba/tests/usage.py | 1 + selftest/knownfail_heimdal_kdc | 6 + selftest/knownfail_mit_kdc | 6 + source4/selftest/tests.py | 10 ++ 6 files changed, 236 insertions(+) create mode 100755 python/samba/tests/krb5/spn_tests.py diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index aed4c427ab08..cc23484ba2cb 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -97,6 +97,7 @@ class KDCBaseTest(RawKerberosTest): USER = auto() COMPUTER = auto() SERVER = auto() + RODC = auto() @classmethod def setUpClass(cls): diff --git a/python/samba/tests/krb5/spn_tests.py b/python/samba/tests/krb5/spn_tests.py new file mode 100755 index 000000000000..62d2ea081bca --- /dev/null +++ b/python/samba/tests/krb5/spn_tests.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# Unix SMB/CIFS implementation. +# Copyright (C) Stefan Metzmacher 2020 +# Copyright (C) 2020 Catalyst.Net Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import sys + +from samba.tests import DynamicTestCase + +import ldb + +from samba.tests.krb5.kdc_base_test import KDCBaseTest +from samba.tests.krb5.raw_testcase import KerberosCredentials +from samba.tests.krb5.rfc4120_constants import ( + AES256_CTS_HMAC_SHA1_96, + ARCFOUR_HMAC_MD5, + KDC_ERR_S_PRINCIPAL_UNKNOWN, + NT_PRINCIPAL, +) + +sys.path.insert(0, "bin/python") +os.environ["PYTHONUNBUFFERED"] = "1" + +global_asn1_print = False +global_hexdump = False + + +@DynamicTestCase +class SpnTests(KDCBaseTest): + test_account_types = { + 'computer': KDCBaseTest.AccountType.COMPUTER, + 'server': KDCBaseTest.AccountType.SERVER, + 'rodc': KDCBaseTest.AccountType.RODC + } + test_spns = { + '2_part': 'ldap/{{account}}', + '3_part_our_domain': 'ldap/{{account}}/{netbios_domain_name}', + '3_part_our_realm': 'ldap/{{account}}/{dns_domain_name}', + '3_part_not_our_realm': 'ldap/{{account}}/test', + '3_part_instance': 'ldap/{{account}}:test/{dns_domain_name}' + } + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls._mock_rodc_creds = None + + @classmethod + def setUpDynamicTestCases(cls): + for account_type_name, account_type in cls.test_account_types.items(): + for spn_name, spn in cls.test_spns.items(): + tname = f'{spn_name}_spn_{account_type_name}' + targs = (account_type, spn) + cls.generate_dynamic_test('test_spn', tname, *targs) + + def _test_spn_with_args(self, account_type, spn): + target_creds = self._get_creds(account_type) + spn = self._format_spn(spn, target_creds) + + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=spn.split('/')) + + client_creds = self.get_client_creds() + tgt = self.get_tgt(client_creds) + + samdb = self.get_samdb() + netbios_domain_name = samdb.domain_netbios_name() + dns_domain_name = samdb.domain_dns_name() + + subkey = self.RandomKey(tgt.session_key.etype) + + etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5,) + + if account_type is self.AccountType.SERVER: + ticket_etype = AES256_CTS_HMAC_SHA1_96 + else: + ticket_etype = None + decryption_key = self.TicketDecryptionKey_from_creds( + target_creds, etype=ticket_etype) + + if (spn.count('/') > 1 + and (spn.endswith(netbios_domain_name) + or spn.endswith(dns_domain_name)) + and account_type is not self.AccountType.SERVER + and account_type is not self.AccountType.RODC): + expected_error_mode = KDC_ERR_S_PRINCIPAL_UNKNOWN + check_error_fn = self.generic_check_kdc_error + check_rep_fn = None + else: + expected_error_mode = 0 + check_error_fn = None + check_rep_fn = self.generic_check_kdc_rep + + kdc_exchange_dict = self.tgs_exchange_dict( + expected_crealm=tgt.crealm, + expected_cname=tgt.cname, + expected_srealm=tgt.srealm, + expected_sname=sname, + ticket_decryption_key=decryption_key, + check_rep_fn=check_rep_fn, + check_error_fn=check_error_fn, + check_kdc_private_fn=self.generic_check_kdc_private, + expected_error_mode=expected_error_mode, + tgt=tgt, + authenticator_subkey=subkey, + kdc_options='0', + expect_edata=False) + + self._generic_kdc_exchange(kdc_exchange_dict, + cname=None, + realm=tgt.srealm, + sname=sname, + etypes=etypes) + + def setUp(self): + super().setUp() + self.do_asn1_print = global_asn1_print + self.do_hexdump = global_hexdump + + def _format_spns(self, spns, creds=None): + return map(lambda spn: self._format_spn(spn, creds), spns) + + def _format_spn(self, spn, creds=None): + samdb = self.get_samdb() + + spn = spn.format(netbios_domain_name=samdb.domain_netbios_name(), + dns_domain_name=samdb.domain_dns_name()) + + if creds is not None: + account_name = creds.get_username() + spn = spn.format(account=account_name) + + return spn + + def _get_creds(self, account_type): + spns = self._format_spns(self.test_spns.values()) + + if account_type is self.AccountType.RODC: + creds = self._mock_rodc_creds + if creds is None: + creds = self._get_mock_rodc_creds(spns) + type(self)._mock_rodc_creds = creds + else: + creds = self.get_cached_creds( + account_type=account_type, + opts={ + 'spn': spns + }) + + return creds + + def _get_mock_rodc_creds(self, spns): + rodc_ctx = self.get_mock_rodc_ctx() + + for spn in spns: + spn = spn.format(account=rodc_ctx.myname) + if spn not in rodc_ctx.SPNs: + rodc_ctx.SPNs.append(spn) + + samdb = self.get_samdb() + rodc_dn = ldb.Dn(samdb, rodc_ctx.acct_dn) + + msg = ldb.Message(rodc_dn) + msg['servicePrincipalName'] = ldb.MessageElement( + rodc_ctx.SPNs, + ldb.FLAG_MOD_REPLACE, + 'servicePrincipalName') + samdb.modify(msg) + + creds = KerberosCredentials() + creds.guess(self.get_lp()) + creds.set_realm(rodc_ctx.realm.upper()) + creds.set_domain(rodc_ctx.domain_name) + creds.set_password(rodc_ctx.acct_pass) + creds.set_username(rodc_ctx.myname) + creds.set_workstation(rodc_ctx.samname) + creds.set_dn(rodc_dn) + creds.set_spn(rodc_ctx.SPNs) + + res = samdb.search(base=rodc_dn, + scope=ldb.SCOPE_BASE, + attrs=['msDS-KeyVersionNumber']) + kvno = int(res[0].get('msDS-KeyVersionNumber', idx=0)) + creds.set_kvno(kvno) + + keys = self.get_keys(samdb, rodc_dn) + self.creds_set_keys(creds, keys) + + return creds + + +if __name__ == "__main__": + global_asn1_print = False + global_hexdump = False + import unittest + unittest.main() diff --git a/python/samba/tests/usage.py b/python/samba/tests/usage.py index af118242a1bd..966056d80c35 100644 --- a/python/samba/tests/usage.py +++ b/python/samba/tests/usage.py @@ -105,6 +105,7 @@ EXCLUDE_USAGE = { 'python/samba/tests/krb5/fast_tests.py', 'python/samba/tests/krb5/rodc_tests.py', 'python/samba/tests/krb5/salt_tests.py', + 'python/samba/tests/krb5/spn_tests.py', } EXCLUDE_HELP = { diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index b39b11c3c53d..45524d70fa29 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -255,3 +255,9 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_auth_data_required ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_a ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_b +# +# SPN tests +# +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 4fc68ffd854c..c86f9c2c2ea7 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -374,3 +374,9 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2003dc ^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008dc ^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008r2dc +# +# SPN tests +# +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer +^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index d770230ab1e2..afaf0719cf6c 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1502,6 +1502,16 @@ planpythontestsuite( 'FAST_SUPPORT': have_fast_support, 'TKT_SIG_SUPPORT': tkt_sig_support }) +planpythontestsuite( + "ad_dc", + "samba.tests.krb5.spn_tests", + environ={ + 'ADMIN_USERNAME': '$USERNAME', + 'ADMIN_PASSWORD': '$PASSWORD', + 'STRICT_CHECKING': '0', + 'FAST_SUPPORT': have_fast_support, + 'TKT_SIG_SUPPORT': tkt_sig_support + }) for env in [ 'vampire_dc', -- 2.25.1 From bfc6be62dd2b41a7527e4f499c0936598373ea4c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 13 Oct 2021 16:07:09 +1300 Subject: [PATCH 073/224] CVE-2020-25721 ndrdump: Add tests for PAC with UPN_DNS_INFO BUG: https://bugzilla.samba.org/show_bug.cgi?id=14835 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/blackbox/ndrdump.py | 35 +++ .../tests/krb5pac_upn_dns_info_ex.b64.txt | 1 + .../librpc/tests/krb5pac_upn_dns_info_ex.txt | 220 ++++++++++++++++++ ...5pac_upn_dns_info_ex_not_supported.b64.txt | 1 + .../krb5pac_upn_dns_info_ex_not_supported.txt | 213 +++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 source4/librpc/tests/krb5pac_upn_dns_info_ex.b64.txt create mode 100644 source4/librpc/tests/krb5pac_upn_dns_info_ex.txt create mode 100644 source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.b64.txt create mode 100644 source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.txt diff --git a/python/samba/tests/blackbox/ndrdump.py b/python/samba/tests/blackbox/ndrdump.py index 7833ec981196..9a4e6cabe8b4 100644 --- a/python/samba/tests/blackbox/ndrdump.py +++ b/python/samba/tests/blackbox/ndrdump.py @@ -90,6 +90,41 @@ class NdrDumpTests(BlackboxTestCase): expected.encode('utf-8')) self.assertTrue(actual.endswith(b"dump OK\n")) + def test_ndrdump_upn_dns_info_ex(self): + with open(self.data_path( + 'krb5pac_upn_dns_info_ex.txt')) as f: + expected = f.read() + data_path = self.data_path( + 'krb5pac_upn_dns_info_ex.b64.txt') + + try: + actual = self.check_output( + 'ndrdump --debug-stdout -d0 krb5pac PAC_DATA struct ' + '--validate --base64-input ' + data_path) + except BlackboxProcessError as e: + self.fail(e) + + self.assertEqual(actual, expected.encode('utf-8')) + + def test_ndrdump_upn_dns_info_ex_not_supported(self): + with open(self.data_path( + 'krb5pac_upn_dns_info_ex_not_supported.txt')) as f: + expected = f.read() + data_path = self.data_path( + 'krb5pac_upn_dns_info_ex_not_supported.b64.txt') + + try: + # This PAC has been edited to remove the + # PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID bit, so that we can + # simulate older versions of Samba parsing this structure. + actual = self.check_output( + 'ndrdump --debug-stdout -d0 krb5pac PAC_DATA struct ' + '--validate --base64-input ' + data_path) + except BlackboxProcessError as e: + self.fail(e) + + self.assertEqual(actual, expected.encode('utf-8')) + def test_ndrdump_with_binary_struct_number(self): expected = '''pull returned Success GUID : 33323130-3534-3736-3839-616263646566 diff --git a/source4/librpc/tests/krb5pac_upn_dns_info_ex.b64.txt b/source4/librpc/tests/krb5pac_upn_dns_info_ex.b64.txt new file mode 100644 index 000000000000..02b570624bc5 --- /dev/null +++ b/source4/librpc/tests/krb5pac_upn_dns_info_ex.b64.txt @@ -0,0 +1 @@ +BgAAAAAAAAABAAAA0AEAAGgAAAAAAAAACgAAABwAAAA4AgAAAAAAAAwAAACoAAAAWAIAAAAAAAAGAAAAFAAAAAADAAAAAAAABwAAABAAAAAYAwAAAAAAABAAAAAQAAAAKAMAAAAAAAABEAgAzMzMzMABAAAAAAAAAAACAAAAAAAAAAAA/////////3//////////f7pMcCzXv9cBugzaVqDA1wG6zMkh2ODXARIAEgAEAAIAAAAAAAgAAgAAAAAADAACAAAAAAAQAAIAAAAAABQAAgAAAAAAGAACAAAAAACOBAAAAQIAAAEAAAAcAAIAIAAAAAAAAAAAAAAAAAAAAAAAAAAOABAAIAACABYAGAAkAAIAKAACAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAALAACAAAAAAAAAAAAAAAAAAkAAAAAAAAACQAAAHQAcwB0AHQAawB0AHUAcwByAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAECAAAHAAAACAAAAAAAAAAHAAAATABPAEMAQQBMAEQAQwAAAAwAAAAAAAAACwAAAFMAQQBNAEIAQQBEAE8ATQBBAEkATgAAAAQAAAABBAAAAAAABRUAAAC2fvX0wDGiOufKt1QBAAAAMAACAAcAAAABAAAAAQEAAAAAABIBAAAAAAAAAIC3ISzXv9cBEgB0AHMAdAB0AGsAdAB1AHMAcgAAAAAANgAYACIAUAADAAAAEgB4ABwAigAAAAAAdABzAHQAdABrAHQAdQBzAHIAQABzAGEAbQBiAGEALgBlAHgAYQBtAHAAbABlAC4AYwBvAG0AAABTAEEATQBCAEEALgBFAFgAQQBNAFAATABFAC4AQwBPAE0AAAAAAAAAdABzAHQAdABrAHQAdQBzAHIAAQUAAAAAAAUVAAAAtn719MAxojrnyrdUjgQAAAAAdv///ys5aox2KdqNY8CVVxkQbs4AAAAAEAAAAFrUeP0b8Pbct0VlVhAAAAB4SC+IGKoLP+0030o= diff --git a/source4/librpc/tests/krb5pac_upn_dns_info_ex.txt b/source4/librpc/tests/krb5pac_upn_dns_info_ex.txt new file mode 100644 index 000000000000..9747d1b6d3a4 --- /dev/null +++ b/source4/librpc/tests/krb5pac_upn_dns_info_ex.txt @@ -0,0 +1,220 @@ +pull returned Success + PAC_DATA: struct PAC_DATA + num_buffers : 0x00000006 (6) + version : 0x00000000 (0) + buffers: ARRAY(6) + buffers: struct PAC_BUFFER + type : PAC_TYPE_LOGON_INFO (1) + _ndr_size : 0x000001d0 (464) + info : * + info : union PAC_INFO(case 1) + logon_info: struct PAC_LOGON_INFO_CTR + info : * + info: struct PAC_LOGON_INFO + info3: struct netr_SamInfo3 + base: struct netr_SamBaseInfo + logon_time : NTTIME(0) + logoff_time : Thu Sep 14 02:48:05 AM 30828 UTC + kickoff_time : Thu Sep 14 02:48:05 AM 30828 UTC + last_password_change : Wed Oct 13 02:08:12 AM 2021 UTC + allow_password_change : Thu Oct 14 02:08:12 AM 2021 UTC + force_password_change : Wed Nov 24 02:08:12 AM 2021 UTC + account_name: struct lsa_String + length : 0x0012 (18) + size : 0x0012 (18) + string : * + string : 'tsttktusr' + full_name: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + logon_script: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + profile_path: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + home_directory: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + home_drive: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + logon_count : 0x0000 (0) + bad_password_count : 0x0000 (0) + rid : 0x0000048e (1166) + primary_gid : 0x00000201 (513) + groups: struct samr_RidWithAttributeArray + count : 0x00000001 (1) + rids : * + rids: ARRAY(1) + rids: struct samr_RidWithAttribute + rid : 0x00000201 (513) + attributes : 0x00000007 (7) + 1: SE_GROUP_MANDATORY + 1: SE_GROUP_ENABLED_BY_DEFAULT + 1: SE_GROUP_ENABLED + 0: SE_GROUP_OWNER + 0: SE_GROUP_USE_FOR_DENY_ONLY + 0: SE_GROUP_INTEGRITY + 0: SE_GROUP_INTEGRITY_ENABLED + 0: SE_GROUP_RESOURCE + 0x00: SE_GROUP_LOGON_ID (0) + user_flags : 0x00000020 (32) + 0: NETLOGON_GUEST + 0: NETLOGON_NOENCRYPTION + 0: NETLOGON_CACHED_ACCOUNT + 0: NETLOGON_USED_LM_PASSWORD + 1: NETLOGON_EXTRA_SIDS + 0: NETLOGON_SUBAUTH_SESSION_KEY + 0: NETLOGON_SERVER_TRUST_ACCOUNT + 0: NETLOGON_NTLMV2_ENABLED + 0: NETLOGON_RESOURCE_GROUPS + 0: NETLOGON_PROFILE_PATH_RETURNED + 0: NETLOGON_GRACE_LOGON + key: struct netr_UserSessionKey + key: ARRAY(16): + logon_server: struct lsa_StringLarge + length : 0x000e (14) + size : 0x0010 (16) + string : * + string : 'LOCALDC' + logon_domain: struct lsa_StringLarge + length : 0x0016 (22) + size : 0x0018 (24) + string : * + string : 'SAMBADOMAIN' + domain_sid : * + domain_sid : S-1-5-21-4109729462-983708096-1421331175 + LMSessKey: struct netr_LMSessionKey + key: ARRAY(8): + acct_flags : 0x00000010 (16) + 0: ACB_DISABLED + 0: ACB_HOMDIRREQ + 0: ACB_PWNOTREQ + 0: ACB_TEMPDUP + 1: ACB_NORMAL + 0: ACB_MNS + 0: ACB_DOMTRUST + 0: ACB_WSTRUST + 0: ACB_SVRTRUST + 0: ACB_PWNOEXP + 0: ACB_AUTOLOCK + 0: ACB_ENC_TXT_PWD_ALLOWED + 0: ACB_SMARTCARD_REQUIRED + 0: ACB_TRUSTED_FOR_DELEGATION + 0: ACB_NOT_DELEGATED + 0: ACB_USE_DES_KEY_ONLY + 0: ACB_DONT_REQUIRE_PREAUTH + 0: ACB_PW_EXPIRED + 0: ACB_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION + 0: ACB_NO_AUTH_DATA_REQD + 0: ACB_PARTIAL_SECRETS_ACCOUNT + 0: ACB_USE_AES_KEYS + sub_auth_status : 0x00000000 (0) + last_successful_logon : NTTIME(0) + last_failed_logon : NTTIME(0) + failed_logon_count : 0x00000000 (0) + reserved : 0x00000000 (0) + sidcount : 0x00000001 (1) + sids : * + sids: ARRAY(1) + sids: struct netr_SidAttr + sid : * + sid : S-1-18-1 + attributes : 0x00000007 (7) + 1: SE_GROUP_MANDATORY + 1: SE_GROUP_ENABLED_BY_DEFAULT + 1: SE_GROUP_ENABLED + 0: SE_GROUP_OWNER + 0: SE_GROUP_USE_FOR_DENY_ONLY + 0: SE_GROUP_INTEGRITY + 0: SE_GROUP_INTEGRITY_ENABLED + 0: SE_GROUP_RESOURCE + 0x00: SE_GROUP_LOGON_ID (0) + resource_groups: struct PAC_DOMAIN_GROUP_MEMBERSHIP + domain_sid : NULL + groups: struct samr_RidWithAttributeArray + count : 0x00000000 (0) + rids : NULL + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_LOGON_NAME (10) + _ndr_size : 0x0000001c (28) + info : * + info : union PAC_INFO(case 10) + logon_name: struct PAC_LOGON_NAME + logon_time : Wed Oct 13 02:08:11 AM 2021 UTC + size : 0x0012 (18) + account_name : 'tsttktusr' + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_UPN_DNS_INFO (12) + _ndr_size : 0x000000a8 (168) + info : * + info : union PAC_INFO(case 12) + upn_dns_info: struct PAC_UPN_DNS_INFO + upn_name_size : 0x0036 (54) + upn_name : * + upn_name : 'tsttktusr@samba.example.com' + dns_domain_name_size : 0x0022 (34) + dns_domain_name : * + dns_domain_name : 'SAMBA.EXAMPLE.COM' + flags : 0x00000003 (3) + 1: PAC_UPN_DNS_FLAG_CONSTRUCTED + 1: PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID + ex : union PAC_UPN_DNS_INFO_EX(case 2) + sam_name_and_sid: struct PAC_UPN_DNS_INFO_SAM_NAME_AND_SID + samaccountname_size : 0x0012 (18) + samaccountname : * + samaccountname : 'tsttktusr' + objectsid_size : 0x001c (28) + objectsid : * + objectsid : S-1-5-21-4109729462-983708096-1421331175-1166 + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_SRV_CHECKSUM (6) + _ndr_size : 0x00000014 (20) + info : * + info : union PAC_INFO(case 6) + srv_cksum: struct PAC_SIGNATURE_DATA + type : 0xffffff76 (4294967158) + signature : DATA_BLOB length=16 +[0000] 2B 39 6A 8C 76 29 DA 8D 63 C0 95 57 19 10 6E CE +9j.v).. c..W..n. + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_KDC_CHECKSUM (7) + _ndr_size : 0x00000010 (16) + info : * + info : union PAC_INFO(case 7) + kdc_cksum: struct PAC_SIGNATURE_DATA + type : 0x00000010 (16) + signature : DATA_BLOB length=12 +[0000] 5A D4 78 FD 1B F0 F6 DC B7 45 65 56 Z.x..... .EeV + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_TICKET_CHECKSUM (16) + _ndr_size : 0x00000010 (16) + info : * + info : union PAC_INFO(case 16) + ticket_checksum: struct PAC_SIGNATURE_DATA + type : 0x00000010 (16) + signature : DATA_BLOB length=12 +[0000] 78 48 2F 88 18 AA 0B 3F ED 34 DF 4A xH/....? .4.J + _pad : 0x00000000 (0) +push returned Success +pull returned Success +WARNING! orig bytes:824 validated pushed bytes:832 +WARNING! orig pulled bytes:824 validated pulled bytes:832 +WARNING! orig and validated differ at byte 0x2C (44) +WARNING! orig byte[0x2C] = 0xA8 validated byte[0x2C] = 0xB0 +dump OK diff --git a/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.b64.txt b/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.b64.txt new file mode 100644 index 000000000000..cd99b9d0b0ad --- /dev/null +++ b/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.b64.txt @@ -0,0 +1 @@ +BgAAAAAAAAABAAAA0AEAAGgAAAAAAAAACgAAABwAAAA4AgAAAAAAAAwAAACoAAAAWAIAAAAAAAAGAAAAFAAAAAADAAAAAAAABwAAABAAAAAYAwAAAAAAABAAAAAQAAAAKAMAAAAAAAABEAgAzMzMzMABAAAAAAAAAAACAAAAAAAAAAAA/////////3//////////f7pMcCzXv9cBugzaVqDA1wG6zMkh2ODXARIAEgAEAAIAAAAAAAgAAgAAAAAADAACAAAAAAAQAAIAAAAAABQAAgAAAAAAGAACAAAAAACOBAAAAQIAAAEAAAAcAAIAIAAAAAAAAAAAAAAAAAAAAAAAAAAOABAAIAACABYAGAAkAAIAKAACAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAALAACAAAAAAAAAAAAAAAAAAkAAAAAAAAACQAAAHQAcwB0AHQAawB0AHUAcwByAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAECAAAHAAAACAAAAAAAAAAHAAAATABPAEMAQQBMAEQAQwAAAAwAAAAAAAAACwAAAFMAQQBNAEIAQQBEAE8ATQBBAEkATgAAAAQAAAABBAAAAAAABRUAAAC2fvX0wDGiOufKt1QBAAAAMAACAAcAAAABAAAAAQEAAAAAABIBAAAAAAAAAIC3ISzXv9cBEgB0AHMAdAB0AGsAdAB1AHMAcgAAAAAANgAYACIAUAABAAAAEgB4ABwAigAAAAAAdABzAHQAdABrAHQAdQBzAHIAQABzAGEAbQBiAGEALgBlAHgAYQBtAHAAbABlAC4AYwBvAG0AAABTAEEATQBCAEEALgBFAFgAQQBNAFAATABFAC4AQwBPAE0AAAAAAAAAdABzAHQAdABrAHQAdQBzAHIAAQUAAAAAAAUVAAAAtn719MAxojrnyrdUjgQAAAAAdv///ys5aox2KdqNY8CVVxkQbs4AAAAAEAAAAFrUeP0b8Pbct0VlVhAAAAB4SC+IGKoLP+0030o= diff --git a/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.txt b/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.txt new file mode 100644 index 000000000000..d29832ede499 --- /dev/null +++ b/source4/librpc/tests/krb5pac_upn_dns_info_ex_not_supported.txt @@ -0,0 +1,213 @@ +pull returned Success + PAC_DATA: struct PAC_DATA + num_buffers : 0x00000006 (6) + version : 0x00000000 (0) + buffers: ARRAY(6) + buffers: struct PAC_BUFFER + type : PAC_TYPE_LOGON_INFO (1) + _ndr_size : 0x000001d0 (464) + info : * + info : union PAC_INFO(case 1) + logon_info: struct PAC_LOGON_INFO_CTR + info : * + info: struct PAC_LOGON_INFO + info3: struct netr_SamInfo3 + base: struct netr_SamBaseInfo + logon_time : NTTIME(0) + logoff_time : Thu Sep 14 02:48:05 AM 30828 UTC + kickoff_time : Thu Sep 14 02:48:05 AM 30828 UTC + last_password_change : Wed Oct 13 02:08:12 AM 2021 UTC + allow_password_change : Thu Oct 14 02:08:12 AM 2021 UTC + force_password_change : Wed Nov 24 02:08:12 AM 2021 UTC + account_name: struct lsa_String + length : 0x0012 (18) + size : 0x0012 (18) + string : * + string : 'tsttktusr' + full_name: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + logon_script: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + profile_path: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + home_directory: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + home_drive: struct lsa_String + length : 0x0000 (0) + size : 0x0000 (0) + string : * + string : '' + logon_count : 0x0000 (0) + bad_password_count : 0x0000 (0) + rid : 0x0000048e (1166) + primary_gid : 0x00000201 (513) + groups: struct samr_RidWithAttributeArray + count : 0x00000001 (1) + rids : * + rids: ARRAY(1) + rids: struct samr_RidWithAttribute + rid : 0x00000201 (513) + attributes : 0x00000007 (7) + 1: SE_GROUP_MANDATORY + 1: SE_GROUP_ENABLED_BY_DEFAULT + 1: SE_GROUP_ENABLED + 0: SE_GROUP_OWNER + 0: SE_GROUP_USE_FOR_DENY_ONLY + 0: SE_GROUP_INTEGRITY + 0: SE_GROUP_INTEGRITY_ENABLED + 0: SE_GROUP_RESOURCE + 0x00: SE_GROUP_LOGON_ID (0) + user_flags : 0x00000020 (32) + 0: NETLOGON_GUEST + 0: NETLOGON_NOENCRYPTION + 0: NETLOGON_CACHED_ACCOUNT + 0: NETLOGON_USED_LM_PASSWORD + 1: NETLOGON_EXTRA_SIDS + 0: NETLOGON_SUBAUTH_SESSION_KEY + 0: NETLOGON_SERVER_TRUST_ACCOUNT + 0: NETLOGON_NTLMV2_ENABLED + 0: NETLOGON_RESOURCE_GROUPS + 0: NETLOGON_PROFILE_PATH_RETURNED + 0: NETLOGON_GRACE_LOGON + key: struct netr_UserSessionKey + key: ARRAY(16): + logon_server: struct lsa_StringLarge + length : 0x000e (14) + size : 0x0010 (16) + string : * + string : 'LOCALDC' + logon_domain: struct lsa_StringLarge + length : 0x0016 (22) + size : 0x0018 (24) + string : * + string : 'SAMBADOMAIN' + domain_sid : * + domain_sid : S-1-5-21-4109729462-983708096-1421331175 + LMSessKey: struct netr_LMSessionKey + key: ARRAY(8): + acct_flags : 0x00000010 (16) + 0: ACB_DISABLED + 0: ACB_HOMDIRREQ + 0: ACB_PWNOTREQ + 0: ACB_TEMPDUP + 1: ACB_NORMAL + 0: ACB_MNS + 0: ACB_DOMTRUST + 0: ACB_WSTRUST + 0: ACB_SVRTRUST + 0: ACB_PWNOEXP + 0: ACB_AUTOLOCK + 0: ACB_ENC_TXT_PWD_ALLOWED + 0: ACB_SMARTCARD_REQUIRED + 0: ACB_TRUSTED_FOR_DELEGATION + 0: ACB_NOT_DELEGATED + 0: ACB_USE_DES_KEY_ONLY + 0: ACB_DONT_REQUIRE_PREAUTH + 0: ACB_PW_EXPIRED + 0: ACB_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION + 0: ACB_NO_AUTH_DATA_REQD + 0: ACB_PARTIAL_SECRETS_ACCOUNT + 0: ACB_USE_AES_KEYS + sub_auth_status : 0x00000000 (0) + last_successful_logon : NTTIME(0) + last_failed_logon : NTTIME(0) + failed_logon_count : 0x00000000 (0) + reserved : 0x00000000 (0) + sidcount : 0x00000001 (1) + sids : * + sids: ARRAY(1) + sids: struct netr_SidAttr + sid : * + sid : S-1-18-1 + attributes : 0x00000007 (7) + 1: SE_GROUP_MANDATORY + 1: SE_GROUP_ENABLED_BY_DEFAULT + 1: SE_GROUP_ENABLED + 0: SE_GROUP_OWNER + 0: SE_GROUP_USE_FOR_DENY_ONLY + 0: SE_GROUP_INTEGRITY + 0: SE_GROUP_INTEGRITY_ENABLED + 0: SE_GROUP_RESOURCE + 0x00: SE_GROUP_LOGON_ID (0) + resource_groups: struct PAC_DOMAIN_GROUP_MEMBERSHIP + domain_sid : NULL + groups: struct samr_RidWithAttributeArray + count : 0x00000000 (0) + rids : NULL + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_LOGON_NAME (10) + _ndr_size : 0x0000001c (28) + info : * + info : union PAC_INFO(case 10) + logon_name: struct PAC_LOGON_NAME + logon_time : Wed Oct 13 02:08:11 AM 2021 UTC + size : 0x0012 (18) + account_name : 'tsttktusr' + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_UPN_DNS_INFO (12) + _ndr_size : 0x000000a8 (168) + info : * + info : union PAC_INFO(case 12) + upn_dns_info: struct PAC_UPN_DNS_INFO + upn_name_size : 0x0036 (54) + upn_name : * + upn_name : 'tsttktusr@samba.example.com' + dns_domain_name_size : 0x0022 (34) + dns_domain_name : * + dns_domain_name : 'SAMBA.EXAMPLE.COM' + flags : 0x00000001 (1) + 1: PAC_UPN_DNS_FLAG_CONSTRUCTED + 0: PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID + ex : union PAC_UPN_DNS_INFO_EX(case 0) + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_SRV_CHECKSUM (6) + _ndr_size : 0x00000014 (20) + info : * + info : union PAC_INFO(case 6) + srv_cksum: struct PAC_SIGNATURE_DATA + type : 0xffffff76 (4294967158) + signature : DATA_BLOB length=16 +[0000] 2B 39 6A 8C 76 29 DA 8D 63 C0 95 57 19 10 6E CE +9j.v).. c..W..n. + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_KDC_CHECKSUM (7) + _ndr_size : 0x00000010 (16) + info : * + info : union PAC_INFO(case 7) + kdc_cksum: struct PAC_SIGNATURE_DATA + type : 0x00000010 (16) + signature : DATA_BLOB length=12 +[0000] 5A D4 78 FD 1B F0 F6 DC B7 45 65 56 Z.x..... .EeV + _pad : 0x00000000 (0) + buffers: struct PAC_BUFFER + type : PAC_TYPE_TICKET_CHECKSUM (16) + _ndr_size : 0x00000010 (16) + info : * + info : union PAC_INFO(case 16) + ticket_checksum: struct PAC_SIGNATURE_DATA + type : 0x00000010 (16) + signature : DATA_BLOB length=12 +[0000] 78 48 2F 88 18 AA 0B 3F ED 34 DF 4A xH/....? .4.J + _pad : 0x00000000 (0) +push returned Success +pull returned Success +WARNING! orig bytes:824 validated pushed bytes:768 +WARNING! orig pulled bytes:824 validated pulled bytes:768 +WARNING! orig and validated differ at byte 0x2C (44) +WARNING! orig byte[0x2C] = 0xA8 validated byte[0x2C] = 0x70 +dump OK -- 2.25.1 From 77977aa0ee9dbe30385fca1a7ebdb8c78898e0ff Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:02:39 +1300 Subject: [PATCH 074/224] CVE-2020-25719 tests/krb5: Add tests for requiring and issuing a PAC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 124 ++++++++++++++++++++--- selftest/knownfail_heimdal_kdc | 9 ++ selftest/knownfail_mit_kdc | 6 ++ 3 files changed, 123 insertions(+), 16 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index f36704f998cf..fbeb5fe61fbb 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -31,6 +31,7 @@ from samba.tests.krb5.rfc4120_constants import ( KRB_ERROR, KRB_TGS_REP, KDC_ERR_BADMATCH, + KDC_ERR_BADOPTION, NT_PRINCIPAL, NT_SRV_INST, ) @@ -214,7 +215,8 @@ class KdcTgsTests(KDCBaseTest): "rep = {%s},%s" % (rep, pac_data)) def _make_tgs_request(self, client_creds, service_creds, tgt, - expect_pac=True): + pac_request=None, expect_pac=True, + expect_error=False): client_account = client_creds.get_username() cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[client_account]) @@ -241,6 +243,15 @@ class KdcTgsTests(KDCBaseTest): authenticator_subkey = self.RandomKey(kcrypto.Enctype.AES256) + if expect_error: + expected_error_mode = KDC_ERR_BADOPTION + check_error_fn = self.generic_check_kdc_error + check_rep_fn = None + else: + expected_error_mode = 0 + check_error_fn = None + check_rep_fn = self.generic_check_kdc_rep + kdc_exchange_dict = self.tgs_exchange_dict( expected_crealm=expected_crealm, expected_cname=expected_cname, @@ -248,12 +259,14 @@ class KdcTgsTests(KDCBaseTest): expected_sname=expected_sname, expected_supported_etypes=expected_supported_etypes, ticket_decryption_key=target_decryption_key, - check_rep_fn=self.generic_check_kdc_rep, + check_error_fn=check_error_fn, + check_rep_fn=check_rep_fn, check_kdc_private_fn=self.generic_check_kdc_private, - expected_error_mode=0, + expected_error_mode=expected_error_mode, tgt=tgt, authenticator_subkey=authenticator_subkey, kdc_options=kdc_options, + pac_request=pac_request, expect_pac=expect_pac) rep = self._generic_kdc_exchange(kdc_exchange_dict, @@ -261,25 +274,43 @@ class KdcTgsTests(KDCBaseTest): realm=realm, sname=sname, etypes=etypes) - self.check_reply(rep, KRB_TGS_REP) + if expect_error: + self.check_error_rep(rep, expected_error_mode) + + return None + else: + self.check_reply(rep, KRB_TGS_REP) + + return kdc_exchange_dict['rep_ticket_creds'] + + def test_request(self): + client_creds = self.get_client_creds() + service_creds = self.get_service_creds() + + tgt = self.get_tgt(client_creds) + + pac = self.get_ticket_pac(tgt) + self.assertIsNotNone(pac) + + ticket = self._make_tgs_request(client_creds, service_creds, tgt) - return kdc_exchange_dict['rep_ticket_creds'] + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) def test_request_no_pac(self): client_creds = self.get_client_creds() service_creds = self.get_service_creds() - tgt = self.get_tgt(client_creds, pac_request=False, - expect_pac=False) + tgt = self.get_tgt(client_creds, pac_request=False) - pac = self.get_ticket_pac(tgt, expect_pac=False) - self.assertIsNone(pac) + pac = self.get_ticket_pac(tgt) + self.assertIsNotNone(pac) ticket = self._make_tgs_request(client_creds, service_creds, tgt, - expect_pac=False) + pac_request=False) - pac = self.get_ticket_pac(ticket, expect_pac=False) - self.assertIsNone(pac) + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) def test_client_no_auth_data_required(self): client_creds = self.get_cached_creds( @@ -297,6 +328,23 @@ class KdcTgsTests(KDCBaseTest): pac = self.get_ticket_pac(ticket) self.assertIsNotNone(pac) + def test_no_pac_client_no_auth_data_required(self): + client_creds = self.get_cached_creds( + account_type=self.AccountType.USER, + opts={'no_auth_data_required': True}) + service_creds = self.get_service_creds() + + tgt = self.get_tgt(client_creds, pac_request=False) + + pac = self.get_ticket_pac(tgt) + self.assertIsNotNone(pac) + + ticket = self._make_tgs_request(client_creds, service_creds, tgt, + pac_request=False) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + def test_service_no_auth_data_required(self): client_creds = self.get_client_creds() service_creds = self.get_cached_creds( @@ -314,8 +362,42 @@ class KdcTgsTests(KDCBaseTest): pac = self.get_ticket_pac(ticket, expect_pac=False) self.assertIsNone(pac) - def test_remove_pac(self): + def test_no_pac_service_no_auth_data_required(self): client_creds = self.get_client_creds() + service_creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={'no_auth_data_required': True}) + + tgt = self.get_tgt(client_creds, pac_request=False) + + pac = self.get_ticket_pac(tgt) + self.assertIsNotNone(pac) + + ticket = self._make_tgs_request(client_creds, service_creds, tgt, + pac_request=False, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_remove_pac_service_no_auth_data_required(self): + client_creds = self.get_client_creds() + service_creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={'no_auth_data_required': True}) + + tgt = self.modified_ticket(self.get_tgt(client_creds), + exclude_pac=True) + + pac = self.get_ticket_pac(tgt, expect_pac=False) + self.assertIsNone(pac) + + self._make_tgs_request(client_creds, service_creds, tgt, + expect_pac=False, expect_error=True) + + def test_remove_pac_client_no_auth_data_required(self): + client_creds = self.get_cached_creds( + account_type=self.AccountType.USER, + opts={'no_auth_data_required': True}) service_creds = self.get_service_creds() tgt = self.modified_ticket(self.get_tgt(client_creds), @@ -324,12 +406,22 @@ class KdcTgsTests(KDCBaseTest): pac = self.get_ticket_pac(tgt, expect_pac=False) self.assertIsNone(pac) - ticket = self._make_tgs_request(client_creds, service_creds, tgt, - expect_pac=False) + self._make_tgs_request(client_creds, service_creds, tgt, + expect_pac=False, expect_error=True) - pac = self.get_ticket_pac(ticket, expect_pac=False) + def test_remove_pac(self): + client_creds = self.get_client_creds() + service_creds = self.get_service_creds() + + tgt = self.modified_ticket(self.get_tgt(client_creds), + exclude_pac=True) + + pac = self.get_ticket_pac(tgt, expect_pac=False) self.assertIsNone(pac) + self._make_tgs_request(client_creds, service_creds, tgt, + expect_pac=False, expect_error=True) + if __name__ == "__main__": global_asn1_print = False diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 45524d70fa29..410ba83123c8 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -261,3 +261,12 @@ ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer +# +# KDC TGS PAC tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_client_no_auth_data_required +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index c86f9c2c2ea7..8612d05b3da6 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -276,7 +276,13 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_ldap_service_ticket\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_get_ticket_for_host_service_of_machine_account\(ad_dc\) # +# KDC TGS PAC tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_client_no_auth_data_required\(ad_dc\) +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac\(ad_dc\) +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required\(ad_dc\) +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_service_no_auth_data_required\(ad_dc\) # -- 2.25.1 From dae1123da3eb152d8baabfed946df70414c314f8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 14:39:36 +1300 Subject: [PATCH 075/224] CVE-2020-25719 tests/krb5: Add a test for making an S4U2Self request without a PAC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/s4u_tests.py | 37 ++++++++++++++++++++++++++-- selftest/knownfail_heimdal_kdc | 1 + 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index 593ef94c9103..a80a7b3427e0 100755 --- a/python/samba/tests/krb5/s4u_tests.py +++ b/python/samba/tests/krb5/s4u_tests.py @@ -256,6 +256,17 @@ class S4UKerberosTests(KDCBaseTest): if unexpected_flags is not None: unexpected_flags = krb5_asn1.TicketFlags(unexpected_flags) + expected_error_mode = kdc_dict.pop('expected_error_mode', 0) + expected_status = kdc_dict.pop('expected_status', None) + if expected_error_mode: + check_error_fn = self.generic_check_kdc_error + check_rep_fn = None + else: + check_error_fn = None + check_rep_fn = self.generic_check_kdc_rep + + self.assertIsNone(expected_status) + kdc_options = kdc_dict.pop('kdc_options', '0') kdc_options = krb5_asn1.KDCOptions(kdc_options) @@ -290,9 +301,11 @@ class S4UKerberosTests(KDCBaseTest): ticket_decryption_key=service_decryption_key, expect_ticket_checksum=True, generate_padata_fn=generate_s4u2self_padata, - check_rep_fn=self.generic_check_kdc_rep, + check_error_fn=check_error_fn, + check_rep_fn=check_rep_fn, check_kdc_private_fn=self.generic_check_kdc_private, - expected_error_mode=0, + expected_error_mode=expected_error_mode, + expected_status=expected_status, tgt=service_tgt, authenticator_subkey=authenticator_subkey, kdc_options=str(kdc_options), @@ -321,6 +334,26 @@ class S4UKerberosTests(KDCBaseTest): 'expected_flags': 'forwardable' }) + # Test performing an S4U2Self operation with a forwardable ticket that does + # not contain a PAC. The request should fail. + def test_s4u2self_no_pac(self): + def forwardable_no_pac(ticket): + ticket = self.set_ticket_forwardable(ticket, flag=True) + return self.remove_ticket_pac(ticket) + + self._run_s4u2self_test( + { + 'expected_error_mode': (KDC_ERR_GENERIC, + KDC_ERR_BADOPTION), + 'expected_status': ntstatus.NT_STATUS_INVALID_PARAMETER, + 'client_opts': { + 'not_delegated': False + }, + 'kdc_options': 'forwardable', + 'modify_service_tgt_fn': forwardable_no_pac, + 'expected_flags': 'forwardable' + }) + # Test performing an S4U2Self operation without requesting a forwardable # ticket. The resulting ticket should not have the 'forwardable' flag set. def test_s4u2self_without_forwardable(self): diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 410ba83123c8..f141efa86e5f 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -243,6 +243,7 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_client_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_service_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable +^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_no_pac ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed # # The lack of KRB5SignedPath means we no longer return -- 2.25.1 From 086a8955e205a3b35ebd03aad9ff50afa94c2c25 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 20:02:45 +1300 Subject: [PATCH 076/224] CVE-2020-25719 tests/krb5: Add principal aliasing test BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/alias_tests.py | 201 +++++++++++++++++++ python/samba/tests/krb5/rfc4120_constants.py | 1 + python/samba/tests/usage.py | 1 + selftest/knownfail_heimdal_kdc | 7 + selftest/knownfail_mit_kdc | 7 + source4/selftest/tests.py | 10 + 6 files changed, 227 insertions(+) create mode 100755 python/samba/tests/krb5/alias_tests.py diff --git a/python/samba/tests/krb5/alias_tests.py b/python/samba/tests/krb5/alias_tests.py new file mode 100755 index 000000000000..60213845a443 --- /dev/null +++ b/python/samba/tests/krb5/alias_tests.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# Unix SMB/CIFS implementation. +# Copyright (C) Stefan Metzmacher 2020 +# Copyright (C) 2021 Catalyst.Net Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import sys +import os + +import ldb + +from samba.tests import delete_force +import samba.tests.krb5.kcrypto as kcrypto +from samba.tests.krb5.kdc_base_test import KDCBaseTest +from samba.tests.krb5.rfc4120_constants import ( + AES256_CTS_HMAC_SHA1_96, + ARCFOUR_HMAC_MD5, + KDC_ERR_CLIENT_NAME_MISMATCH, + NT_PRINCIPAL, +) + +sys.path.insert(0, 'bin/python') +os.environ['PYTHONUNBUFFERED'] = '1' + +global_asn1_print = False +global_hexdump = False + + +class AliasTests(KDCBaseTest): + def test_dc_alias_rename(self): + self._run_dc_alias(action='rename') + + def test_dc_alias_delete(self): + self._run_dc_alias(action='delete') + + def _run_dc_alias(self, action=None): + target_creds = self.get_dc_creds() + target_name = target_creds.get_username()[:-1] + + self._run_alias(target_name, lambda: target_creds, action=action) + + def test_create_alias_rename(self): + self._run_create_alias(action='rename') + + def test_create_alias_delete(self): + self._run_create_alias(action='delete') + + def _run_create_alias(self, action=None): + target_name = self.get_new_username() + + def create_target(): + samdb = self.get_samdb() + + realm = samdb.domain_dns_name().lower() + + hostname = f'{target_name}.{realm}' + spn = f'ldap/{hostname}' + + details = { + 'dNSHostName': hostname + } + + creds, fn = self.create_account( + samdb, + target_name, + account_type=self.AccountType.COMPUTER, + spn=spn, + additional_details=details) + + return creds + + self._run_alias(target_name, create_target, action=action) + + def _run_alias(self, target_name, target_creds_fn, action=None): + samdb = self.get_samdb() + + mach_name = self.get_new_username() + + # Create a machine account. + mach_creds, mach_dn = self.create_account( + samdb, mach_name, account_type=self.AccountType.COMPUTER) + self.addCleanup(delete_force, samdb, mach_dn) + + mach_sid = self.get_objectSid(samdb, mach_dn) + realm = mach_creds.get_realm() + + # The account salt doesn't change when the account is renamed. + old_salt = mach_creds.get_salt() + mach_creds.set_forced_salt(old_salt) + + # Rename the account to alias with the target account. + msg = ldb.Message(ldb.Dn(samdb, mach_dn)) + msg['sAMAccountName'] = ldb.MessageElement(target_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + mach_creds.set_username(target_name) + + # Get a TGT for the machine account. + tgt = self.get_tgt(mach_creds, kdc_options='0', fresh=True) + + # Check the PAC. + pac_data = self.get_pac_data(tgt.ticket_private['authorization-data']) + + upn = f'{target_name}@{realm.lower()}' + + self.assertEqual(target_name, str(pac_data.account_name)) + self.assertEqual(mach_sid, pac_data.account_sid) + self.assertEqual(target_name, pac_data.logon_name) + self.assertEqual(upn, pac_data.upn) + self.assertEqual(realm, pac_data.domain_name) + + # Rename or delete the machine account. + if action == 'rename': + mach_name2 = self.get_new_username() + + msg = ldb.Message(ldb.Dn(samdb, mach_dn)) + msg['sAMAccountName'] = ldb.MessageElement(mach_name2, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + elif action == 'delete': + samdb.delete(mach_dn) + else: + self.fail(action) + + # Get the credentials for the target account. + target_creds = target_creds_fn() + + # Look up the DNS host name of the target account. + target_dn = target_creds.get_dn() + res = samdb.search(target_dn, + scope=ldb.SCOPE_BASE, + attrs=['dNSHostName']) + target_hostname = str(res[0].get('dNSHostName', idx=0)) + + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['ldap', target_hostname]) + target_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[target_name]) + + target_decryption_key = self.TicketDecryptionKey_from_creds( + target_creds) + + authenticator_subkey = self.RandomKey(kcrypto.Enctype.AES256) + + etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5) + + def generate_s4u2self_padata(_kdc_exchange_dict, + _callback_dict, + req_body): + padata = self.PA_S4U2Self_create(name=target_cname, + realm=realm, + tgt_session_key=tgt.session_key, + ctype=None) + return [padata], req_body + + expected_error_mode = KDC_ERR_CLIENT_NAME_MISMATCH + + # Make a request using S4U2Self. The request should fail. + kdc_exchange_dict = self.tgs_exchange_dict( + expected_crealm=realm, + expected_cname=target_cname, + expected_srealm=realm, + expected_sname=sname, + ticket_decryption_key=target_decryption_key, + generate_padata_fn=generate_s4u2self_padata, + expected_error_mode=expected_error_mode, + check_error_fn=self.generic_check_kdc_error, + check_kdc_private_fn=self.generic_check_kdc_private, + tgt=tgt, + authenticator_subkey=authenticator_subkey, + kdc_options='0', + expect_pac=True) + + rep = self._generic_kdc_exchange(kdc_exchange_dict, + cname=None, + realm=realm, + sname=sname, + etypes=etypes) + self.check_error_rep(rep, expected_error_mode) + + +if __name__ == '__main__': + global_asn1_print = False + global_hexdump = False + import unittest + unittest.main() diff --git a/python/samba/tests/krb5/rfc4120_constants.py b/python/samba/tests/krb5/rfc4120_constants.py index 39bb2db8e329..b643185f7676 100644 --- a/python/samba/tests/krb5/rfc4120_constants.py +++ b/python/samba/tests/krb5/rfc4120_constants.py @@ -81,6 +81,7 @@ KDC_ERR_SKEW = 37 KDC_ERR_MODIFIED = 41 KDC_ERR_INAPP_CKSUM = 50 KDC_ERR_GENERIC = 60 +KDC_ERR_CLIENT_NAME_MISMATCH = 75 KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS = 93 # Extended error types diff --git a/python/samba/tests/usage.py b/python/samba/tests/usage.py index 966056d80c35..7b4c09ab939b 100644 --- a/python/samba/tests/usage.py +++ b/python/samba/tests/usage.py @@ -106,6 +106,7 @@ EXCLUDE_USAGE = { 'python/samba/tests/krb5/rodc_tests.py', 'python/samba/tests/krb5/salt_tests.py', 'python/samba/tests/krb5/spn_tests.py', + 'python/samba/tests/krb5/alias_tests.py', } EXCLUDE_HELP = { diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index f141efa86e5f..5e0d958ee595 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -271,3 +271,10 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac +# +# Alias tests +# +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_delete +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_rename +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_delete +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_rename diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 8612d05b3da6..4441be9d2039 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -386,3 +386,10 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer +# +# Alias tests +# +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_delete +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_rename +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_delete +^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_rename diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index afaf0719cf6c..13a3dc4f3e9c 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1512,6 +1512,16 @@ planpythontestsuite( 'FAST_SUPPORT': have_fast_support, 'TKT_SIG_SUPPORT': tkt_sig_support }) +planpythontestsuite( + "ad_dc", + "samba.tests.krb5.alias_tests", + environ={ + 'ADMIN_USERNAME': '$USERNAME', + 'ADMIN_PASSWORD': '$PASSWORD', + 'STRICT_CHECKING': '0', + 'FAST_SUPPORT': have_fast_support, + 'TKT_SIG_SUPPORT': tkt_sig_support + }) for env in [ 'vampire_dc', -- 2.25.1 From a11c8aabbc6f454406111232a1351bf50d039e20 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 11:45:23 +1300 Subject: [PATCH 077/224] CVE-2020-25718 tests/krb5: Add tests for RODC-printed and invalid TGTs BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 6 +- python/samba/tests/krb5/kdc_tgs_tests.py | 808 +++++++++++++++++++ python/samba/tests/krb5/rfc4120_constants.py | 1 + selftest/knownfail_heimdal_kdc | 64 ++ selftest/knownfail_mit_kdc | 67 ++ 5 files changed, 944 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index cc23484ba2cb..4fe7485c4928 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -657,7 +657,8 @@ class KDCBaseTest(RawKerberosTest): 'delegation_to_spn': None, 'delegation_from_dn': None, 'trusted_to_auth_for_delegation': False, - 'fast_support': False + 'fast_support': False, + 'id': None } account_opts = { @@ -698,7 +699,8 @@ class KDCBaseTest(RawKerberosTest): delegation_to_spn, delegation_from_dn, trusted_to_auth_for_delegation, - fast_support): + fast_support, + id): if account_type is self.AccountType.USER: self.assertIsNone(spn) self.assertIsNone(delegation_to_spn) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index fbeb5fe61fbb..74f1032163eb 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -20,6 +20,13 @@ import sys import os +import ldb + + +from samba import dsdb + +from samba.dcerpc import krb5pac + sys.path.insert(0, "bin/python") os.environ["PYTHONUNBUFFERED"] = "1" @@ -32,6 +39,10 @@ from samba.tests.krb5.rfc4120_constants import ( KRB_TGS_REP, KDC_ERR_BADMATCH, KDC_ERR_BADOPTION, + KDC_ERR_CLIENT_NAME_MISMATCH, + KDC_ERR_POLICY, + KDC_ERR_S_PRINCIPAL_UNKNOWN, + KDC_ERR_TGT_REVOKED, NT_PRINCIPAL, NT_SRV_INST, ) @@ -422,6 +433,803 @@ class KdcTgsTests(KDCBaseTest): self._make_tgs_request(client_creds, service_creds, tgt, expect_pac=False, expect_error=True) + # Test making a TGS request. + def test_tgs_req(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + self._run_tgs(tgt, expected_error=0) + + def test_renew_req(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, renewable=True) + self._renew_tgt(tgt, expected_error=0) + + def test_validate_req(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, invalid=True) + self._validate_tgt(tgt, expected_error=0) + + def test_s4u2self_req(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + self._s4u2self(tgt, creds, expected_error=0) + + def test_user2user_req(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + self._user2user(tgt, creds, expected_error=0) + + # Test making a request without a PAC. + def test_tgs_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True) + self._run_tgs(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_renew_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, renewable=True, remove_pac=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_validate_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, invalid=True, remove_pac=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_s4u2self_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_BADOPTION) + + def test_user2user_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_BADOPTION) + + # Test changing the SID in the PAC to that of another account. + def test_tgs_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_renew_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, renewable=True, new_rid=existing_rid) + self._renew_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_validate_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, invalid=True, new_rid=existing_rid) + self._validate_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_s4u2self_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid) + self._s4u2self(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_user2user_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid) + self._user2user(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + # Test changing the SID in the PAC to a non-existent one. + def test_tgs_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_renew_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, renewable=True, + new_rid=nonexistent_rid) + self._renew_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_validate_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, invalid=True, + new_rid=nonexistent_rid) + self._validate_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_s4u2self_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid) + self._s4u2self(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_user2user_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid) + self._user2user(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + # Test with an RODC-issued ticket where the client is revealed to the RODC. + def test_tgs_rodc_revealed(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._run_tgs(tgt, expected_error=0) + + def test_renew_rodc_revealed(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._renew_tgt(tgt, expected_error=0) + + def test_validate_rodc_revealed(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._validate_tgt(tgt, expected_error=0) + + def test_s4u2self_rodc_revealed(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._s4u2self(tgt, creds, expected_error=0) + + def test_user2user_rodc_revealed(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._user2user(tgt, creds, expected_error=0) + + # Test with an RODC-issued ticket where the SID in the PAC is changed to + # that of another account. + def test_tgs_rodc_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_renew_rodc_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True, + new_rid=existing_rid) + self._renew_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_validate_rodc_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True, + new_rid=existing_rid) + self._validate_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_s4u2self_rodc_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_user2user_rodc_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) + self._user2user(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + # Test with an RODC-issued ticket where the SID in the PAC is changed to a + # non-existent one. + def test_tgs_rodc_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_renew_rodc_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, renewable=True, from_rodc=True, + new_rid=nonexistent_rid) + self._renew_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_validate_rodc_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, invalid=True, from_rodc=True, + new_rid=nonexistent_rid) + self._validate_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_s4u2self_rodc_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_user2user_rodc_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid) + self._user2user(tgt, creds, + expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + # Test with an RODC-issued ticket where the client is not revealed to the + # RODC. + def test_tgs_rodc_not_revealed(self): + creds = self._get_creds(replication_allowed=True) + tgt = self._get_tgt(creds, from_rodc=True) + # TODO: error code + self._run_tgs(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_renew_rodc_not_revealed(self): + creds = self._get_creds(replication_allowed=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_validate_rodc_not_revealed(self): + creds = self._get_creds(replication_allowed=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_s4u2self_rodc_not_revealed(self): + creds = self._get_creds(replication_allowed=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + def test_user2user_rodc_not_revealed(self): + creds = self._get_creds(replication_allowed=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + # Test with an RODC-issued ticket where the RODC account does not have the + # PARTIAL_SECRETS bit set. + def test_tgs_rodc_no_partial_secrets(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_partial_secrets() + self._run_tgs(tgt, expected_error=KDC_ERR_POLICY) + + def test_renew_rodc_no_partial_secrets(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._remove_rodc_partial_secrets() + self._renew_tgt(tgt, expected_error=KDC_ERR_POLICY) + + def test_validate_rodc_no_partial_secrets(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._remove_rodc_partial_secrets() + self._validate_tgt(tgt, expected_error=KDC_ERR_POLICY) + + def test_s4u2self_rodc_no_partial_secrets(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_partial_secrets() + self._s4u2self(tgt, creds, expected_error=KDC_ERR_POLICY) + + def test_user2user_rodc_no_partial_secrets(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_partial_secrets() + self._user2user(tgt, creds, expected_error=KDC_ERR_POLICY) + + # Test with an RODC-issued ticket where the RODC account does not have an + # msDS-KrbTgtLink. + def test_tgs_rodc_no_krbtgt_link(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_krbtgt_link() + self._run_tgs(tgt, expected_error=KDC_ERR_POLICY) + + def test_renew_rodc_no_krbtgt_link(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._remove_rodc_krbtgt_link() + self._renew_tgt(tgt, expected_error=KDC_ERR_POLICY) + + def test_validate_rodc_no_krbtgt_link(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._remove_rodc_krbtgt_link() + self._validate_tgt(tgt, expected_error=KDC_ERR_POLICY) + + def test_s4u2self_rodc_no_krbtgt_link(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_krbtgt_link() + self._s4u2self(tgt, creds, expected_error=KDC_ERR_POLICY) + + def test_user2user_rodc_no_krbtgt_link(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._remove_rodc_krbtgt_link() + self._user2user(tgt, creds, expected_error=KDC_ERR_POLICY) + + # Test with an RODC-issued ticket where the client is not allowed to + # replicate to the RODC. + def test_tgs_rodc_not_allowed(self): + creds = self._get_creds(revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._run_tgs(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_renew_rodc_not_allowed(self): + creds = self._get_creds(revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_validate_rodc_not_allowed(self): + creds = self._get_creds(revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_s4u2self_rodc_not_allowed(self): + creds = self._get_creds(revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + def test_user2user_rodc_not_allowed(self): + creds = self._get_creds(revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + # Test with an RODC-issued ticket where the client is denied from + # replicating to the RODC. + def test_tgs_rodc_denied(self): + creds = self._get_creds(replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._run_tgs(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_renew_rodc_denied(self): + creds = self._get_creds(replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_validate_rodc_denied(self): + creds = self._get_creds(replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_s4u2self_rodc_denied(self): + creds = self._get_creds(replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + def test_user2user_rodc_denied(self): + creds = self._get_creds(replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + # Test with an RODC-issued ticket where the client is both allowed and + # denied replicating to the RODC. + def test_tgs_rodc_allowed_denied(self): + creds = self._get_creds(replication_allowed=True, + replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._run_tgs(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_renew_rodc_allowed_denied(self): + creds = self._get_creds(replication_allowed=True, + replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, renewable=True, from_rodc=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_validate_rodc_allowed_denied(self): + creds = self._get_creds(replication_allowed=True, + replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, invalid=True, from_rodc=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_TGT_REVOKED) + + def test_s4u2self_rodc_allowed_denied(self): + creds = self._get_creds(replication_allowed=True, + replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + def test_user2user_rodc_allowed_denied(self): + creds = self._get_creds(replication_allowed=True, + replication_denied=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_TGT_REVOKED) + + # Test user-to-user with incorrect service principal names. + def test_user2user_matching_sname_host(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + user_name = self._get_mach_creds().get_username() + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['host', user_name]) + + self._user2user(tgt, creds, sname=sname, + expected_error=KDC_ERR_S_PRINCIPAL_UNKNOWN) + + def test_user2user_matching_sname_no_host(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + user_name = self._get_mach_creds().get_username() + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[user_name]) + + self._user2user(tgt, creds, sname=sname, + expected_error=KDC_ERR_BADMATCH) + + def test_user2user_wrong_sname(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + other_creds = self.get_service_creds() + user_name = other_creds.get_username() + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[user_name]) + + self._user2user(tgt, creds, sname=sname, + expected_error=(KDC_ERR_BADMATCH, + KDC_ERR_BADOPTION)) + + def test_user2user_non_existent_sname(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['host', 'non_existent_user']) + + self._user2user(tgt, creds, sname=sname, + expected_error=KDC_ERR_S_PRINCIPAL_UNKNOWN) + + def test_user2user_service_ticket(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + service_creds = self.get_service_creds() + service_ticket = self.get_service_ticket(tgt, service_creds) + + self._user2user(service_ticket, creds, expected_error=KDC_ERR_POLICY) + + def _get_tgt(self, + client_creds, + renewable=False, + invalid=False, + from_rodc=False, + new_rid=None, + remove_pac=False): + self.assertFalse(renewable and invalid) + + if remove_pac: + self.assertIsNone(new_rid) + + tgt = self.get_tgt(client_creds) + + if from_rodc: + krbtgt_creds = self.get_mock_rodc_krbtgt_creds() + else: + krbtgt_creds = self.get_krbtgt_creds() + + if new_rid is not None: + def change_sid_fn(pac): + for pac_buffer in pac.buffers: + if pac_buffer.type == krb5pac.PAC_TYPE_LOGON_INFO: + logon_info = pac_buffer.info.info + + logon_info.info3.base.rid = new_rid + + return pac + + modify_pac_fn = change_sid_fn + else: + modify_pac_fn = None + + krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) + + if remove_pac: + checksum_keys = None + else: + checksum_keys = { + krb5pac.PAC_TYPE_KDC_CHECKSUM: krbtgt_key + } + + if renewable: + def set_renewable(enc_part): + # Set the renewable flag. + renewable_flag = krb5_asn1.TicketFlags('renewable') + pos = len(tuple(renewable_flag)) - 1 + + flags = enc_part['flags'] + self.assertLessEqual(pos, len(flags)) + + new_flags = flags[:pos] + '1' + flags[pos + 1:] + enc_part['flags'] = new_flags + + # Set the renew-till time to be in the future. + renew_till = self.get_KerberosTime(offset=100 * 60 * 60) + enc_part['renew-till'] = renew_till + + return enc_part + + modify_fn = set_renewable + elif invalid: + def set_invalid(enc_part): + # Set the invalid flag. + invalid_flag = krb5_asn1.TicketFlags('invalid') + pos = len(tuple(invalid_flag)) - 1 + + flags = enc_part['flags'] + self.assertLessEqual(pos, len(flags)) + + new_flags = flags[:pos] + '1' + flags[pos + 1:] + enc_part['flags'] = new_flags + + # Set the ticket start time to be in the past. + past_time = self.get_KerberosTime(offset=-100 * 60 * 60) + enc_part['starttime'] = past_time + + return enc_part + + modify_fn = set_invalid + else: + modify_fn = None + + return self.modified_ticket( + tgt, + new_ticket_key=krbtgt_key, + modify_fn=modify_fn, + modify_pac_fn=modify_pac_fn, + exclude_pac=remove_pac, + update_pac_checksums=not remove_pac, + checksum_keys=checksum_keys) + + def _remove_rodc_partial_secrets(self): + samdb = self.get_samdb() + + rodc_ctx = self.get_mock_rodc_ctx() + rodc_dn = ldb.Dn(samdb, rodc_ctx.acct_dn) + + def add_rodc_partial_secrets(): + msg = ldb.Message() + msg.dn = rodc_dn + msg['userAccountControl'] = ldb.MessageElement( + str(rodc_ctx.userAccountControl), + ldb.FLAG_MOD_REPLACE, + 'userAccountControl') + samdb.modify(msg) + + self.addCleanup(add_rodc_partial_secrets) + + uac = rodc_ctx.userAccountControl & ~dsdb.UF_PARTIAL_SECRETS_ACCOUNT + + msg = ldb.Message() + msg.dn = rodc_dn + msg['userAccountControl'] = ldb.MessageElement( + str(uac), + ldb.FLAG_MOD_REPLACE, + 'userAccountControl') + samdb.modify(msg) + + def _remove_rodc_krbtgt_link(self): + samdb = self.get_samdb() + + rodc_ctx = self.get_mock_rodc_ctx() + rodc_dn = ldb.Dn(samdb, rodc_ctx.acct_dn) + + def add_rodc_krbtgt_link(): + msg = ldb.Message() + msg.dn = rodc_dn + msg['msDS-KrbTgtLink'] = ldb.MessageElement( + rodc_ctx.new_krbtgt_dn, + ldb.FLAG_MOD_ADD, + 'msDS-KrbTgtLink') + samdb.modify(msg) + + self.addCleanup(add_rodc_krbtgt_link) + + msg = ldb.Message() + msg.dn = rodc_dn + msg['msDS-KrbTgtLink'] = ldb.MessageElement( + [], + ldb.FLAG_MOD_DELETE, + 'msDS-KrbTgtLink') + samdb.modify(msg) + + def _get_creds(self, + replication_allowed=False, + replication_denied=False, + revealed_to_rodc=False): + return self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={ + 'allowed_replication_mock': replication_allowed, + 'denied_replication_mock': replication_denied, + 'revealed_to_mock_rodc': revealed_to_rodc, + 'id': 0 + }) + + def _get_existing_rid(self, + replication_allowed=False, + replication_denied=False, + revealed_to_rodc=False): + other_creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={ + 'allowed_replication_mock': replication_allowed, + 'denied_replication_mock': replication_denied, + 'revealed_to_mock_rodc': revealed_to_rodc, + 'id': 1 + }) + + samdb = self.get_samdb() + + other_dn = other_creds.get_dn() + other_sid = self.get_objectSid(samdb, other_dn) + + other_rid = int(other_sid.rsplit('-', 1)[1]) + + return other_rid + + def _get_mach_creds(self): + return self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={ + 'allowed_replication_mock': True, + 'denied_replication_mock': False, + 'revealed_to_mock_rodc': True, + 'id': 2 + }) + + def _get_non_existent_rid(self): + return (1 << 30) - 1 + + def _run_tgs(self, tgt, expected_error): + target_creds = self.get_service_creds() + self._tgs_req(tgt, expected_error, target_creds) + + def _renew_tgt(self, tgt, expected_error): + krbtgt_creds = self.get_krbtgt_creds() + kdc_options = str(krb5_asn1.KDCOptions('renew')) + self._tgs_req(tgt, expected_error, krbtgt_creds, + kdc_options=kdc_options) + + def _validate_tgt(self, tgt, expected_error): + krbtgt_creds = self.get_krbtgt_creds() + kdc_options = str(krb5_asn1.KDCOptions('validate')) + self._tgs_req(tgt, expected_error, krbtgt_creds, + kdc_options=kdc_options) + + def _s4u2self(self, tgt, tgt_creds, expected_error): + user_creds = self._get_mach_creds() + + user_name = user_creds.get_username() + user_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[user_name]) + user_realm = user_creds.get_realm() + + def generate_s4u2self_padata(_kdc_exchange_dict, + _callback_dict, + req_body): + padata = self.PA_S4U2Self_create( + name=user_cname, + realm=user_realm, + tgt_session_key=tgt.session_key, + ctype=None) + + return [padata], req_body + + self._tgs_req(tgt, expected_error, tgt_creds, + expected_cname=user_cname, + generate_padata_fn=generate_s4u2self_padata, + expect_claims=False) + + def _user2user(self, tgt, tgt_creds, expected_error, sname=None): + user_creds = self._get_mach_creds() + user_tgt = self.get_tgt(user_creds) + + kdc_options = str(krb5_asn1.KDCOptions('enc-tkt-in-skey')) + self._tgs_req(user_tgt, expected_error, tgt_creds, + kdc_options=kdc_options, + additional_ticket=tgt, + sname=sname) + + def _tgs_req(self, tgt, expected_error, target_creds, + kdc_options='0', + expected_cname=None, + additional_ticket=None, + generate_padata_fn=None, + sname=None, + expect_claims=True): + srealm = target_creds.get_realm() + + if sname is None: + target_name = target_creds.get_username() + if target_name == 'krbtgt': + sname = self.PrincipalName_create(name_type=NT_SRV_INST, + names=[target_name, srealm]) + else: + if target_name[-1] == '$': + target_name = target_name[:-1] + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['host', target_name]) + + if additional_ticket is not None: + additional_tickets = [additional_ticket.ticket] + decryption_key = additional_ticket.session_key + else: + additional_tickets = None + decryption_key = self.TicketDecryptionKey_from_creds( + target_creds) + + subkey = self.RandomKey(tgt.session_key.etype) + + etypes = (AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5) + + if expected_error: + check_error_fn = self.generic_check_kdc_error + check_rep_fn = None + else: + check_error_fn = None + check_rep_fn = self.generic_check_kdc_rep + + if expected_cname is None: + expected_cname = tgt.cname + + kdc_exchange_dict = self.tgs_exchange_dict( + expected_crealm=tgt.crealm, + expected_cname=expected_cname, + expected_srealm=srealm, + expected_sname=sname, + ticket_decryption_key=decryption_key, + generate_padata_fn=generate_padata_fn, + check_error_fn=check_error_fn, + check_rep_fn=check_rep_fn, + check_kdc_private_fn=self.generic_check_kdc_private, + expected_error_mode=expected_error, + tgt=tgt, + authenticator_subkey=subkey, + kdc_options=kdc_options, + expect_edata=False, + expect_claims=expect_claims) + + self._generic_kdc_exchange(kdc_exchange_dict, + cname=None, + realm=srealm, + sname=sname, + etypes=etypes, + additional_tickets=additional_tickets) + if __name__ == "__main__": global_asn1_print = False diff --git a/python/samba/tests/krb5/rfc4120_constants.py b/python/samba/tests/krb5/rfc4120_constants.py index b643185f7676..490cd255ec30 100644 --- a/python/samba/tests/krb5/rfc4120_constants.py +++ b/python/samba/tests/krb5/rfc4120_constants.py @@ -72,6 +72,7 @@ KDC_ERR_POLICY = 12 KDC_ERR_BADOPTION = 13 KDC_ERR_ETYPE_NOSUPP = 14 KDC_ERR_SUMTYPE_NOSUPP = 15 +KDC_ERR_TGT_REVOKED = 20 KDC_ERR_PREAUTH_FAILED = 24 KDC_ERR_PREAUTH_REQUIRED = 25 KDC_ERR_BAD_INTEGRITY = 31 diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 5e0d958ee595..27c2dfe3be3a 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -278,3 +278,67 @@ ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_rename ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_delete ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_rename +# +# KDC TGT tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_non_existent_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 4441be9d2039..84f74e5523ee 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -393,3 +393,70 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_rename ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_delete ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_rename +# +# KDC TGT tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_req +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_krbtgt_link +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_partial_secrets +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_allowed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_revealed +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting -- 2.25.1 From 736229876ad696f09115f275dabfbbbab3a035e1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 16:20:07 +1300 Subject: [PATCH 078/224] CVE-2020-25719 tests/krb5: Add tests for including authdata without a PAC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 32 +++++++++++++++++++++++- python/samba/tests/krb5/raw_testcase.py | 14 +++++++---- selftest/knownfail_heimdal_kdc | 5 ++++ selftest/knownfail_mit_kdc | 5 ++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 74f1032163eb..5de79c30e1be 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -485,6 +485,34 @@ class KdcTgsTests(KDCBaseTest): tgt = self._get_tgt(creds, remove_pac=True) self._user2user(tgt, creds, expected_error=KDC_ERR_BADOPTION) + # Test making a request with authdata and without a PAC. + def test_tgs_authdata_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True, allow_empty_authdata=True) + self._run_tgs(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_renew_authdata_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, renewable=True, remove_pac=True, + allow_empty_authdata=True) + self._renew_tgt(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_validate_authdata_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, invalid=True, remove_pac=True, + allow_empty_authdata=True) + self._validate_tgt(tgt, expected_error=KDC_ERR_BADOPTION) + + def test_s4u2self_authdata_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True, allow_empty_authdata=True) + self._s4u2self(tgt, creds, expected_error=KDC_ERR_BADOPTION) + + def test_user2user_authdata_no_pac(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac=True, allow_empty_authdata=True) + self._user2user(tgt, creds, expected_error=KDC_ERR_BADOPTION) + # Test changing the SID in the PAC to that of another account. def test_tgs_sid_mismatch_existing(self): creds = self._get_creds() @@ -928,7 +956,8 @@ class KdcTgsTests(KDCBaseTest): invalid=False, from_rodc=False, new_rid=None, - remove_pac=False): + remove_pac=False, + allow_empty_authdata=False): self.assertFalse(renewable and invalid) if remove_pac: @@ -1011,6 +1040,7 @@ class KdcTgsTests(KDCBaseTest): modify_fn=modify_fn, modify_pac_fn=modify_pac_fn, exclude_pac=remove_pac, + allow_empty_authdata=allow_empty_authdata, update_pac_checksums=not remove_pac, checksum_keys=checksum_keys) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 8e55790272a7..b5ac393ea673 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -3224,6 +3224,7 @@ class RawKerberosTest(TestCaseInTempDir): modify_fn=None, modify_pac_fn=None, exclude_pac=False, + allow_empty_authdata=False, update_pac_checksums=True, checksum_keys=None, include_checksums=None): @@ -3332,8 +3333,10 @@ class RawKerberosTest(TestCaseInTempDir): # Replace the PAC in the authorization data and re-add it to the # ticket enc-part. - auth_data, _ = self.replace_pac(auth_data, new_pac, - expect_pac=expect_pac) + auth_data, _ = self.replace_pac( + auth_data, new_pac, + expect_pac=expect_pac, + allow_empty_authdata=allow_empty_authdata) enc_part['authorization-data'] = auth_data # Re-encrypt the ticket enc-part with the new key. @@ -3454,7 +3457,8 @@ class RawKerberosTest(TestCaseInTempDir): kdc_checksum_buffer.info.signature = kdc_checksum - def replace_pac(self, auth_data, new_pac, expect_pac=True): + def replace_pac(self, auth_data, new_pac, expect_pac=True, + allow_empty_authdata=False): if new_pac is not None: self.assertElementEqual(new_pac, 'ad-type', AD_WIN2K_PAC) self.assertElementPresent(new_pac, 'ad-data') @@ -3483,7 +3487,7 @@ class RawKerberosTest(TestCaseInTempDir): if expect_pac: self.assertIsNotNone(old_pac, 'Expected PAC') - if relevant_elems: + if relevant_elems or allow_empty_authdata: ad_relevant = self.der_encode( relevant_elems, asn1Spec=krb5_asn1.AD_IF_RELEVANT()) @@ -3494,7 +3498,7 @@ class RawKerberosTest(TestCaseInTempDir): else: authdata_elem = None - if authdata_elem is not None: + if authdata_elem is not None or allow_empty_authdata: new_auth_data.append(authdata_elem) if expect_pac: diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 27c2dfe3be3a..572fbb0e0cde 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -281,6 +281,7 @@ # # KDC TGT tests # +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied @@ -292,6 +293,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied @@ -303,6 +305,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied @@ -314,6 +317,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac @@ -331,6 +335,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 84f74e5523ee..127fcdc425d5 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -396,6 +396,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ # # KDC TGT tests # +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied @@ -408,6 +409,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied @@ -421,6 +423,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied @@ -433,6 +436,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req @@ -448,6 +452,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied -- 2.25.1 From 237e26f005df9f273bee37a6eb0b6187932eaf83 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 16:46:56 +1300 Subject: [PATCH 079/224] CVE-2020-25721 tests/krb5: Add tests for extended PAC_UPN_DNS_INFO PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14835 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 3 +- python/samba/tests/krb5/kdc_tgs_tests.py | 51 +++++++++++++++++++++++- python/samba/tests/krb5/raw_testcase.py | 41 +++++++++++++++++++ python/samba/tests/krb5/s4u_tests.py | 2 + selftest/knownfail_heimdal_kdc | 4 ++ selftest/knownfail_mit_kdc | 4 ++ 6 files changed, 103 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 4fe7485c4928..9be6cbab30bc 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1358,7 +1358,7 @@ class KDCBaseTest(RawKerberosTest): def get_tgt(self, creds, to_rodc=False, kdc_options=None, expected_flags=None, unexpected_flags=None, - expected_account_name=None, + expected_account_name=None, expected_upn_name=None, expected_sid=None, pac_request=True, expect_pac=True, fresh=False): user_name = creds.get_username() @@ -1410,6 +1410,7 @@ class KDCBaseTest(RawKerberosTest): expected_srealm=realm, expected_sname=sname, expected_account_name=expected_account_name, + expected_upn_name=expected_upn_name, expected_sid=expected_sid, expected_salt=salt, expected_flags=expected_flags, diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 5de79c30e1be..5313dbc6045f 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -227,7 +227,10 @@ class KdcTgsTests(KDCBaseTest): def _make_tgs_request(self, client_creds, service_creds, tgt, pac_request=None, expect_pac=True, - expect_error=False): + expect_error=False, + expected_account_name=None, + expected_upn_name=None, + expected_sid=None): client_account = client_creds.get_username() cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[client_account]) @@ -268,6 +271,9 @@ class KdcTgsTests(KDCBaseTest): expected_cname=expected_cname, expected_srealm=expected_srealm, expected_sname=expected_sname, + expected_account_name=expected_account_name, + expected_upn_name=expected_upn_name, + expected_sid=expected_sid, expected_supported_etypes=expected_supported_etypes, ticket_decryption_key=target_decryption_key, check_error_fn=check_error_fn, @@ -433,6 +439,49 @@ class KdcTgsTests(KDCBaseTest): self._make_tgs_request(client_creds, service_creds, tgt, expect_pac=False, expect_error=True) + def test_upn_dns_info_ex_user(self): + client_creds = self.get_client_creds() + self._run_upn_dns_info_ex_test(client_creds) + + def test_upn_dns_info_ex_mac(self): + mach_creds = self.get_mach_creds() + self._run_upn_dns_info_ex_test(mach_creds) + + def test_upn_dns_info_ex_upn_user(self): + client_creds = self.get_cached_creds( + account_type=self.AccountType.USER, + opts={'upn': 'upn_dns_info_test_upn0@bar'}) + self._run_upn_dns_info_ex_test(client_creds) + + def test_upn_dns_info_ex_upn_mac(self): + mach_creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={'upn': 'upn_dns_info_test_upn1@bar'}) + self._run_upn_dns_info_ex_test(mach_creds) + + def _run_upn_dns_info_ex_test(self, client_creds): + service_creds = self.get_service_creds() + + samdb = self.get_samdb() + dn = client_creds.get_dn() + + account_name = client_creds.get_username() + upn_name = client_creds.get_upn() + if upn_name is None: + realm = client_creds.get_realm().lower() + upn_name = f'{account_name}@{realm}' + sid = self.get_objectSid(samdb, dn) + + tgt = self.get_tgt(client_creds, + expected_account_name=account_name, + expected_upn_name=upn_name, + expected_sid=sid) + + self._make_tgs_request(client_creds, service_creds, tgt, + expected_account_name=account_name, + expected_upn_name=upn_name, + expected_sid=sid) + # Test making a TGS request. def test_tgs_req(self): creds = self._get_creds() diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index b5ac393ea673..18ee8738eaa5 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -1986,6 +1986,7 @@ class RawKerberosTest(TestCaseInTempDir): expected_srealm=None, expected_sname=None, expected_account_name=None, + expected_upn_name=None, expected_sid=None, expected_supported_etypes=None, expected_flags=None, @@ -2019,6 +2020,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_edata=None, expect_pac=True, expect_claims=True, + expect_upn_dns_info_ex=None, to_rodc=False): if expected_error_mode == 0: expected_error_mode = () @@ -2037,6 +2039,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expected_srealm': expected_srealm, 'expected_sname': expected_sname, 'expected_account_name': expected_account_name, + 'expected_upn_name': expected_upn_name, 'expected_sid': expected_sid, 'expected_supported_etypes': expected_supported_etypes, 'expected_flags': expected_flags, @@ -2070,6 +2073,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_edata': expect_edata, 'expect_pac': expect_pac, 'expect_claims': expect_claims, + 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, 'to_rodc': to_rodc } if callback_dict is None: @@ -2084,6 +2088,7 @@ class RawKerberosTest(TestCaseInTempDir): expected_srealm=None, expected_sname=None, expected_account_name=None, + expected_upn_name=None, expected_sid=None, expected_supported_etypes=None, expected_flags=None, @@ -2116,6 +2121,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_edata=None, expect_pac=True, expect_claims=True, + expect_upn_dns_info_ex=None, expected_proxy_target=None, expected_transited_services=None, to_rodc=False): @@ -2136,6 +2142,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expected_srealm': expected_srealm, 'expected_sname': expected_sname, 'expected_account_name': expected_account_name, + 'expected_upn_name': expected_upn_name, 'expected_sid': expected_sid, 'expected_supported_etypes': expected_supported_etypes, 'expected_flags': expected_flags, @@ -2168,6 +2175,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_edata': expect_edata, 'expect_pac': expect_pac, 'expect_claims': expect_claims, + 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, 'expected_proxy_target': expected_proxy_target, 'expected_transited_services': expected_transited_services, 'to_rodc': to_rodc @@ -2584,6 +2592,12 @@ class RawKerberosTest(TestCaseInTempDir): expected_account_name = kdc_exchange_dict['expected_account_name'] expected_sid = kdc_exchange_dict['expected_sid'] + expect_upn_dns_info_ex = kdc_exchange_dict['expect_upn_dns_info_ex'] + if expect_upn_dns_info_ex is None and ( + expected_account_name is not None + or expected_sid is not None): + expect_upn_dns_info_ex = True + for pac_buffer in pac.buffers: if pac_buffer.type == krb5pac.PAC_TYPE_CONSTRAINED_DELEGATION: expected_proxy_target = kdc_exchange_dict[ @@ -2618,6 +2632,31 @@ class RawKerberosTest(TestCaseInTempDir): expected_rid = int(expected_sid.rsplit('-', 1)[1]) self.assertEqual(expected_rid, logon_info.rid) + elif pac_buffer.type == krb5pac.PAC_TYPE_UPN_DNS_INFO: + upn_dns_info = pac_buffer.info + upn_dns_info_ex = upn_dns_info.ex + + expected_realm = kdc_exchange_dict['expected_crealm'] + self.assertEqual(expected_realm, + upn_dns_info.dns_domain_name) + + expected_upn_name = kdc_exchange_dict['expected_upn_name'] + if expected_upn_name is not None: + self.assertEqual(expected_upn_name, + upn_dns_info.upn_name) + + if expect_upn_dns_info_ex: + self.assertIsNotNone(upn_dns_info_ex) + + if upn_dns_info_ex is not None: + if expected_account_name is not None: + self.assertEqual(expected_account_name, + upn_dns_info_ex.samaccountname) + + if expected_sid is not None: + self.assertEqual(expected_sid, + str(upn_dns_info_ex.objectsid)) + def generic_check_kdc_error(self, kdc_exchange_dict, callback_dict, @@ -3600,6 +3639,7 @@ class RawKerberosTest(TestCaseInTempDir): padata, kdc_options, expected_account_name=None, + expected_upn_name=None, expected_sid=None, expected_flags=None, unexpected_flags=None, @@ -3634,6 +3674,7 @@ class RawKerberosTest(TestCaseInTempDir): expected_srealm=expected_srealm, expected_sname=expected_sname, expected_account_name=expected_account_name, + expected_upn_name=expected_upn_name, expected_sid=expected_sid, expected_supported_etypes=expected_supported_etypes, ticket_decryption_key=ticket_decryption_key, diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index a80a7b3427e0..5005affd6b39 100755 --- a/python/samba/tests/krb5/s4u_tests.py +++ b/python/samba/tests/krb5/s4u_tests.py @@ -309,6 +309,7 @@ class S4UKerberosTests(KDCBaseTest): tgt=service_tgt, authenticator_subkey=authenticator_subkey, kdc_options=str(kdc_options), + expect_upn_dns_info_ex=False, expect_claims=False) self._generic_kdc_exchange(kdc_exchange_dict, @@ -610,6 +611,7 @@ class S4UKerberosTests(KDCBaseTest): kdc_options=kdc_options, pac_options=pac_options, expect_edata=expect_edata, + expect_upn_dns_info_ex=False, expected_proxy_target=expected_proxy_target, expected_transited_services=expected_transited_services, expect_pac=expect_pac) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 572fbb0e0cde..468668235907 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -317,6 +317,10 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 127fcdc425d5..d2acc5559ed5 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -436,6 +436,10 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac -- 2.25.1 From d18434c7d47c328dea317552d6f11bce2f8fd296 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 24 Aug 2021 17:11:24 +0200 Subject: [PATCH 080/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Add tests for connecting to services anonymously and without a PAC At the end of the patchset we assume NT_STATUS_NO_IMPERSONATION_TOKEN if no PAC is available. For now we want to look for ACCESS_DENIED as this allows the test to pass (showing that gensec:require_pac = true is a useful partial mitigation). This will also help others doing backports that do not take the full patch set. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/test_ccache.py | 31 +++++++++--- python/samba/tests/krb5/test_ldap.py | 70 ++++++++++++++++++++++---- python/samba/tests/krb5/test_rpc.py | 46 ++++++++++++++--- python/samba/tests/krb5/test_smb.py | 31 +++++++++--- source4/selftest/tests.py | 17 ++++--- 5 files changed, 158 insertions(+), 37 deletions(-) diff --git a/python/samba/tests/krb5/test_ccache.py b/python/samba/tests/krb5/test_ccache.py index 040ae5cc9a1f..cb5061b92d9c 100755 --- a/python/samba/tests/krb5/test_ccache.py +++ b/python/samba/tests/krb5/test_ccache.py @@ -21,10 +21,11 @@ import sys import os from ldb import SCOPE_SUBTREE -from samba import gensec +from samba import NTSTATUSError, gensec from samba.auth import AuthContext from samba.dcerpc import security from samba.ndr import ndr_unpack +from samba.ntstatus import NT_STATUS_ACCESS_DENIED from samba.tests.krb5.kdc_base_test import KDCBaseTest @@ -41,11 +42,18 @@ class CcacheTests(KDCBaseTest): """ def test_ccache(self): + self._run_ccache_test("ccacheusr") + + def test_ccache_no_pac(self): + self._run_ccache_test("ccacheusr_nopac", include_pac=False, + expect_anon=True, allow_error=True) + + def _run_ccache_test(self, user_name, include_pac=True, + expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the # user are stored. - user_name = "ccacheusr" mach_name = "ccachemac" service = "host" @@ -67,7 +75,10 @@ class CcacheTests(KDCBaseTest): # ticket, to ensure that the krbtgt ticket doesn't also need to be # stored. (creds, cachefile) = self.create_ccache_with_user(user_credentials, - mach_credentials) + mach_credentials, + pac=include_pac) + # Remove the cached credentials file. + self.addCleanup(os.remove, cachefile.name) # Authenticate in-process to the machine account using the user's # cached credentials. @@ -117,7 +128,16 @@ class CcacheTests(KDCBaseTest): sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) # Retrieve the SIDs from the security token. - session = gensec_server.session_info() + try: + session = gensec_server.session_info() + except NTSTATUSError as e: + if not allow_error: + self.fail() + + enum, _ = e.args + self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + return + token = session.security_token token_sids = token.sids self.assertGreater(len(token_sids), 0) @@ -125,9 +145,6 @@ class CcacheTests(KDCBaseTest): # Ensure that they match. self.assertEqual(sid, token_sids[0]) - # Remove the cached credentials file. - os.remove(cachefile.name) - if __name__ == "__main__": global_asn1_print = False diff --git a/python/samba/tests/krb5/test_ldap.py b/python/samba/tests/krb5/test_ldap.py index 7d9ffebe2985..31e50487338b 100755 --- a/python/samba/tests/krb5/test_ldap.py +++ b/python/samba/tests/krb5/test_ldap.py @@ -20,10 +20,11 @@ import sys import os -from ldb import SCOPE_BASE, SCOPE_SUBTREE +from ldb import LdbError, ERR_OPERATIONS_ERROR, SCOPE_BASE, SCOPE_SUBTREE from samba.dcerpc import security from samba.ndr import ndr_unpack from samba.samdb import SamDB +from samba import credentials from samba.tests.krb5.kdc_base_test import KDCBaseTest @@ -40,13 +41,20 @@ class LdapTests(KDCBaseTest): """ def test_ldap(self): + self._run_ldap_test("ldapusr") + + def test_ldap_no_pac(self): + self._run_ldap_test("ldapusr_nopac", include_pac=False, + expect_anon=True, allow_error=True) + + def _run_ldap_test(self, user_name, include_pac=True, + expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the # user are stored. samdb = self.get_samdb() - user_name = "ldapusr" mach_name = samdb.host_dns_name() service = "ldap" @@ -62,7 +70,10 @@ class LdapTests(KDCBaseTest): (creds, cachefile) = self.create_ccache_with_user(user_credentials, mach_credentials, service, - mach_name) + mach_name, + pac=include_pac) + # Remove the cached credentials file. + self.addCleanup(os.remove, cachefile.name) # Authenticate in-process to the machine account using the user's # cached credentials. @@ -74,22 +85,61 @@ class LdapTests(KDCBaseTest): self.assertEqual(1, len(ldb_res)) sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) + # Connect to the machine account and retrieve the user SID. + try: + ldb_as_user = SamDB(url="ldap://%s" % mach_name, + credentials=creds, + lp=self.get_lp()) + except LdbError as e: + if not allow_error: + self.fail() + + enum, estr = e.args + self.assertEqual(ERR_OPERATIONS_ERROR, enum) + self.assertIn('NT_STATUS_ACCESS_DENIED', estr) + return + + ldb_res = ldb_as_user.search('', + scope=SCOPE_BASE, + attrs=["tokenGroups"]) + self.assertEqual(1, len(ldb_res)) + + token_groups = ldb_res[0]["tokenGroups"] + token_sid = ndr_unpack(security.dom_sid, token_groups[0]) + + if expect_anon: + # Ensure we got an anonymous token. + self.assertEqual(security.SID_NT_ANONYMOUS, str(token_sid)) + token_sid = ndr_unpack(security.dom_sid, token_groups[1]) + self.assertEqual(security.SID_NT_NETWORK, str(token_sid)) + if len(token_groups) >= 3: + token_sid = ndr_unpack(security.dom_sid, token_groups[2]) + self.assertEqual(security.SID_NT_THIS_ORGANISATION, + str(token_sid)) + else: + # Ensure that they match. + self.assertEqual(sid, token_sid) + + def test_ldap_anonymous(self): + samdb = self.get_samdb() + mach_name = samdb.host_dns_name() + + anon_creds = credentials.Credentials() + anon_creds.set_anonymous() + # Connect to the machine account and retrieve the user SID. ldb_as_user = SamDB(url="ldap://%s" % mach_name, - credentials=creds, + credentials=anon_creds, lp=self.get_lp()) ldb_res = ldb_as_user.search('', scope=SCOPE_BASE, attrs=["tokenGroups"]) self.assertEqual(1, len(ldb_res)) + # Ensure we got an anonymous token. token_sid = ndr_unpack(security.dom_sid, ldb_res[0]["tokenGroups"][0]) - - # Ensure that they match. - self.assertEqual(sid, token_sid) - - # Remove the cached credentials file. - os.remove(cachefile.name) + self.assertEqual(security.SID_NT_ANONYMOUS, str(token_sid)) + self.assertEqual(len(ldb_res[0]["tokenGroups"]), 1) if __name__ == "__main__": diff --git a/python/samba/tests/krb5/test_rpc.py b/python/samba/tests/krb5/test_rpc.py index ef8dd4dcbf5b..54ad7cf0e481 100755 --- a/python/samba/tests/krb5/test_rpc.py +++ b/python/samba/tests/krb5/test_rpc.py @@ -20,7 +20,9 @@ import sys import os +from samba import NTSTATUSError, credentials from samba.dcerpc import lsa +from samba.ntstatus import NT_STATUS_ACCESS_DENIED from samba.tests.krb5.kdc_base_test import KDCBaseTest @@ -37,13 +39,20 @@ class RpcTests(KDCBaseTest): """ def test_rpc(self): + self._run_rpc_test("rpcusr") + + def test_rpc_no_pac(self): + self._run_rpc_test("rpcusr_nopac", include_pac=False, + expect_anon=True, allow_error=True) + + def _run_rpc_test(self, user_name, include_pac=True, + expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the # user are stored. samdb = self.get_samdb() - user_name = "rpcusr" mach_name = samdb.host_dns_name() service = "cifs" @@ -59,20 +68,45 @@ class RpcTests(KDCBaseTest): (creds, cachefile) = self.create_ccache_with_user(user_credentials, mach_credentials, service, - mach_name) + mach_name, + pac=include_pac) + # Remove the cached credentials file. + self.addCleanup(os.remove, cachefile.name) # Authenticate in-process to the machine account using the user's # cached credentials. binding_str = "ncacn_np:%s[\\pipe\\lsarpc]" % mach_name - conn = lsa.lsarpc(binding_str, self.get_lp(), creds) + try: + conn = lsa.lsarpc(binding_str, self.get_lp(), creds) + except NTSTATUSError as e: + if not allow_error: + self.fail() + + enum, _ = e.args + self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + return (account_name, _) = conn.GetUserName(None, None, None) - self.assertEqual(user_name, account_name.string) + if expect_anon: + self.assertNotEqual(user_name, account_name.string) + else: + self.assertEqual(user_name, account_name.string) - # Remove the cached credentials file. - os.remove(cachefile.name) + def test_rpc_anonymous(self): + samdb = self.get_samdb() + mach_name = samdb.host_dns_name() + + anon_creds = credentials.Credentials() + anon_creds.set_anonymous() + + binding_str = "ncacn_np:%s[\\pipe\\lsarpc]" % mach_name + conn = lsa.lsarpc(binding_str, self.get_lp(), anon_creds) + + (account_name, _) = conn.GetUserName(None, None, None) + + self.assertEqual('ANONYMOUS LOGON', account_name.string) if __name__ == "__main__": diff --git a/python/samba/tests/krb5/test_smb.py b/python/samba/tests/krb5/test_smb.py index 1e70ed322bfc..79ff16ac8795 100755 --- a/python/samba/tests/krb5/test_smb.py +++ b/python/samba/tests/krb5/test_smb.py @@ -21,8 +21,10 @@ import sys import os from ldb import SCOPE_SUBTREE +from samba import NTSTATUSError from samba.dcerpc import security from samba.ndr import ndr_unpack +from samba.ntstatus import NT_STATUS_ACCESS_DENIED from samba.samba3 import libsmb_samba_internal as libsmb from samba.samba3 import param as s3param @@ -41,13 +43,20 @@ class SmbTests(KDCBaseTest): """ def test_smb(self): + self._run_smb_test("smbusr") + + def test_smb_no_pac(self): + self._run_smb_test("smbusr_nopac", include_pac=False, + expect_error=True) + + def _run_smb_test(self, user_name, include_pac=True, + expect_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the # user are stored. samdb = self.get_samdb() - user_name = "smbusr" mach_name = samdb.host_dns_name() service = "cifs" share = "tmp" @@ -64,7 +73,10 @@ class SmbTests(KDCBaseTest): (creds, cachefile) = self.create_ccache_with_user(user_credentials, mach_credentials, service, - mach_name) + mach_name, + pac=include_pac) + # Remove the cached credentials file. + self.addCleanup(os.remove, cachefile.name) # Set the Kerberos 5 credentials cache environment variable. This is # required because the codepath that gets run (gse_krb5) looks for it @@ -95,16 +107,23 @@ class SmbTests(KDCBaseTest): self.addCleanup(s3_lp.set, "client max protocol", max_protocol) s3_lp.set("client max protocol", "NT1") - conn = libsmb.Conn(mach_name, share, lp=s3_lp, creds=creds) + try: + conn = libsmb.Conn(mach_name, share, lp=s3_lp, creds=creds) + except NTSTATUSError as e: + if not expect_error: + self.fail() + + enum, _ = e.args + self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + return + else: + self.assertFalse(expect_error) (uid, gid, gids, sids, guest) = conn.posix_whoami() # Ensure that they match. self.assertEqual(sid, sids[0]) - # Remove the cached credentials file. - os.remove(cachefile.name) - if __name__ == "__main__": global_asn1_print = False diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 13a3dc4f3e9c..7a249140c747 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -850,14 +850,15 @@ planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_ldap", 'FAST_SUPPORT': have_fast_support, 'TKT_SIG_SUPPORT': tkt_sig_support }) -planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_rpc", - environ={ - 'ADMIN_USERNAME': '$USERNAME', - 'ADMIN_PASSWORD': '$PASSWORD', - 'STRICT_CHECKING': '0', - 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support - }) +for env in ['ad_dc_default', 'ad_member']: + planoldpythontestsuite(env, "samba.tests.krb5.test_rpc", + environ={ + 'ADMIN_USERNAME': '$DC_USERNAME', + 'ADMIN_PASSWORD': '$DC_PASSWORD', + 'STRICT_CHECKING': '0', + 'FAST_SUPPORT': have_fast_support, + 'TKT_SIG_SUPPORT': tkt_sig_support + }) planoldpythontestsuite("ad_dc_smb1", "samba.tests.krb5.test_smb", environ={ 'ADMIN_USERNAME': '$USERNAME', -- 2.25.1 From 9631007797408987011ba2147df998d384c2fd83 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 22 Oct 2021 16:20:36 +0200 Subject: [PATCH 081/224] CVE-2020-25719 CVE-2020-25717: selftest: remove "gensec:require_pac" settings BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 [jsutton@samba.org Added knownfail entries] Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/no-pac | 4 ++++ selftest/selftest.pl | 2 -- selftest/target/Samba4.pm | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 selftest/knownfail.d/no-pac diff --git a/selftest/knownfail.d/no-pac b/selftest/knownfail.d/no-pac new file mode 100644 index 000000000000..9723d581c2a1 --- /dev/null +++ b/selftest/knownfail.d/no-pac @@ -0,0 +1,4 @@ +^samba.tests.krb5.test_ccache.samba.tests.krb5.test_ccache.CcacheTests.test_ccache_no_pac +^samba.tests.krb5.test_ldap.samba.tests.krb5.test_ldap.LdapTests.test_ldap_no_pac +^samba.tests.krb5.test_rpc.samba.tests.krb5.test_rpc.RpcTests.test_rpc_no_pac +^samba.tests.krb5.test_smb.samba.tests.krb5.test_smb.SmbTests.test_smb_no_pac diff --git a/selftest/selftest.pl b/selftest/selftest.pl index 4c27edd29697..c7bd216545fb 100755 --- a/selftest/selftest.pl +++ b/selftest/selftest.pl @@ -583,8 +583,6 @@ sub write_clientconf($$$) client min protocol = CORE log level = 1 torture:basedir = $clientdir -#We don't want to pass our self-tests if the PAC code is wrong - gensec:require_pac = true #We don't want to run 'speed' tests for very long torture:timelimit = 1 winbind separator = / diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm index 615e8b240b43..651faa73ec73 100755 --- a/selftest/target/Samba4.pm +++ b/selftest/target/Samba4.pm @@ -777,8 +777,6 @@ sub provision_raw_step1($$) notify:inotify = false ldb:nosync = true ldap server require strong auth = yes -#We don't want to pass our self-tests if the PAC code is wrong - gensec:require_pac = true log file = $ctx->{logdir}/log.\%m log level = $ctx->{server_loglevel} lanman auth = Yes -- 2.25.1 From 4d07966e7d91f89841df26483fec8b0b9e06db58 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 10:27:41 +1300 Subject: [PATCH 082/224] CVE-2020-25719 CVE-2020-25717 tests/krb5: Adapt tests for connecting without a PAC to new error codes BUG: https://bugzilla.samba.org/show_bug.cgi?id=14799 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Joseph Sutton Reviewed-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/test_ccache.py | 5 +++-- python/samba/tests/krb5/test_ldap.py | 2 +- python/samba/tests/krb5/test_rpc.py | 4 ++-- python/samba/tests/krb5/test_smb.py | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/python/samba/tests/krb5/test_ccache.py b/python/samba/tests/krb5/test_ccache.py index cb5061b92d9c..d21ec84796e5 100755 --- a/python/samba/tests/krb5/test_ccache.py +++ b/python/samba/tests/krb5/test_ccache.py @@ -25,7 +25,7 @@ from samba import NTSTATUSError, gensec from samba.auth import AuthContext from samba.dcerpc import security from samba.ndr import ndr_unpack -from samba.ntstatus import NT_STATUS_ACCESS_DENIED +from samba.ntstatus import NT_STATUS_NO_IMPERSONATION_TOKEN from samba.tests.krb5.kdc_base_test import KDCBaseTest @@ -84,6 +84,7 @@ class CcacheTests(KDCBaseTest): # cached credentials. lp = self.get_lp() + lp.set('server role', 'active directory domain controller') settings = {} settings["lp_ctx"] = lp @@ -135,7 +136,7 @@ class CcacheTests(KDCBaseTest): self.fail() enum, _ = e.args - self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, enum) return token = session.security_token diff --git a/python/samba/tests/krb5/test_ldap.py b/python/samba/tests/krb5/test_ldap.py index 31e50487338b..0205bdf6fb73 100755 --- a/python/samba/tests/krb5/test_ldap.py +++ b/python/samba/tests/krb5/test_ldap.py @@ -96,7 +96,7 @@ class LdapTests(KDCBaseTest): enum, estr = e.args self.assertEqual(ERR_OPERATIONS_ERROR, enum) - self.assertIn('NT_STATUS_ACCESS_DENIED', estr) + self.assertIn('NT_STATUS_NO_IMPERSONATION_TOKEN', estr) return ldb_res = ldb_as_user.search('', diff --git a/python/samba/tests/krb5/test_rpc.py b/python/samba/tests/krb5/test_rpc.py index 54ad7cf0e481..0f2170a8dede 100755 --- a/python/samba/tests/krb5/test_rpc.py +++ b/python/samba/tests/krb5/test_rpc.py @@ -22,7 +22,7 @@ import os from samba import NTSTATUSError, credentials from samba.dcerpc import lsa -from samba.ntstatus import NT_STATUS_ACCESS_DENIED +from samba.ntstatus import NT_STATUS_NO_IMPERSONATION_TOKEN from samba.tests.krb5.kdc_base_test import KDCBaseTest @@ -84,7 +84,7 @@ class RpcTests(KDCBaseTest): self.fail() enum, _ = e.args - self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, enum) return (account_name, _) = conn.GetUserName(None, None, None) diff --git a/python/samba/tests/krb5/test_smb.py b/python/samba/tests/krb5/test_smb.py index 79ff16ac8795..7408e5dbecea 100755 --- a/python/samba/tests/krb5/test_smb.py +++ b/python/samba/tests/krb5/test_smb.py @@ -24,7 +24,7 @@ from ldb import SCOPE_SUBTREE from samba import NTSTATUSError from samba.dcerpc import security from samba.ndr import ndr_unpack -from samba.ntstatus import NT_STATUS_ACCESS_DENIED +from samba.ntstatus import NT_STATUS_NO_IMPERSONATION_TOKEN from samba.samba3 import libsmb_samba_internal as libsmb from samba.samba3 import param as s3param @@ -114,7 +114,7 @@ class SmbTests(KDCBaseTest): self.fail() enum, _ = e.args - self.assertEqual(NT_STATUS_ACCESS_DENIED, enum) + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, enum) return else: self.assertFalse(expect_error) -- 2.25.1 From 1749315bb2c6a2de02598eb89d957e2d9e21d666 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 17:29:34 +0200 Subject: [PATCH 083/224] CVE-2020-25717: s3:winbindd: make sure we default to r->out.authoritative = true We need to make sure that temporary failures don't trigger a fallback to the local SAM that silently ignores the domain name part for users. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/winbindd/winbindd_dual_srv.c | 7 +++++++ source3/winbindd/winbindd_irpc.c | 7 +++++++ source3/winbindd/winbindd_pam.c | 15 +++++++++++---- source3/winbindd/winbindd_pam_auth_crap.c | 9 ++++++++- source3/winbindd/winbindd_util.c | 7 +++++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c index 32d11e1fa57d..0be5ae5554bd 100644 --- a/source3/winbindd/winbindd_dual_srv.c +++ b/source3/winbindd/winbindd_dual_srv.c @@ -941,6 +941,13 @@ NTSTATUS _winbind_SamLogon(struct pipes_struct *p, union netr_Validation *validation = NULL; bool interactive = false; + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + r->out.authoritative = true; + domain = wb_child_domain(); if (domain == NULL) { return NT_STATUS_REQUEST_NOT_ACCEPTED; diff --git a/source3/winbindd/winbindd_irpc.c b/source3/winbindd/winbindd_irpc.c index e419736010b8..918393c08277 100644 --- a/source3/winbindd/winbindd_irpc.c +++ b/source3/winbindd/winbindd_irpc.c @@ -142,6 +142,13 @@ static NTSTATUS wb_irpc_SamLogon(struct irpc_message *msg, const char *target_domain_name = NULL; const char *account_name = NULL; + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + req->out.authoritative = true; + switch (req->in.logon_level) { case NetlogonInteractiveInformation: case NetlogonServiceInformation: diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c index f27802ee0654..f71eb8197a43 100644 --- a/source3/winbindd/winbindd_pam.c +++ b/source3/winbindd/winbindd_pam.c @@ -1799,7 +1799,7 @@ static NTSTATUS winbindd_dual_pam_auth_samlogon( { fstring name_namespace, name_domain, name_user; NTSTATUS result; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags = 0; uint16_t validation_level = 0; union netr_Validation *validation = NULL; @@ -2453,6 +2453,13 @@ done: result = NT_STATUS_NO_LOGON_SERVERS; } + /* + * Here we don't alter + * state->response->data.auth.authoritative based + * on the servers response + * as we don't want a fallback to the local sam + * for interactive PAM logons + */ set_auth_errors(state->response, result); DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n", @@ -2667,7 +2674,7 @@ enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain, const char *name_domain = NULL; const char *workstation; uint64_t logon_id = 0; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags = 0; uint16_t validation_level; union netr_Validation *validation = NULL; @@ -2740,7 +2747,6 @@ enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain, &validation_level, &validation); if (!NT_STATUS_IS_OK(result)) { - state->response->data.auth.authoritative = authoritative; goto done; } @@ -2772,7 +2778,6 @@ enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain, "from firewalled domain [%s]\n", info3->base.account_name.string, info3->base.logon_domain.string); - state->response->data.auth.authoritative = true; result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; goto done; } @@ -2794,6 +2799,8 @@ done: } set_auth_errors(state->response, result); + state->response->data.auth.authoritative = authoritative; + /* * Log the winbind pam authentication, the logon_id will tie this to * any of the logons invoked from this request. diff --git a/source3/winbindd/winbindd_pam_auth_crap.c b/source3/winbindd/winbindd_pam_auth_crap.c index dacb6566be67..a6f13806df97 100644 --- a/source3/winbindd/winbindd_pam_auth_crap.c +++ b/source3/winbindd/winbindd_pam_auth_crap.c @@ -26,6 +26,7 @@ struct winbindd_pam_auth_crap_state { struct winbindd_response *response; + bool authoritative; uint32_t flags; }; @@ -47,7 +48,7 @@ struct tevent_req *winbindd_pam_auth_crap_send( if (req == NULL) { return NULL; } - + state->authoritative = true; state->flags = request->flags; if (state->flags & WBFLAG_PAM_AUTH_PAC) { @@ -126,6 +127,11 @@ struct tevent_req *winbindd_pam_auth_crap_send( domain = find_auth_domain(request->flags, auth_domain); if (domain == NULL) { + /* + * We don't know the domain so + * we're not authoritative + */ + state->authoritative = false; tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); return tevent_req_post(req, ev); } @@ -186,6 +192,7 @@ NTSTATUS winbindd_pam_auth_crap_recv(struct tevent_req *req, if (tevent_req_is_nterror(req, &status)) { set_auth_errors(response, status); + response->data.auth.authoritative = state->authoritative; return status; } diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c index 414b770a2710..ac4cd731c09a 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -2095,6 +2095,13 @@ void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain) void set_auth_errors(struct winbindd_response *resp, NTSTATUS result) { + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + resp->data.auth.authoritative = true; + resp->data.auth.nt_status = NT_STATUS_V(result); fstrcpy(resp->data.auth.nt_status_string, nt_errstr(result)); -- 2.25.1 From 8aeb1fb81602c5858c92c90d6a89ba3f9c38143c Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 17:29:34 +0200 Subject: [PATCH 084/224] CVE-2020-25717: s4:auth/ntlm: make sure auth_check_password() defaults to r->out.authoritative = true We need to make sure that temporary failures don't trigger a fallback to the local SAM that silently ignores the domain name part for users. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source4/auth/ntlm/auth.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c index e54eb7719f57..4c66f2c23cb6 100644 --- a/source4/auth/ntlm/auth.c +++ b/source4/auth/ntlm/auth.c @@ -169,6 +169,11 @@ _PUBLIC_ NTSTATUS auth_check_password(struct auth4_context *auth_ctx, /*TODO: create a new event context here! */ ev = auth_ctx->event_ctx; + /* + * We are authoritative by default + */ + *pauthoritative = 1; + subreq = auth_check_password_send(mem_ctx, ev, auth_ctx, -- 2.25.1 From a5d82101df5dca18f90532cd2100f2bd05954b31 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 085/224] CVE-2020-25717: s4:torture: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source4/torture/rpc/samlogon.c | 4 ++-- source4/torture/rpc/schannel.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source4/torture/rpc/samlogon.c b/source4/torture/rpc/samlogon.c index 76933b8869ee..703e25fe3c51 100644 --- a/source4/torture/rpc/samlogon.c +++ b/source4/torture/rpc/samlogon.c @@ -1407,7 +1407,7 @@ static bool test_SamLogon(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, union netr_LogonLevel logon; union netr_Validation validation; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags = 0; ZERO_STRUCT(logon); @@ -1520,7 +1520,7 @@ bool test_InteractiveLogon(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, union netr_LogonLevel logon; union netr_Validation validation; - uint8_t authoritative = 0; + uint8_t authoritative = 1; struct dcerpc_binding_handle *b = p->binding_handle; ZERO_STRUCT(a); diff --git a/source4/torture/rpc/schannel.c b/source4/torture/rpc/schannel.c index a5755041040b..08a5120b66d6 100644 --- a/source4/torture/rpc/schannel.c +++ b/source4/torture/rpc/schannel.c @@ -50,7 +50,7 @@ bool test_netlogon_ex_ops(struct dcerpc_pipe *p, struct torture_context *tctx, struct netr_NetworkInfo ninfo; union netr_LogonLevel logon; union netr_Validation validation; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t _flags = 0; DATA_BLOB names_blob, chal, lm_resp, nt_resp; int i; -- 2.25.1 From 3e9ff9e0f438a211c189d9cccf46e6271fe581db Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 086/224] CVE-2020-25717: s4:smb_server: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source4/smb_server/smb/sesssetup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source4/smb_server/smb/sesssetup.c b/source4/smb_server/smb/sesssetup.c index 68cdd70feff8..8428ca3fabb9 100644 --- a/source4/smb_server/smb/sesssetup.c +++ b/source4/smb_server/smb/sesssetup.c @@ -102,7 +102,7 @@ static void sesssetup_old_send(struct tevent_req *subreq) struct auth_session_info *session_info; struct smbsrv_session *smb_sess; NTSTATUS status; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags; status = auth_check_password_recv(subreq, req, &user_info_dc, @@ -243,7 +243,7 @@ static void sesssetup_nt1_send(struct tevent_req *subreq) struct auth_user_info_dc *user_info_dc = NULL; struct auth_session_info *session_info; struct smbsrv_session *smb_sess; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags; NTSTATUS status; -- 2.25.1 From abaea6ac5c0ba64eff8561852d960446d8289f16 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 087/224] CVE-2020-25717: s4:auth_simple: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source4/auth/ntlm/auth_simple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/auth/ntlm/auth_simple.c b/source4/auth/ntlm/auth_simple.c index 8df160cefc37..8301aec519ca 100644 --- a/source4/auth/ntlm/auth_simple.c +++ b/source4/auth/ntlm/auth_simple.c @@ -150,7 +150,7 @@ static void authenticate_ldap_simple_bind_done(struct tevent_req *subreq) const struct tsocket_address *local_address = user_info->local_host; const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE; struct auth_user_info_dc *user_info_dc = NULL; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags = 0; NTSTATUS nt_status; -- 2.25.1 From da9e78d5414ec577f30ffc8847a7140637144eb5 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 088/224] CVE-2020-25717: s3:ntlm_auth: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/utils/ntlm_auth.c | 4 ++-- source3/utils/ntlm_auth_diagnostics.c | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index 0370803167ff..b3ab3a473bf7 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -1927,7 +1927,7 @@ static void manage_ntlm_server_1_request(enum stdio_helper_mode stdio_helper_mod TALLOC_FREE(mem_ctx); } else { - uint8_t authoritative = 0; + uint8_t authoritative = 1; if (!domain) { domain = smb_xstrdup(get_winbind_domain()); @@ -2443,7 +2443,7 @@ static bool check_auth_crap(void) char *hex_lm_key; char *hex_user_session_key; char *error_string; - uint8_t authoritative = 0; + uint8_t authoritative = 1; setbuf(stdout, NULL); diff --git a/source3/utils/ntlm_auth_diagnostics.c b/source3/utils/ntlm_auth_diagnostics.c index 41591a8de339..fc0fc19bacb4 100644 --- a/source3/utils/ntlm_auth_diagnostics.c +++ b/source3/utils/ntlm_auth_diagnostics.c @@ -54,7 +54,7 @@ static bool test_lm_ntlm_broken(enum ntlm_break break_which) DATA_BLOB lm_response = data_blob(NULL, 24); DATA_BLOB nt_response = data_blob(NULL, 24); DATA_BLOB session_key = data_blob(NULL, 16); - uint8_t authoritative = 0; + uint8_t authoritative = 1; uchar lm_key[8]; uchar user_session_key[16]; uchar lm_hash[16]; @@ -177,7 +177,7 @@ static bool test_ntlm_in_lm(void) NTSTATUS nt_status; uint32_t flags = 0; DATA_BLOB nt_response = data_blob(NULL, 24); - uint8_t authoritative = 0; + uint8_t authoritative = 1; uchar lm_key[8]; uchar lm_hash[16]; uchar user_session_key[16]; @@ -245,7 +245,7 @@ static bool test_ntlm_in_both(void) uint32_t flags = 0; DATA_BLOB nt_response = data_blob(NULL, 24); DATA_BLOB session_key = data_blob(NULL, 16); - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint8_t lm_key[8]; uint8_t lm_hash[16]; uint8_t user_session_key[16]; @@ -322,7 +322,7 @@ static bool test_lmv2_ntlmv2_broken(enum ntlm_break break_which) DATA_BLOB lmv2_response = data_blob_null; DATA_BLOB ntlmv2_session_key = data_blob_null; DATA_BLOB names_blob = NTLMv2_generate_names_blob(NULL, get_winbind_netbios_name(), get_winbind_domain()); - uint8_t authoritative = 0; + uint8_t authoritative = 1; uchar user_session_key[16]; DATA_BLOB chall = get_challenge(); char *error_string; @@ -452,7 +452,7 @@ static bool test_plaintext(enum ntlm_break break_which) char *password; smb_ucs2_t *nt_response_ucs2; size_t converted_size; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uchar user_session_key[16]; uchar lm_key[16]; static const uchar zeros[8] = { 0, }; -- 2.25.1 From 2160d65f64db3842de1fc2abb770b5809fbb6198 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 089/224] CVE-2020-25717: s3:torture: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/torture/pdbtest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/torture/pdbtest.c b/source3/torture/pdbtest.c index 5d74aa9ab780..b300504c4cb4 100644 --- a/source3/torture/pdbtest.c +++ b/source3/torture/pdbtest.c @@ -277,7 +277,7 @@ static bool test_auth(TALLOC_CTX *mem_ctx, struct samu *pdb_entry) struct netr_SamInfo6 *info6_wbc = NULL; NTSTATUS status; bool ok; - uint8_t authoritative = 0; + uint8_t authoritative = 1; int rc; rc = SMBOWFencrypt(pdb_get_nt_passwd(pdb_entry), challenge_8, -- 2.25.1 From 31e2a9d94fcac529821153d0370fa6c8b5a054d5 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 090/224] CVE-2020-25717: s3:rpcclient: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/rpcclient/cmd_netlogon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/rpcclient/cmd_netlogon.c b/source3/rpcclient/cmd_netlogon.c index d5c1b91f2bee..4ea63e40b8db 100644 --- a/source3/rpcclient/cmd_netlogon.c +++ b/source3/rpcclient/cmd_netlogon.c @@ -496,7 +496,7 @@ static NTSTATUS cmd_netlogon_sam_logon(struct rpc_pipe_client *cli, uint32_t logon_param = 0; const char *workstation = NULL; struct netr_SamInfo3 *info3 = NULL; - uint8_t authoritative = 0; + uint8_t authoritative = 1; uint32_t flags = 0; uint16_t validation_level; union netr_Validation *validation = NULL; -- 2.25.1 From b74d5fcaaa4c1715ca4872de149c353be7bb75a0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 091/224] CVE-2020-25717: s3:auth: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 2 +- source3/auth/auth_samba4.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index e62585e919c8..57281874b357 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -416,7 +416,7 @@ NTSTATUS auth_check_password_session_info(struct auth4_context *auth_context, { NTSTATUS nt_status; void *server_info; - uint8_t authoritative = 0; + uint8_t authoritative = 1; struct tevent_context *ev = NULL; struct tevent_req *subreq = NULL; bool ok; diff --git a/source3/auth/auth_samba4.c b/source3/auth/auth_samba4.c index ac1413547473..fde78d5e74ef 100644 --- a/source3/auth/auth_samba4.c +++ b/source3/auth/auth_samba4.c @@ -120,7 +120,7 @@ static NTSTATUS check_samba4_security( NTSTATUS nt_status; struct auth_user_info_dc *user_info_dc; struct auth4_context *auth4_context; - uint8_t authoritative = 0; + uint8_t authoritative = 1; struct auth_serversupplied_info *server_info = NULL; nt_status = make_auth4_context_s4(auth_context, mem_ctx, &auth4_context); -- 2.25.1 From 2a9998d4b40e502f6ea70434ccf3b452f55e1b72 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 092/224] CVE-2020-25717: auth/ntlmssp: start with authoritative = 1 This is not strictly needed, but makes it easier to audit that we don't miss important places. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- auth/ntlmssp/ntlmssp_server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/ntlmssp/ntlmssp_server.c b/auth/ntlmssp/ntlmssp_server.c index 001238278d7f..939aa0ef4aa5 100644 --- a/auth/ntlmssp/ntlmssp_server.c +++ b/auth/ntlmssp/ntlmssp_server.c @@ -799,7 +799,7 @@ static void ntlmssp_server_auth_done(struct tevent_req *subreq) struct gensec_security *gensec_security = state->gensec_security; struct gensec_ntlmssp_context *gensec_ntlmssp = state->gensec_ntlmssp; struct auth4_context *auth_context = gensec_security->auth_context; - uint8_t authoritative = 0; + uint8_t authoritative = 1; NTSTATUS status; status = auth_context->check_ntlm_password_recv(subreq, -- 2.25.1 From 9dd0fea6112051764edffa187baa958b70293efa Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 28 Sep 2021 10:43:40 +0200 Subject: [PATCH 093/224] CVE-2020-25717: loadparm: Add new parameter "min domain uid" BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Samuel Cabrero Signed-off-by: Stefan Metzmacher [abartlet@samba.org Backported from master/4.15 due to conflicts with other new parameters] --- docs-xml/smbdotconf/security/mindomainuid.xml | 17 +++++++++++++++++ docs-xml/smbdotconf/winbind/idmapconfig.xml | 4 ++++ lib/param/loadparm.c | 4 ++++ source3/param/loadparm.c | 2 ++ 4 files changed, 27 insertions(+) create mode 100644 docs-xml/smbdotconf/security/mindomainuid.xml diff --git a/docs-xml/smbdotconf/security/mindomainuid.xml b/docs-xml/smbdotconf/security/mindomainuid.xml new file mode 100644 index 000000000000..46ae795d7304 --- /dev/null +++ b/docs-xml/smbdotconf/security/mindomainuid.xml @@ -0,0 +1,17 @@ + + + + The integer parameter specifies the minimum uid allowed when mapping a + local account to a domain account. + + + + Note that this option interacts with the configured idmap ranges! + + + +1000 + diff --git a/docs-xml/smbdotconf/winbind/idmapconfig.xml b/docs-xml/smbdotconf/winbind/idmapconfig.xml index 1374040fb29d..f70f11df7571 100644 --- a/docs-xml/smbdotconf/winbind/idmapconfig.xml +++ b/docs-xml/smbdotconf/winbind/idmapconfig.xml @@ -80,6 +80,9 @@ authoritative for a unix ID to SID mapping, so it must be set for each individually configured domain and for the default configuration. The configured ranges must be mutually disjoint. + + + Note that the low value interacts with the option! @@ -115,4 +118,5 @@ +min domain uid diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index 3548c47d857c..eedfa00bcb0a 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -3090,6 +3090,10 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) "client smb encrypt", "default"); + lpcfg_do_global_parameter(lp_ctx, + "min domain uid", + "1000"); + for (i = 0; parm_table[i].label; i++) { if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) { lp_ctx->flags[i] |= FLAG_DEFAULT; diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index acb4d149f0b4..300b539748ba 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -967,6 +967,8 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.client_smb_encrypt = SMB_ENCRYPTION_DEFAULT; + Globals.min_domain_uid = 1000; + /* Now put back the settings that were set with lp_set_cmdline() */ apply_lp_set_cmdline(); } -- 2.25.1 From abf32daa35f62363582b85a9a12ad10703005de5 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 5 Oct 2021 12:31:29 +0200 Subject: [PATCH 094/224] CVE-2020-25717: selftest: Add ad_member_no_nss_wb environment This environment creates an AD member that doesn't have 'nss_winbind' configured, while winbindd is still started. For testing we map a DOMAIN\root user to the local root account and unix token of the local root user. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Samuel Cabrero Signed-off-by: Stefan Metzmacher [abartlet@samba.org backported to Samba 4.14 without offline tests in Samba3.pm] --- selftest/target/Samba.pm | 1 + selftest/target/Samba3.pm | 68 +++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/selftest/target/Samba.pm b/selftest/target/Samba.pm index d48ac2c64a1a..7e7f8e4cbf89 100644 --- a/selftest/target/Samba.pm +++ b/selftest/target/Samba.pm @@ -589,6 +589,7 @@ sub get_interface($) lclnt4dc2smb1 => 55, fipsdc => 56, fipsadmember => 57, + admemnonsswb => 60, rootdnsforwarder => 64, diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index 8b68fc0fe4fb..fd5e29f6db55 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -238,6 +238,7 @@ sub check_env($$) ad_member_idmap_rid => ["ad_dc"], ad_member_idmap_ad => ["fl2008r2dc"], ad_member_fips => ["ad_dc_fips"], + ad_member_no_nss_wb => ["ad_dc"], clusteredmember => ["nt4_dc"], ); @@ -650,7 +651,9 @@ sub provision_ad_member $dcvars, $trustvars_f, $trustvars_e, - $force_fips_mode) = @_; + $extra_member_options, + $force_fips_mode, + $no_nss_winbind) = @_; my $prefix_abs = abs_path($prefix); my @dirs = (); @@ -688,6 +691,10 @@ sub provision_ad_member $netbios_aliases = "netbios aliases = foo bar"; } + unless (defined($extra_member_options)) { + $extra_member_options = ""; + } + my $member_options = " security = ads workgroup = $dcvars->{DOMAIN} @@ -711,6 +718,10 @@ sub provision_ad_member rpc_daemon:epmd = fork rpc_daemon:lsasd = fork + # Begin extra member options + $extra_member_options + # End extra member options + [sub_dug] path = $share_dir/D_%D/U_%U/G_%G writeable = yes @@ -829,12 +840,17 @@ sub provision_ad_member # access the share for tests. chmod 0777, "$prefix/share"; - if (not $self->check_or_start( - env_vars => $ret, - nmbd => "yes", - winbindd => "yes", - smbd => "yes")) { - return undef; + if (defined($no_nss_winbind)) { + $ret->{NSS_WRAPPER_MODULE_SO_PATH} = ""; + $ret->{NSS_WRAPPER_MODULE_FN_PREFIX} = ""; + } + + if (not $self->check_or_start( + env_vars => $ret, + nmbd => "yes", + winbindd => "yes", + smbd => "yes")) { + return undef; } $ret->{DC_SERVER} = $dcvars->{SERVER}; @@ -1214,9 +1230,47 @@ sub setup_ad_member_fips $dcvars, $trustvars_f, $trustvars_e, + undef, 1); } +sub setup_ad_member_no_nss_wb +{ + my ($self, + $prefix, + $dcvars, + $trustvars_f, + $trustvars_e) = @_; + + # If we didn't build with ADS, pretend this env was never available + if (not $self->have_ads()) { + return "UNKNOWN"; + } + + print "PROVISIONING AD MEMBER WITHOUT NSS WINBIND..."; + + my $extra_member_options = " + username map = $prefix/lib/username.map +"; + + my $ret = $self->provision_ad_member($prefix, + "ADMEMNONSSWB", + $dcvars, + $trustvars_f, + $trustvars_e, + $extra_member_options, + undef, + 1); + + open(USERMAP, ">$prefix/lib/username.map") or die("Unable to open $prefix/lib/username.map"); + print USERMAP " +root = $dcvars->{DOMAIN}/root +"; + close(USERMAP); + + return $ret; +} + sub setup_simpleserver { my ($self, $path) = @_; -- 2.25.1 From 13500a086580a4e02680f037b5cb1fbe7b585afa Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 5 Oct 2021 16:56:06 +0200 Subject: [PATCH 095/224] CVE-2020-25717: selftest: Add a test for the new 'min domain uid' parameter BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Samuel Cabrero Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett [abartlet@samba.org Fixed knowfail per instruction from metze] --- .../samba/tests/krb5/test_min_domain_uid.py | 121 ++++++++++++++++++ python/samba/tests/usage.py | 1 + selftest/knownfail.d/min_domain_uid | 1 + source4/selftest/tests.py | 7 + 4 files changed, 130 insertions(+) create mode 100755 python/samba/tests/krb5/test_min_domain_uid.py create mode 100644 selftest/knownfail.d/min_domain_uid diff --git a/python/samba/tests/krb5/test_min_domain_uid.py b/python/samba/tests/krb5/test_min_domain_uid.py new file mode 100755 index 000000000000..77414b239f08 --- /dev/null +++ b/python/samba/tests/krb5/test_min_domain_uid.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Unix SMB/CIFS implementation. +# Copyright (C) Samuel Cabrero 2021 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import sys +import os +import pwd +import ctypes + +from samba.tests import env_get_var_value +from samba.samba3 import libsmb_samba_internal as libsmb +from samba.samba3 import param as s3param +from samba import NTSTATUSError, ntstatus + +from samba.tests.krb5.kdc_base_test import KDCBaseTest +from samba.credentials import MUST_USE_KERBEROS, DONT_USE_KERBEROS + +sys.path.insert(0, "bin/python") +os.environ["PYTHONUNBUFFERED"] = "1" + +class SmbMinDomainUid(KDCBaseTest): + """Test for SMB authorization without NSS winbind. In such setup domain + accounts are mapped to local accounts using the 'username map' option. + """ + + def setUp(self): + super(KDCBaseTest, self).setUp() + + # Create a user account, along with a Kerberos credentials cache file + # where the service ticket authenticating the user are stored. + self.samdb = self.get_samdb() + + self.mach_name = env_get_var_value('SERVER') + self.user_name = "root" + self.service = "cifs" + self.share = "tmp" + + # Create the user account. + (self.user_creds, _) = self.create_account(self.samdb, self.user_name) + + # Build the global inject file path + server_conf = env_get_var_value('SMB_CONF_PATH') + server_conf_dir = os.path.dirname(server_conf) + self.global_inject = os.path.join(server_conf_dir, "global_inject.conf") + + def _test_min_uid(self, creds): + # Assert unix root uid is less than 'idmap config ADDOMAIN' minimum + s3_lp = s3param.get_context() + s3_lp.load(self.get_lp().configfile) + + domain_range = s3_lp.get("idmap config * : range").split('-') + domain_range_low = int(domain_range[0]) + unix_root_pw = pwd.getpwnam(self.user_name) + self.assertLess(unix_root_pw.pw_uid, domain_range_low) + self.assertLess(unix_root_pw.pw_gid, domain_range_low) + + conn = libsmb.Conn(self.mach_name, self.share, lp=s3_lp, creds=creds) + # Disconnect + conn = None + + # Restrict access to local root account uid + with open(self.global_inject, 'w') as f: + f.write("min domain uid = %s\n" % (unix_root_pw.pw_uid + 1)) + + with self.assertRaises(NTSTATUSError) as cm: + conn = libsmb.Conn(self.mach_name, + self.share, + lp=s3_lp, + creds=creds) + code = ctypes.c_uint32(cm.exception.args[0]).value + self.assertEqual(code, ntstatus.NT_STATUS_INVALID_TOKEN) + + # check that the local root account uid is now allowed + with open(self.global_inject, 'w') as f: + f.write("min domain uid = %s\n" % unix_root_pw.pw_uid) + + conn = libsmb.Conn(self.mach_name, self.share, lp=s3_lp, creds=creds) + # Disconnect + conn = None + + with open(self.global_inject, 'w') as f: + f.truncate() + + def test_min_domain_uid_krb5(self): + krb5_state = self.user_creds.get_kerberos_state() + self.user_creds.set_kerberos_state(MUST_USE_KERBEROS) + ret = self._test_min_uid(self.user_creds) + self.user_creds.set_kerberos_state(krb5_state) + return ret + + def test_min_domain_uid_ntlmssp(self): + krb5_state = self.user_creds.get_kerberos_state() + self.user_creds.set_kerberos_state(DONT_USE_KERBEROS) + ret = self._test_min_uid(self.user_creds) + self.user_creds.set_kerberos_state(krb5_state) + return ret + + def tearDown(self): + # Ensure no leftovers in global inject file + with open(self.global_inject, 'w') as f: + f.truncate() + + super(KDCBaseTest, self).tearDown() + +if __name__ == "__main__": + import unittest + unittest.main() diff --git a/python/samba/tests/usage.py b/python/samba/tests/usage.py index 7b4c09ab939b..3dd1345d4851 100644 --- a/python/samba/tests/usage.py +++ b/python/samba/tests/usage.py @@ -107,6 +107,7 @@ EXCLUDE_USAGE = { 'python/samba/tests/krb5/salt_tests.py', 'python/samba/tests/krb5/spn_tests.py', 'python/samba/tests/krb5/alias_tests.py', + 'python/samba/tests/krb5/test_min_domain_uid.py', } EXCLUDE_HELP = { diff --git a/selftest/knownfail.d/min_domain_uid b/selftest/knownfail.d/min_domain_uid new file mode 100644 index 000000000000..00bf75cd8af5 --- /dev/null +++ b/selftest/knownfail.d/min_domain_uid @@ -0,0 +1 @@ +^samba.tests.krb5.test_min_domain_uid.samba.*.SmbMinDomainUid.test_min_domain_uid_.*\(ad_member_no_nss_wb:local\) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 7a249140c747..567fff736b5a 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -867,6 +867,13 @@ planoldpythontestsuite("ad_dc_smb1", "samba.tests.krb5.test_smb", 'FAST_SUPPORT': have_fast_support, 'TKT_SIG_SUPPORT': tkt_sig_support }) +planoldpythontestsuite("ad_member_no_nss_wb:local", + "samba.tests.krb5.test_min_domain_uid", + environ={ + 'ADMIN_USERNAME': '$DC_USERNAME', + 'ADMIN_PASSWORD': '$DC_PASSWORD', + 'STRICT_CHECKING': '0' + }) for env in ["ad_dc", smbv1_disabled_testenv]: planoldpythontestsuite(env, "samba.tests.smb", extra_args=['-U"$USERNAME%$PASSWORD"']) -- 2.25.1 From 82ad397529e3e3e8deebb43a4d5001d481e4c5d1 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 19:57:18 +0200 Subject: [PATCH 096/224] CVE-2020-25717: s3:auth: let auth3_generate_session_info_pac() forward the low level errors Mapping everything to ACCESS_DENIED makes it hard to debug problems, which may happen because of our more restrictive behaviour in future. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index 57281874b357..f6a501f1df7b 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -166,7 +166,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to map kerberos pac to server info (%s)\n", nt_errstr(status))); - status = NT_STATUS_ACCESS_DENIED; + status = nt_status_squash(status); goto done; } -- 2.25.1 From 31226110d26eaef293fed21269dd602e800ea0af Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 28 Sep 2021 10:45:11 +0200 Subject: [PATCH 097/224] CVE-2020-25717: s3:auth: Check minimum domain uid BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Samuel Cabrero Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett [abartlet@samba.org Removed knownfail on advice from metze] --- selftest/knownfail.d/min_domain_uid | 1 - source3/auth/auth_util.c | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) delete mode 100644 selftest/knownfail.d/min_domain_uid diff --git a/selftest/knownfail.d/min_domain_uid b/selftest/knownfail.d/min_domain_uid deleted file mode 100644 index 00bf75cd8af5..000000000000 --- a/selftest/knownfail.d/min_domain_uid +++ /dev/null @@ -1 +0,0 @@ -^samba.tests.krb5.test_min_domain_uid.samba.*.SmbMinDomainUid.test_min_domain_uid_.*\(ad_member_no_nss_wb:local\) diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c index 4686b29111e8..4de4bc74374b 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -2103,6 +2103,22 @@ NTSTATUS make_server_info_info3(TALLOC_CTX *mem_ctx, } } goto out; + } else if ((lp_security() == SEC_ADS || lp_security() == SEC_DOMAIN) && + !is_myname(domain) && pwd->pw_uid < lp_min_domain_uid()) { + /* + * !is_myname(domain) because when smbd starts tries to setup + * the guest user info, calling this function with nobody + * username. Nobody is usually uid 65535 but it can be changed + * to a regular user with 'guest account' parameter + */ + nt_status = NT_STATUS_INVALID_TOKEN; + DBG_NOTICE("Username '%s%s%s' is invalid on this system, " + "it does not meet 'min domain uid' " + "restriction (%u < %u): %s\n", + nt_domain, lp_winbind_separator(), nt_username, + pwd->pw_uid, lp_min_domain_uid(), + nt_errstr(nt_status)); + goto out; } result = make_server_info(tmp_ctx); -- 2.25.1 From 352c54a39cdc7922d5cdf8fe754b84eb8463b9c3 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 17:40:30 +0200 Subject: [PATCH 098/224] CVE-2020-25717: s3:auth: we should not try to autocreate the guest account We should avoid autocreation of users as much as possible. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/user_krb5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/auth/user_krb5.c b/source3/auth/user_krb5.c index 8998f9c8f8ae..074e8c7eb711 100644 --- a/source3/auth/user_krb5.c +++ b/source3/auth/user_krb5.c @@ -155,7 +155,7 @@ NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, if (!fuser) { return NT_STATUS_NO_MEMORY; } - pw = smb_getpwnam(mem_ctx, fuser, &unixuser, true); + pw = smb_getpwnam(mem_ctx, fuser, &unixuser, false); } /* extra sanity check that the guest account is valid */ -- 2.25.1 From 2e803f9375b4bd67b9846a6b587f6c9f227f515a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 18:08:20 +0200 Subject: [PATCH 099/224] CVE-2020-25717: s3:auth: no longer let check_account() autocreate local users So far we autocreated local user accounts based on just the account_name (just ignoring any domain part). This only happens via a possible 'add user script', which is not typically defined on domain members and on NT4 DCs local users already exist in the local passdb anyway. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c index 4de4bc74374b..99b85d47a5f0 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -1898,7 +1898,7 @@ static NTSTATUS check_account(TALLOC_CTX *mem_ctx, const char *domain, return NT_STATUS_NO_MEMORY; } - passwd = smb_getpwnam(mem_ctx, dom_user, &real_username, true ); + passwd = smb_getpwnam(mem_ctx, dom_user, &real_username, false); if (!passwd) { DEBUG(3, ("Failed to find authenticated user %s via " "getpwnam(), denying access.\n", dom_user)); -- 2.25.1 From 283df3466f8136dab5a6d7be02ca9773a76d94e5 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 8 Oct 2021 12:33:16 +0200 Subject: [PATCH 100/224] CVE-2020-25717: s3:auth: remove fallbacks in smb_getpwnam() So far we tried getpwnam("DOMAIN\account") first and always did a fallback to getpwnam("account") completely ignoring the domain part, this just causes problems as we mix "DOMAIN1\account", "DOMAIN2\account", and "account"! As we require a running winbindd for domain member setups we should no longer do a fallback to just "account" for users served by winbindd! For users of the local SAM don't use this code path, as check_sam_security() doesn't call check_account(). The only case where smb_getpwnam("account") happens is when map_username() via ("username map [script]") mapped "DOMAIN\account" to something without '\', but that is explicitly desired by the admin. Note: use 'git show -w' BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Ralph Boehme Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ktest | 26 +++++++++++++ source3/auth/auth_util.c | 77 +++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 selftest/knownfail.d/ktest diff --git a/selftest/knownfail.d/ktest b/selftest/knownfail.d/ktest new file mode 100644 index 000000000000..809612ba0b94 --- /dev/null +++ b/selftest/knownfail.d/ktest @@ -0,0 +1,26 @@ +^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2...lsa.LookupSidsReply.ktest +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5...rpcclient.ktest:local +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5...rpcclient.ktest:local +^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,connect...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,connect...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,packet...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,packet...lsa.LookupSidsReply.ktest +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,packet...rpcclient.ktest:local +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,packet...rpcclient.ktest:local +^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,sign...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,sign...lsa.LookupSidsReply.ktest +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,sign...rpcclient.ktest:local +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,sign...rpcclient.ktest:local +^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,seal...lsa.LookupSidsReply.ktest +^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,seal...lsa.LookupSidsReply.ktest +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,seal...rpcclient.ktest:local +^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,seal...rpcclient.ktest:local +^samba3.blackbox.smbclient_krb5.old.ccache..smbclient.ktest:local +^samba3.blackbox.smbclient_krb5.new.ccache..smbclient.ktest:local +^samba3.blackbox.smbclient_large_file..krb5.smbclient.large.posix.write.read.ktest:local +^samba3.blackbox.smbclient_large_file..krb5.cmp.of.read.and.written.files.ktest:local +^samba3.blackbox.smbclient_krb5.old.ccache.--client-protection=encrypt.smbclient.ktest:local +^samba3.blackbox.smbclient_krb5.new.ccache.--client-protection=encrypt.smbclient.ktest:local +^samba3.blackbox.smbclient_large_file.--client-protection=encrypt.krb5.smbclient.large.posix.write.read.ktest:local +^samba3.blackbox.smbclient_large_file.--client-protection=encrypt.krb5.cmp.of.read.and.written.files.ktest:local diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c index 99b85d47a5f0..d81313a0495e 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -1933,7 +1933,7 @@ struct passwd *smb_getpwnam( TALLOC_CTX *mem_ctx, const char *domuser, { struct passwd *pw = NULL; char *p = NULL; - char *username = NULL; + const char *username = NULL; /* we only save a copy of the username it has been mangled by winbindd use default domain */ @@ -1952,48 +1952,55 @@ struct passwd *smb_getpwnam( TALLOC_CTX *mem_ctx, const char *domuser, /* code for a DOMAIN\user string */ if ( p ) { - pw = Get_Pwnam_alloc( mem_ctx, domuser ); - if ( pw ) { - /* make sure we get the case of the username correct */ - /* work around 'winbind use default domain = yes' */ - - if ( lp_winbind_use_default_domain() && - !strchr_m( pw->pw_name, *lp_winbind_separator() ) ) { - char *domain; - - /* split the domain and username into 2 strings */ - *p = '\0'; - domain = username; - - *p_save_username = talloc_asprintf(mem_ctx, - "%s%c%s", - domain, - *lp_winbind_separator(), - pw->pw_name); - if (!*p_save_username) { - TALLOC_FREE(pw); - return NULL; - } - } else { - *p_save_username = talloc_strdup(mem_ctx, pw->pw_name); - } + const char *domain = NULL; - /* whew -- done! */ - return pw; + /* split the domain and username into 2 strings */ + *p = '\0'; + domain = username; + p++; + username = p; + + if (strequal(domain, get_global_sam_name())) { + /* + * This typically don't happen + * as check_sam_Security() + * don't call make_server_info_info3() + * and thus check_account(). + * + * But we better keep this. + */ + goto username_only; } - /* setup for lookup of just the username */ - /* remember that p and username are overlapping memory */ - - p++; - username = talloc_strdup(mem_ctx, p); - if (!username) { + pw = Get_Pwnam_alloc( mem_ctx, domuser ); + if (pw == NULL) { return NULL; } + /* make sure we get the case of the username correct */ + /* work around 'winbind use default domain = yes' */ + + if ( lp_winbind_use_default_domain() && + !strchr_m( pw->pw_name, *lp_winbind_separator() ) ) { + *p_save_username = talloc_asprintf(mem_ctx, + "%s%c%s", + domain, + *lp_winbind_separator(), + pw->pw_name); + if (!*p_save_username) { + TALLOC_FREE(pw); + return NULL; + } + } else { + *p_save_username = talloc_strdup(mem_ctx, pw->pw_name); + } + + /* whew -- done! */ + return pw; + } /* just lookup a plain username */ - +username_only: pw = Get_Pwnam_alloc(mem_ctx, username); /* Create local user if requested but only if winbindd -- 2.25.1 From c524a4481a90dabe9d7bc0485ec1bc37497e09b8 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 13:13:52 +0200 Subject: [PATCH 101/224] CVE-2020-25717: s3:lib: add lp_allow_trusted_domains() logic to is_allowed_domain() is_allowed_domain() is a central place we already use to trigger NT_STATUS_AUTHENTICATION_FIREWALL_FAILED, so we can add additional logic there. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/lib/util_names.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/source3/lib/util_names.c b/source3/lib/util_names.c index 630a25875c7b..876035cbe29c 100644 --- a/source3/lib/util_names.c +++ b/source3/lib/util_names.c @@ -200,5 +200,18 @@ bool is_allowed_domain(const char *domain_name) } } - return true; + if (lp_allow_trusted_domains()) { + return true; + } + + if (strequal(lp_workgroup(), domain_name)) { + return true; + } + + if (is_myname(domain_name)) { + return true; + } + + DBG_NOTICE("Not trusted domain '%s'\n", domain_name); + return false; } -- 2.25.1 From cc02c8c73ec9f8939613913fb273152e66561438 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 18:03:55 +0200 Subject: [PATCH 102/224] CVE-2020-25717: s3:auth: don't let create_local_token depend on !winbind_ping() We always require a running winbindd on a domain member, so we should better fail a request instead of silently alter the behaviour, which results in a different unix token, just because winbindd might be restarted. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_util.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c index d81313a0495e..065b525500f9 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -576,13 +576,11 @@ NTSTATUS create_local_token(TALLOC_CTX *mem_ctx, } /* - * If winbind is not around, we can not make much use of the SIDs the - * domain controller provided us with. Likewise if the user name was - * mapped to some local unix user. + * If the user name was mapped to some local unix user, + * we can not make much use of the SIDs the + * domain controller provided us with. */ - - if (((lp_server_role() == ROLE_DOMAIN_MEMBER) && !winbind_ping()) || - (server_info->nss_token)) { + if (server_info->nss_token) { char *found_username = NULL; status = create_token_from_username(session_info, server_info->unix_name, -- 2.25.1 From 454598675d97876f9ccf8a7f68664f5dcb8ef933 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 11 Nov 2020 18:50:45 +0200 Subject: [PATCH 103/224] CVE-2020-25717: Add FreeIPA domain controller role As we want to reduce use of 'classic domain controller' role but FreeIPA relies on it internally, add a separate role to mark FreeIPA domain controller role. It means that role won't result in ROLE_STANDALONE. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Alexander Bokovoy Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- docs-xml/smbdotconf/security/serverrole.xml | 7 ++++ lib/param/loadparm_server_role.c | 2 ++ lib/param/param_table.c | 1 + lib/param/util.c | 1 + libcli/netlogon/netlogon.c | 2 +- libds/common/roles.h | 1 + source3/auth/auth.c | 3 ++ source3/auth/auth_sam.c | 14 ++++---- source3/include/smb_macros.h | 2 +- source3/lib/netapi/joindomain.c | 1 + source3/param/loadparm.c | 4 ++- source3/passdb/lookup_sid.c | 2 +- source3/passdb/machine_account_secrets.c | 7 ++-- source3/registry/reg_backend_prod_options.c | 1 + source3/rpc_server/dssetup/srv_dssetup_nt.c | 1 + source3/smbd/server.c | 2 +- source3/winbindd/winbindd_misc.c | 2 +- source3/winbindd/winbindd_util.c | 40 ++++++++++++++++----- source4/auth/ntlm/auth.c | 1 + source4/kdc/kdc-heimdal.c | 1 + source4/rpc_server/samr/dcesrv_samr.c | 2 ++ 21 files changed, 72 insertions(+), 25 deletions(-) diff --git a/docs-xml/smbdotconf/security/serverrole.xml b/docs-xml/smbdotconf/security/serverrole.xml index 9511c61c96d6..b8b83a127b5d 100644 --- a/docs-xml/smbdotconf/security/serverrole.xml +++ b/docs-xml/smbdotconf/security/serverrole.xml @@ -78,6 +78,13 @@ url="http://wiki.samba.org/index.php/Samba4/HOWTO">Samba4 HOWTO + SERVER ROLE = IPA DOMAIN CONTROLLER + + This mode of operation runs Samba in a hybrid mode for IPA + domain controller, providing forest trust to Active Directory. + This role requires special configuration performed by IPA installers + and should not be used manually by any administrator. + security diff --git a/lib/param/loadparm_server_role.c b/lib/param/loadparm_server_role.c index 7a6bc7707235..a78d1ab9cf39 100644 --- a/lib/param/loadparm_server_role.c +++ b/lib/param/loadparm_server_role.c @@ -42,6 +42,7 @@ static const struct srv_role_tab { { ROLE_DOMAIN_BDC, "ROLE_DOMAIN_BDC" }, { ROLE_DOMAIN_PDC, "ROLE_DOMAIN_PDC" }, { ROLE_ACTIVE_DIRECTORY_DC, "ROLE_ACTIVE_DIRECTORY_DC" }, + { ROLE_IPA_DC, "ROLE_IPA_DC"}, { 0, NULL } }; @@ -140,6 +141,7 @@ bool lp_is_security_and_server_role_valid(int server_role, int security) case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: case ROLE_ACTIVE_DIRECTORY_DC: + case ROLE_IPA_DC: if (security == SEC_USER) { valid = true; } diff --git a/lib/param/param_table.c b/lib/param/param_table.c index e2f737279dc8..3dc5fc599917 100644 --- a/lib/param/param_table.c +++ b/lib/param/param_table.c @@ -111,6 +111,7 @@ static const struct enum_list enum_server_role[] = { {ROLE_ACTIVE_DIRECTORY_DC, "active directory domain controller"}, {ROLE_ACTIVE_DIRECTORY_DC, "domain controller"}, {ROLE_ACTIVE_DIRECTORY_DC, "dc"}, + {ROLE_IPA_DC, "IPA primary domain controller"}, {-1, NULL} }; diff --git a/lib/param/util.c b/lib/param/util.c index cd8e74b9d8f3..9a0fc102de89 100644 --- a/lib/param/util.c +++ b/lib/param/util.c @@ -255,6 +255,7 @@ const char *lpcfg_sam_name(struct loadparm_context *lp_ctx) case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: case ROLE_ACTIVE_DIRECTORY_DC: + case ROLE_IPA_DC: return lpcfg_workgroup(lp_ctx); default: return lpcfg_netbios_name(lp_ctx); diff --git a/libcli/netlogon/netlogon.c b/libcli/netlogon/netlogon.c index 239503e85b64..59af460dc4e8 100644 --- a/libcli/netlogon/netlogon.c +++ b/libcli/netlogon/netlogon.c @@ -93,7 +93,7 @@ NTSTATUS pull_netlogon_samlogon_response(DATA_BLOB *data, TALLOC_CTX *mem_ctx, if (ndr->offset < ndr->data_size) { TALLOC_FREE(ndr); /* - * We need to handle a bug in FreeIPA (at least <= 4.1.2). + * We need to handle a bug in IPA (at least <= 4.1.2). * * They include the ip address information without setting * NETLOGON_NT_VERSION_5EX_WITH_IP, while using diff --git a/libds/common/roles.h b/libds/common/roles.h index 4772c8d7d3f7..03ba1915b215 100644 --- a/libds/common/roles.h +++ b/libds/common/roles.h @@ -33,6 +33,7 @@ enum server_role { /* not in samr.idl */ ROLE_ACTIVE_DIRECTORY_DC = 4, + ROLE_IPA_DC = 5, /* To determine the role automatically, this is not a valid role */ ROLE_AUTO = 100 diff --git a/source3/auth/auth.c b/source3/auth/auth.c index 6d2a40f0f298..d0036ea6b308 100644 --- a/source3/auth/auth.c +++ b/source3/auth/auth.c @@ -543,6 +543,7 @@ NTSTATUS make_auth3_context_for_ntlm(TALLOC_CTX *mem_ctx, break; case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: role = "'DC'"; methods = "anonymous sam winbind sam_ignoredomain"; break; @@ -574,6 +575,7 @@ NTSTATUS make_auth3_context_for_netlogon(TALLOC_CTX *mem_ctx, switch (lp_server_role()) { case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: methods = "sam_netlogon3 winbind"; break; @@ -595,6 +597,7 @@ NTSTATUS make_auth3_context_for_winbind(TALLOC_CTX *mem_ctx, case ROLE_DOMAIN_MEMBER: case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: methods = "sam"; break; case ROLE_ACTIVE_DIRECTORY_DC: diff --git a/source3/auth/auth_sam.c b/source3/auth/auth_sam.c index e8e0d543f8c3..a2ce10139750 100644 --- a/source3/auth/auth_sam.c +++ b/source3/auth/auth_sam.c @@ -143,12 +143,13 @@ static NTSTATUS auth_samstrict_auth(const struct auth_context *auth_context, break; case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: if (!is_local_name && !is_my_domain) { /* If we are running on a DC that has PASSDB module with domain * information, check if DNS forest name is matching the domain - * name. This is the case of FreeIPA domain controller when - * trusted AD DCs attempt to authenticate FreeIPA users using - * the forest root domain (which is the only domain in FreeIPA). + * name. This is the case of IPA domain controller when + * trusted AD DCs attempt to authenticate IPA users using + * the forest root domain (which is the only domain in IPA). */ struct pdb_domain_info *dom_info = NULL; @@ -234,6 +235,7 @@ static NTSTATUS auth_sam_netlogon3_auth(const struct auth_context *auth_context, switch (lp_server_role()) { case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: break; default: DBG_ERR("Invalid server role\n"); @@ -252,9 +254,9 @@ static NTSTATUS auth_sam_netlogon3_auth(const struct auth_context *auth_context, if (!is_my_domain) { /* If we are running on a DC that has PASSDB module with domain * information, check if DNS forest name is matching the domain - * name. This is the case of FreeIPA domain controller when - * trusted AD DCs attempt to authenticate FreeIPA users using - * the forest root domain (which is the only domain in FreeIPA). + * name. This is the case of IPA domain controller when + * trusted AD DCs attempt to authenticate IPA users using + * the forest root domain (which is the only domain in IPA). */ struct pdb_domain_info *dom_info = NULL; dom_info = pdb_get_domain_info(mem_ctx); diff --git a/source3/include/smb_macros.h b/source3/include/smb_macros.h index d9583945c55d..def122727f0d 100644 --- a/source3/include/smb_macros.h +++ b/source3/include/smb_macros.h @@ -203,7 +203,7 @@ copy an IP address from one buffer to another Check to see if we are a DC for this domain *****************************************************************************/ -#define IS_DC (lp_server_role()==ROLE_DOMAIN_PDC || lp_server_role()==ROLE_DOMAIN_BDC || lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) +#define IS_DC (lp_server_role()==ROLE_DOMAIN_PDC || lp_server_role()==ROLE_DOMAIN_BDC || lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC || lp_server_role() == ROLE_IPA_DC) #define IS_AD_DC (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) /* diff --git a/source3/lib/netapi/joindomain.c b/source3/lib/netapi/joindomain.c index f2d36fc00dbe..d1710c4b938b 100644 --- a/source3/lib/netapi/joindomain.c +++ b/source3/lib/netapi/joindomain.c @@ -375,6 +375,7 @@ WERROR NetGetJoinInformation_l(struct libnetapi_ctx *ctx, case ROLE_DOMAIN_MEMBER: case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: *r->out.name_type = NetSetupDomainName; break; case ROLE_STANDALONE: diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 300b539748ba..8bcd35f3d88a 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -4412,6 +4412,7 @@ int lp_default_server_announce(void) default_server_announce |= SV_TYPE_DOMAIN_MEMBER; break; case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: default_server_announce |= SV_TYPE_DOMAIN_CTRL; break; case ROLE_DOMAIN_BDC: @@ -4437,7 +4438,8 @@ int lp_default_server_announce(void) bool lp_domain_master(void) { if (Globals._domain_master == Auto) - return (lp_server_role() == ROLE_DOMAIN_PDC); + return (lp_server_role() == ROLE_DOMAIN_PDC || + lp_server_role() == ROLE_IPA_DC); return (bool)Globals._domain_master; } diff --git a/source3/passdb/lookup_sid.c b/source3/passdb/lookup_sid.c index 0e01467b3cb4..a551bcfd24a0 100644 --- a/source3/passdb/lookup_sid.c +++ b/source3/passdb/lookup_sid.c @@ -121,7 +121,7 @@ bool lookup_name(TALLOC_CTX *mem_ctx, /* If we are running on a DC that has PASSDB module with domain * information, check if DNS forest name is matching the domain - * name. This is the case of FreeIPA domain controller when + * name. This is the case of IPA domain controller when * trusted AD DC looks up users found in a Global Catalog of * the forest root domain. */ if (!check_global_sam && (IS_DC)) { diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c index 1964eb5a4489..f98f0c986746 100644 --- a/source3/passdb/machine_account_secrets.c +++ b/source3/passdb/machine_account_secrets.c @@ -198,7 +198,8 @@ bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid) dyn_guid = (struct GUID *)secrets_fetch(key, &size); if (!dyn_guid) { - if (lp_server_role() == ROLE_DOMAIN_PDC) { + if (lp_server_role() == ROLE_DOMAIN_PDC || + lp_server_role() == ROLE_IPA_DC) { new_guid = GUID_random(); if (!secrets_store_domain_guid(domain, &new_guid)) return False; @@ -314,9 +315,7 @@ static const char *trust_keystr(const char *domain) enum netr_SchannelType get_default_sec_channel(void) { - if (lp_server_role() == ROLE_DOMAIN_BDC || - lp_server_role() == ROLE_DOMAIN_PDC || - lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + if (IS_DC) { return SEC_CHAN_BDC; } else { return SEC_CHAN_WKSTA; diff --git a/source3/registry/reg_backend_prod_options.c b/source3/registry/reg_backend_prod_options.c index 655c587ac405..7bd3f324c373 100644 --- a/source3/registry/reg_backend_prod_options.c +++ b/source3/registry/reg_backend_prod_options.c @@ -40,6 +40,7 @@ static int prod_options_fetch_values(const char *key, struct regval_ctr *regvals switch (lp_server_role()) { case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: value_ascii = "LanmanNT"; break; case ROLE_STANDALONE: diff --git a/source3/rpc_server/dssetup/srv_dssetup_nt.c b/source3/rpc_server/dssetup/srv_dssetup_nt.c index 64569382695a..932452bc13ba 100644 --- a/source3/rpc_server/dssetup/srv_dssetup_nt.c +++ b/source3/rpc_server/dssetup/srv_dssetup_nt.c @@ -63,6 +63,7 @@ static WERROR fill_dsrole_dominfo_basic(TALLOC_CTX *ctx, basic->domain = get_global_sam_name(); break; case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: basic->role = DS_ROLE_PRIMARY_DC; basic->domain = get_global_sam_name(); break; diff --git a/source3/smbd/server.c b/source3/smbd/server.c index a0456100afe1..26961fa6cf49 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -1979,7 +1979,7 @@ extern void build_options(bool screen); exit_daemon("smbd can not open secrets.tdb", EACCES); } - if (lp_server_role() == ROLE_DOMAIN_BDC || lp_server_role() == ROLE_DOMAIN_PDC) { + if (lp_server_role() == ROLE_DOMAIN_BDC || lp_server_role() == ROLE_DOMAIN_PDC || lp_server_role() == ROLE_IPA_DC) { struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers()); if (!open_schannel_session_store(NULL, lp_ctx)) { exit_daemon("ERROR: Samba cannot open schannel store for secured NETLOGON operations.", EACCES); diff --git a/source3/winbindd/winbindd_misc.c b/source3/winbindd/winbindd_misc.c index 451ad6aee142..db7e1c87dee1 100644 --- a/source3/winbindd/winbindd_misc.c +++ b/source3/winbindd/winbindd_misc.c @@ -76,7 +76,7 @@ static char *get_trust_type_string(TALLOC_CTX *mem_ctx, case SEC_CHAN_BDC: { int role = lp_server_role(); - if (role == ROLE_DOMAIN_PDC) { + if (role == ROLE_DOMAIN_PDC || role == ROLE_IPA_DC) { s = talloc_strdup(mem_ctx, "PDC"); if (s == NULL) { return NULL; diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c index ac4cd731c09a..42ddbfd2f44e 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -1254,15 +1254,37 @@ bool init_domain_list(void) secure_channel_type = SEC_CHAN_LOCAL; } - status = add_trusted_domain(get_global_sam_name(), - NULL, - get_global_sam_sid(), - LSA_TRUST_TYPE_DOWNLEVEL, - trust_flags, - 0, /* trust_attribs */ - secure_channel_type, - NULL, - &domain); + if ((pdb_domain_info != NULL) && (role == ROLE_IPA_DC)) { + /* This is IPA DC that presents itself as + * an Active Directory domain controller to trusted AD + * forests but in fact is a classic domain controller. + */ + trust_flags = NETR_TRUST_FLAG_PRIMARY; + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + trust_flags |= NETR_TRUST_FLAG_NATIVE; + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + trust_flags |= NETR_TRUST_FLAG_TREEROOT; + status = add_trusted_domain(pdb_domain_info->name, + pdb_domain_info->dns_domain, + &pdb_domain_info->sid, + LSA_TRUST_TYPE_UPLEVEL, + trust_flags, + LSA_TRUST_ATTRIBUTE_WITHIN_FOREST, + secure_channel_type, + NULL, + &domain); + TALLOC_FREE(pdb_domain_info); + } else { + status = add_trusted_domain(get_global_sam_name(), + NULL, + get_global_sam_sid(), + LSA_TRUST_TYPE_DOWNLEVEL, + trust_flags, + 0, /* trust_attribs */ + secure_channel_type, + NULL, + &domain); + } if (!NT_STATUS_IS_OK(status)) { DBG_ERR("Failed to add local SAM to " "domain to winbindd's internal list\n"); diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c index 4c66f2c23cb6..ea9ff70ce802 100644 --- a/source4/auth/ntlm/auth.c +++ b/source4/auth/ntlm/auth.c @@ -737,6 +737,7 @@ const char **auth_methods_from_lp(TALLOC_CTX *mem_ctx, struct loadparm_context * case ROLE_DOMAIN_BDC: case ROLE_DOMAIN_PDC: case ROLE_ACTIVE_DIRECTORY_DC: + case ROLE_IPA_DC: auth_methods = str_list_make(mem_ctx, "anonymous sam winbind sam_ignoredomain", NULL); break; } diff --git a/source4/kdc/kdc-heimdal.c b/source4/kdc/kdc-heimdal.c index a3c357ca1a66..ba74df4f2ec7 100644 --- a/source4/kdc/kdc-heimdal.c +++ b/source4/kdc/kdc-heimdal.c @@ -276,6 +276,7 @@ static NTSTATUS kdc_task_init(struct task_server *task) return NT_STATUS_INVALID_DOMAIN_ROLE; case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: task_server_terminate( task, "Cannot start KDC as a 'classic Samba' DC", false); return NT_STATUS_INVALID_DOMAIN_ROLE; diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c index cda887d45eee..29c509522beb 100644 --- a/source4/rpc_server/samr/dcesrv_samr.c +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -575,6 +575,7 @@ static NTSTATUS dcesrv_samr_info_DomGeneralInformation(struct samr_domain_state break; case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: case ROLE_AUTO: return NT_STATUS_INTERNAL_ERROR; case ROLE_DOMAIN_MEMBER: @@ -723,6 +724,7 @@ static NTSTATUS dcesrv_samr_info_DomInfo7(struct samr_domain_state *state, break; case ROLE_DOMAIN_PDC: case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: case ROLE_AUTO: return NT_STATUS_INTERNAL_ERROR; case ROLE_DOMAIN_MEMBER: -- 2.25.1 From 0e03973c71b3c42087d9983f6ea6c9a1b4bd8dce Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 18:11:57 +0200 Subject: [PATCH 104/224] CVE-2020-25719 CVE-2020-25717: auth/gensec: always require a PAC in domain mode (DC or member) AD domains always provide a PAC unless UF_NO_AUTH_DATA_REQUIRED is set on the service account, which can only be explicitly configured, but that's an invalid configuration! We still try to support standalone servers in an MIT realm, as legacy setup. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett [jsutton@samba.org Removed knownfail entries] --- auth/gensec/gensec_util.c | 27 +++++++++++++++++++++++---- selftest/knownfail.d/no-pac | 4 ---- 2 files changed, 23 insertions(+), 8 deletions(-) delete mode 100644 selftest/knownfail.d/no-pac diff --git a/auth/gensec/gensec_util.c b/auth/gensec/gensec_util.c index e185acc0c205..694661b53b56 100644 --- a/auth/gensec/gensec_util.c +++ b/auth/gensec/gensec_util.c @@ -25,6 +25,8 @@ #include "auth/gensec/gensec_internal.h" #include "auth/common_auth.h" #include "../lib/util/asn1.h" +#include "param/param.h" +#include "libds/common/roles.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -46,10 +48,27 @@ NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS; if (!pac_blob) { - if (gensec_setting_bool(gensec_security->settings, "gensec", "require_pac", false)) { - DEBUG(1, ("Unable to find PAC in ticket from %s, failing to allow access\n", - principal_string)); - return NT_STATUS_ACCESS_DENIED; + enum server_role server_role = + lpcfg_server_role(gensec_security->settings->lp_ctx); + + /* + * For any domain setup (DC or member) we require having + * a PAC, as the service ticket comes from an AD DC, + * which will always provide a PAC, unless + * UF_NO_AUTH_DATA_REQUIRED is configured for our + * account, but that's just an invalid configuration, + * the admin configured for us! + * + * As a legacy case, we still allow kerberos tickets from an MIT + * realm, but only in standalone mode. In that mode we'll only + * ever accept a kerberos authentication with a keytab file + * being explicitly configured via the 'keytab method' option. + */ + if (server_role != ROLE_STANDALONE) { + DBG_WARNING("Unable to find PAC in ticket from %s, " + "failing to allow access\n", + principal_string); + return NT_STATUS_NO_IMPERSONATION_TOKEN; } DBG_NOTICE("Unable to find PAC for %s, resorting to local " "user lookup\n", principal_string); diff --git a/selftest/knownfail.d/no-pac b/selftest/knownfail.d/no-pac deleted file mode 100644 index 9723d581c2a1..000000000000 --- a/selftest/knownfail.d/no-pac +++ /dev/null @@ -1,4 +0,0 @@ -^samba.tests.krb5.test_ccache.samba.tests.krb5.test_ccache.CcacheTests.test_ccache_no_pac -^samba.tests.krb5.test_ldap.samba.tests.krb5.test_ldap.LdapTests.test_ldap_no_pac -^samba.tests.krb5.test_rpc.samba.tests.krb5.test_rpc.RpcTests.test_rpc_no_pac -^samba.tests.krb5.test_smb.samba.tests.krb5.test_smb.SmbTests.test_smb_no_pac -- 2.25.1 From b491e3abaeae1753f562434e94b3fa58cd9bcd7e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 11 Oct 2021 23:17:19 +0200 Subject: [PATCH 105/224] CVE-2020-25719 CVE-2020-25717: s4:auth: remove unused auth_generate_session_info_principal() We'll require a PAC at the main gensec layer already. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source4/auth/auth.h | 8 ------ source4/auth/ntlm/auth.c | 49 ++++-------------------------------- source4/auth/ntlm/auth_sam.c | 12 --------- 3 files changed, 5 insertions(+), 64 deletions(-) diff --git a/source4/auth/auth.h b/source4/auth/auth.h index 3f9fb1ae3cbc..6b7db99cbe2d 100644 --- a/source4/auth/auth.h +++ b/source4/auth/auth.h @@ -69,14 +69,6 @@ struct auth_operations { TALLOC_CTX *mem_ctx, struct auth_user_info_dc **interim_info, bool *authoritative); - - /* Lookup a 'session info interim' return based only on the principal or DN */ - NTSTATUS (*get_user_info_dc_principal)(TALLOC_CTX *mem_ctx, - struct auth4_context *auth_context, - const char *principal, - struct ldb_dn *user_dn, - struct auth_user_info_dc **interim_info); - uint32_t flags; }; struct auth_method_context { diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c index ea9ff70ce802..3dd2ffc92765 100644 --- a/source4/auth/ntlm/auth.c +++ b/source4/auth/ntlm/auth.c @@ -86,48 +86,6 @@ _PUBLIC_ NTSTATUS auth_get_challenge(struct auth4_context *auth_ctx, uint8_t cha return NT_STATUS_OK; } -/**************************************************************************** -Used in the gensec_gssapi and gensec_krb5 server-side code, where the -PAC isn't available, and for tokenGroups in the DSDB stack. - - Supply either a principal or a DN -****************************************************************************/ -static NTSTATUS auth_generate_session_info_principal(struct auth4_context *auth_ctx, - TALLOC_CTX *mem_ctx, - const char *principal, - struct ldb_dn *user_dn, - uint32_t session_info_flags, - struct auth_session_info **session_info) -{ - NTSTATUS nt_status; - struct auth_method_context *method; - struct auth_user_info_dc *user_info_dc; - - for (method = auth_ctx->methods; method; method = method->next) { - if (!method->ops->get_user_info_dc_principal) { - continue; - } - - nt_status = method->ops->get_user_info_dc_principal(mem_ctx, auth_ctx, principal, user_dn, &user_info_dc); - if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOT_IMPLEMENTED)) { - continue; - } - if (!NT_STATUS_IS_OK(nt_status)) { - return nt_status; - } - - nt_status = auth_generate_session_info_wrapper(auth_ctx, mem_ctx, - user_info_dc, - user_info_dc->info->account_name, - session_info_flags, session_info); - talloc_free(user_info_dc); - - return nt_status; - } - - return NT_STATUS_NOT_IMPLEMENTED; -} - /** * Check a user's Plaintext, LM or NTLM password. * (sync version) @@ -627,8 +585,11 @@ static NTSTATUS auth_generate_session_info_pac(struct auth4_context *auth_ctx, TALLOC_CTX *tmp_ctx; if (!pac_blob) { - return auth_generate_session_info_principal(auth_ctx, mem_ctx, principal_name, - NULL, session_info_flags, session_info); + /* + * This should already be catched at the main + * gensec layer, but better check twice + */ + return NT_STATUS_INTERNAL_ERROR; } tmp_ctx = talloc_named(mem_ctx, 0, "gensec_gssapi_session_info context"); diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c index a521bc94bc4a..dbbf97665db3 100644 --- a/source4/auth/ntlm/auth_sam.c +++ b/source4/auth/ntlm/auth_sam.c @@ -938,22 +938,11 @@ static NTSTATUS authsam_want_check(struct auth_method_context *ctx, return NT_STATUS_OK; } -/* Wrapper for the auth subsystem pointer */ -static NTSTATUS authsam_get_user_info_dc_principal_wrapper(TALLOC_CTX *mem_ctx, - struct auth4_context *auth_context, - const char *principal, - struct ldb_dn *user_dn, - struct auth_user_info_dc **user_info_dc) -{ - return authsam_get_user_info_dc_principal(mem_ctx, auth_context->lp_ctx, auth_context->sam_ctx, - principal, user_dn, user_info_dc); -} static const struct auth_operations sam_ignoredomain_ops = { .name = "sam_ignoredomain", .want_check = authsam_ignoredomain_want_check, .check_password_send = authsam_check_password_send, .check_password_recv = authsam_check_password_recv, - .get_user_info_dc_principal = authsam_get_user_info_dc_principal_wrapper, }; static const struct auth_operations sam_ops = { @@ -961,7 +950,6 @@ static const struct auth_operations sam_ops = { .want_check = authsam_want_check, .check_password_send = authsam_check_password_send, .check_password_recv = authsam_check_password_recv, - .get_user_info_dc_principal = authsam_get_user_info_dc_principal_wrapper, }; _PUBLIC_ NTSTATUS auth4_sam_init(TALLOC_CTX *); -- 2.25.1 From 02b54b795a1b0e6ecd51d2fdc8d9829b45bcb1d6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 12:27:28 +0200 Subject: [PATCH 106/224] CVE-2020-25717: s3:ntlm_auth: fix memory leaks in ntlm_auth_generate_session_info_pac() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/utils/ntlm_auth.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index b3ab3a473bf7..16431771e74a 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -818,23 +818,27 @@ static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_c if (!p) { DEBUG(3, ("[%s] Doesn't look like a valid principal\n", princ_name)); - return NT_STATUS_LOGON_FAILURE; + status = NT_STATUS_LOGON_FAILURE; + goto done; } user = talloc_strndup(mem_ctx, princ_name, p - princ_name); if (!user) { - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto done; } realm = talloc_strdup(talloc_tos(), p + 1); if (!realm) { - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto done; } if (!strequal(realm, lp_realm())) { DEBUG(3, ("Ticket for foreign realm %s@%s\n", user, realm)); if (!lp_allow_trusted_domains()) { - return NT_STATUS_LOGON_FAILURE; + status = NT_STATUS_LOGON_FAILURE; + goto done; } } @@ -842,7 +846,8 @@ static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_c domain = talloc_strdup(mem_ctx, logon_info->info3.base.logon_domain.string); if (!domain) { - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto done; } DEBUG(10, ("Domain is [%s] (using PAC)\n", domain)); } else { @@ -872,7 +877,8 @@ static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_c domain = talloc_strdup(mem_ctx, realm); } if (!domain) { - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto done; } DEBUG(10, ("Domain is [%s] (using Winbind)\n", domain)); } -- 2.25.1 From c7ea1f3f3bf14fc6686e63bce80ea5722bc58111 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 12:44:01 +0200 Subject: [PATCH 107/224] CVE-2020-25717: s3:ntlm_auth: let ntlm_auth_generate_session_info_pac() base the name on the PAC LOGON_INFO only BUG: https://bugzilla.samba.org/show_bug.cgi?id=14801 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/utils/ntlm_auth.c | 91 ++++++++++++--------------------------- 1 file changed, 28 insertions(+), 63 deletions(-) diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index 16431771e74a..02a23792890a 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -790,10 +790,8 @@ static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_c struct PAC_LOGON_INFO *logon_info = NULL; char *unixuser; NTSTATUS status; - char *domain = NULL; - char *realm = NULL; - char *user = NULL; - char *p; + const char *domain = ""; + const char *user = ""; tmp_ctx = talloc_new(mem_ctx); if (!tmp_ctx) { @@ -810,79 +808,46 @@ static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_c if (!NT_STATUS_IS_OK(status)) { goto done; } - } - - DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name)); - - p = strchr_m(princ_name, '@'); - if (!p) { - DEBUG(3, ("[%s] Doesn't look like a valid principal\n", - princ_name)); - status = NT_STATUS_LOGON_FAILURE; + } else { + status = NT_STATUS_ACCESS_DENIED; + DBG_WARNING("Kerberos ticket for[%s] has no PAC: %s\n", + princ_name, nt_errstr(status)); goto done; } - user = talloc_strndup(mem_ctx, princ_name, p - princ_name); - if (!user) { - status = NT_STATUS_NO_MEMORY; - goto done; + if (logon_info->info3.base.account_name.string != NULL) { + user = logon_info->info3.base.account_name.string; + } else { + user = ""; + } + if (logon_info->info3.base.logon_domain.string != NULL) { + domain = logon_info->info3.base.logon_domain.string; + } else { + domain = ""; } - realm = talloc_strdup(talloc_tos(), p + 1); - if (!realm) { - status = NT_STATUS_NO_MEMORY; + if (strlen(user) == 0 || strlen(domain) == 0) { + status = NT_STATUS_ACCESS_DENIED; + DBG_WARNING("Kerberos ticket for[%s] has invalid " + "account_name[%s]/logon_domain[%s]: %s\n", + princ_name, + logon_info->info3.base.account_name.string, + logon_info->info3.base.logon_domain.string, + nt_errstr(status)); goto done; } - if (!strequal(realm, lp_realm())) { - DEBUG(3, ("Ticket for foreign realm %s@%s\n", user, realm)); + DBG_NOTICE("Kerberos ticket principal name is [%s] " + "account_name[%s]/logon_domain[%s]\n", + princ_name, user, domain); + + if (!strequal(domain, lp_workgroup())) { if (!lp_allow_trusted_domains()) { status = NT_STATUS_LOGON_FAILURE; goto done; } } - if (logon_info && logon_info->info3.base.logon_domain.string) { - domain = talloc_strdup(mem_ctx, - logon_info->info3.base.logon_domain.string); - if (!domain) { - status = NT_STATUS_NO_MEMORY; - goto done; - } - DEBUG(10, ("Domain is [%s] (using PAC)\n", domain)); - } else { - - /* If we have winbind running, we can (and must) shorten the - username by using the short netbios name. Otherwise we will - have inconsistent user names. With Kerberos, we get the - fully qualified realm, with ntlmssp we get the short - name. And even w2k3 does use ntlmssp if you for example - connect to an ip address. */ - - wbcErr wbc_status; - struct wbcDomainInfo *info = NULL; - - DEBUG(10, ("Mapping [%s] to short name using winbindd\n", - realm)); - - wbc_status = wbcDomainInfo(realm, &info); - - if (WBC_ERROR_IS_OK(wbc_status)) { - domain = talloc_strdup(mem_ctx, - info->short_name); - wbcFreeMemory(info); - } else { - DEBUG(3, ("Could not find short name: %s\n", - wbcErrorString(wbc_status))); - domain = talloc_strdup(mem_ctx, realm); - } - if (!domain) { - status = NT_STATUS_NO_MEMORY; - goto done; - } - DEBUG(10, ("Domain is [%s] (using Winbind)\n", domain)); - } - unixuser = talloc_asprintf(tmp_ctx, "%s%c%s", domain, winbind_separator(), user); if (!unixuser) { status = NT_STATUS_NO_MEMORY; -- 2.25.1 From 116db5341fa2ce7196dd2e2c6e37566d454fb3e2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 19:42:20 +0200 Subject: [PATCH 108/224] CVE-2020-25717: s3:auth: let auth3_generate_session_info_pac() delegate everything to make_server_info_wbcAuthUserInfo() This consolidates the code paths used for NTLMSSP and Kerberos! I checked what we were already doing for NTLMSSP, which is this: a) source3/auth/auth_winbind.c calls wbcAuthenticateUserEx() b) as a domain member we require a valid response from winbindd, otherwise we'll return NT_STATUS_NO_LOGON_SERVERS c) we call make_server_info_wbcAuthUserInfo(), which internally calls make_server_info_info3() d) auth_check_ntlm_password() calls smb_pam_accountcheck(unix_username, rhost), where rhost is only an ipv4 or ipv6 address (without reverse dns lookup) e) from auth3_check_password_send/auth3_check_password_recv() server_returned_info will be passed to auth3_generate_session_info(), triggered by gensec_session_info(), which means we'll call into create_local_token() in order to transform auth_serversupplied_info into auth_session_info. For Kerberos gensec_session_info() will call auth3_generate_session_info_pac() via the gensec_generate_session_info_pac() helper function. The current logic is this: a) gensec_generate_session_info_pac() is the function that evaluates the 'gensec:require_pac', which defaulted to 'no' before. b) auth3_generate_session_info_pac() called wbcAuthenticateUserEx() in order to pass the PAC blob to winbindd, but only to prime its cache, e.g. netsamlogon cache and others. Most failures were just ignored. c) If the PAC blob is available, it extracted the PAC_LOGON_INFO from it. d) Then we called the horrible get_user_from_kerberos_info() function: - It uses a first part of the tickets principal name (before the @) as username and combines that with the 'logon_info->base.logon_domain' if the logon_info (PAC) is present. - As a fallback without a PAC it's tries to ask winbindd for a mapping from realm to netbios domain name. - Finally is falls back to using the realm as netbios domain name With this information is builds 'userdomain+winbind_separator+useraccount' and calls map_username() followed by smb_getpwnam() with create=true, Note this is similar to the make_server_info_info3() => check_account() => smb_getpwnam() logic under 3. - It also calls smb_pam_accountcheck(), but may pass the reverse DNS lookup name instead of the ip address as rhost. - It does some MAP_TO_GUEST_ON_BAD_UID logic and auto creates the guest account. e) We called create_info3_from_pac_logon_info() f) make_session_info_krb5() calls gets called and triggers this: - If get_user_from_kerberos_info() mapped to guest, it calls make_server_info_guest() - If create_info3_from_pac_logon_info() created a info3 from logon_info, it calls make_server_info_info3() - Without a PAC it tries pdb_getsampwnam()/make_server_info_sam() with a fallback to make_server_info_pw() From there it calls create_local_token() I tried to change auth3_generate_session_info_pac() to behave similar to auth_winbind.c together with auth3_generate_session_info() as a domain member, as we now rely on a PAC: a) As domain member we require a PAC and always call wbcAuthenticateUserEx() and require a valid response! b) we call make_server_info_wbcAuthUserInfo(), which internally calls make_server_info_info3(). Note make_server_info_info3() handles MAP_TO_GUEST_ON_BAD_UID and make_server_info_guest() internally. c) Similar to auth_check_ntlm_password() we now call smb_pam_accountcheck(unix_username, rhost), where rhost is only an ipv4 or ipv6 address (without reverse dns lookup) d) From there it calls create_local_token() As standalone server (in an MIT realm) we continue with the already existing code logic, which works without a PAC: a) we keep smb_getpwnam() with create=true logic as it also requires an explicit 'add user script' option. b) In the following commits we assert that there's actually no PAC in this mode, which means we can remove unused and confusing code. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14646 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 137 ++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 28 deletions(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index f6a501f1df7b..a8390b1b1565 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -46,6 +46,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, uint32_t session_info_flags, struct auth_session_info **session_info) { + enum server_role server_role = lp_server_role(); TALLOC_CTX *tmp_ctx; struct PAC_LOGON_INFO *logon_info = NULL; struct netr_SamInfo3 *info3_copy = NULL; @@ -54,39 +55,59 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, char *ntuser; char *ntdomain; char *username; - char *rhost; + const char *rhost; struct passwd *pw; NTSTATUS status; - int rc; tmp_ctx = talloc_new(mem_ctx); if (!tmp_ctx) { return NT_STATUS_NO_MEMORY; } - if (pac_blob) { -#ifdef HAVE_KRB5 + if (tsocket_address_is_inet(remote_address, "ip")) { + rhost = tsocket_address_inet_addr_string( + remote_address, tmp_ctx); + if (rhost == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + } else { + rhost = "127.0.0.1"; + } + + if (server_role != ROLE_STANDALONE) { struct wbcAuthUserParams params = { 0 }; struct wbcAuthUserInfo *info = NULL; struct wbcAuthErrorInfo *err = NULL; + struct auth_serversupplied_info *server_info = NULL; + char *original_user_name = NULL; + char *p = NULL; wbcErr wbc_err; + if (pac_blob == NULL) { + /* + * This should already be catched at the main + * gensec layer, but better check twice + */ + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + /* * Let winbind decode the PAC. * This will also store the user * data in the netsamlogon cache. * - * We need to do this *before* we - * call get_user_from_kerberos_info() - * as that does a user lookup that - * expects info in the netsamlogon cache. - * - * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=11259 + * This used to be a cache prime + * optimization, but now we delegate + * all logic to winbindd, as we require + * winbindd as domain member anyway. */ params.level = WBC_AUTH_USER_LEVEL_PAC; params.password.pac.data = pac_blob->data; params.password.pac.length = pac_blob->length; + /* we are contacting the privileged pipe */ become_root(); wbc_err = wbcAuthenticateUserEx(¶ms, &info, &err); unbecome_root(); @@ -99,18 +120,90 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, */ switch (wbc_err) { - case WBC_ERR_WINBIND_NOT_AVAILABLE: case WBC_ERR_SUCCESS: break; + case WBC_ERR_WINBIND_NOT_AVAILABLE: + status = NT_STATUS_NO_LOGON_SERVERS; + DBG_ERR("winbindd not running - " + "but required as domain member: %s\n", + nt_errstr(status)); + goto done; case WBC_ERR_AUTH_ERROR: status = NT_STATUS(err->nt_status); wbcFreeMemory(err); goto done; + case WBC_ERR_NO_MEMORY: + status = NT_STATUS_NO_MEMORY; + goto done; default: status = NT_STATUS_LOGON_FAILURE; goto done; } + status = make_server_info_wbcAuthUserInfo(tmp_ctx, + info->account_name, + info->domain_name, + info, &server_info); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("make_server_info_wbcAuthUserInfo failed: %s\n", + nt_errstr(status))); + goto done; + } + + /* We skip doing this step if the caller asked us not to */ + if (!(server_info->guest)) { + const char *unix_username = server_info->unix_name; + + /* We might not be root if we are an RPC call */ + become_root(); + status = smb_pam_accountcheck(unix_username, rhost); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("check_ntlm_password: PAM Account for user [%s] " + "FAILED with error %s\n", + unix_username, nt_errstr(status))); + goto done; + } + + DEBUG(5, ("check_ntlm_password: PAM Account for user [%s] " + "succeeded\n", unix_username)); + } + + DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name)); + + p = strchr_m(princ_name, '@'); + if (!p) { + DEBUG(3, ("[%s] Doesn't look like a valid principal\n", + princ_name)); + status = NT_STATUS_LOGON_FAILURE; + goto done; + } + + original_user_name = talloc_strndup(tmp_ctx, princ_name, p - princ_name); + if (original_user_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + status = create_local_token(mem_ctx, + server_info, + NULL, + original_user_name, + session_info); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("create_local_token failed: %s\n", + nt_errstr(status))); + goto done; + } + + goto session_info_ready; + } + + /* This is the standalone legacy code path */ + + if (pac_blob != NULL) { +#ifdef HAVE_KRB5 status = kerberos_pac_logon_info(tmp_ctx, *pac_blob, NULL, NULL, NULL, NULL, 0, &logon_info); #else @@ -121,22 +214,6 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, } } - rc = get_remote_hostname(remote_address, - &rhost, - tmp_ctx); - if (rc < 0) { - status = NT_STATUS_NO_MEMORY; - goto done; - } - if (strequal(rhost, "UNKNOWN")) { - rhost = tsocket_address_inet_addr_string(remote_address, - tmp_ctx); - if (rhost == NULL) { - status = NT_STATUS_NO_MEMORY; - goto done; - } - } - status = get_user_from_kerberos_info(tmp_ctx, rhost, princ_name, logon_info, &is_mapped, &is_guest, @@ -170,6 +247,8 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, goto done; } +session_info_ready: + /* setup the string used by %U */ set_current_user_info((*session_info)->unix_info->sanitized_username, (*session_info)->unix_info->unix_name, @@ -179,7 +258,9 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, lp_load_with_shares(get_dyn_CONFIGFILE()); DEBUG(5, (__location__ "OK: user: %s domain: %s client: %s\n", - ntuser, ntdomain, rhost)); + (*session_info)->info->account_name, + (*session_info)->info->domain_name, + rhost)); status = NT_STATUS_OK; -- 2.25.1 From e5cfd8dba081bb888447359d2fe376af41e0f712 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 17:14:01 +0200 Subject: [PATCH 109/224] CVE-2020-25717: selftest: configure 'ktest' env with winbindd and idmap_autorid The 'ktest' environment was/is designed to test kerberos in an active directory member setup. It was created at a time we wanted to test smbd/winbindd with kerberos without having the source4 ad dc available. This still applies to testing the build with system krb5 libraries but without relying on a running ad dc. As a domain member setup requires a running winbindd, we should test it that way, in order to reflect a valid setup. As a side effect it provides a way to demonstrate that we can accept smb connections authenticated via kerberos, but no connection to a domain controller! In order get this working offline, we need an idmap backend with ID_TYPE_BOTH support, so we use 'autorid', which should be the default choice. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14646 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ktest | 26 -------------------------- selftest/target/Samba3.pm | 12 +++++------- 2 files changed, 5 insertions(+), 33 deletions(-) delete mode 100644 selftest/knownfail.d/ktest diff --git a/selftest/knownfail.d/ktest b/selftest/knownfail.d/ktest deleted file mode 100644 index 809612ba0b94..000000000000 --- a/selftest/knownfail.d/ktest +++ /dev/null @@ -1,26 +0,0 @@ -^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2...lsa.LookupSidsReply.ktest -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5...rpcclient.ktest:local -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5...rpcclient.ktest:local -^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,connect...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,connect...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,packet...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,packet...lsa.LookupSidsReply.ktest -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,packet...rpcclient.ktest:local -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,packet...rpcclient.ktest:local -^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,sign...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,sign...lsa.LookupSidsReply.ktest -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,sign...rpcclient.ktest:local -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,sign...rpcclient.ktest:local -^samba3.rpc.lsa.lookupsids.krb5.with.old.ccache.ncacn_np.with..smb2,seal...lsa.LookupSidsReply.ktest -^samba3.rpc.lsa.lookupsids.krb5.ncacn_np.with..smb2,seal...lsa.LookupSidsReply.ktest -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..krb5,seal...rpcclient.ktest:local -^samba3.blackbox.rpcclient.krb5.ncacn_np.with..spnego,krb5,seal...rpcclient.ktest:local -^samba3.blackbox.smbclient_krb5.old.ccache..smbclient.ktest:local -^samba3.blackbox.smbclient_krb5.new.ccache..smbclient.ktest:local -^samba3.blackbox.smbclient_large_file..krb5.smbclient.large.posix.write.read.ktest:local -^samba3.blackbox.smbclient_large_file..krb5.cmp.of.read.and.written.files.ktest:local -^samba3.blackbox.smbclient_krb5.old.ccache.--client-protection=encrypt.smbclient.ktest:local -^samba3.blackbox.smbclient_krb5.new.ccache.--client-protection=encrypt.smbclient.ktest:local -^samba3.blackbox.smbclient_large_file.--client-protection=encrypt.krb5.smbclient.large.posix.write.read.ktest:local -^samba3.blackbox.smbclient_large_file.--client-protection=encrypt.krb5.cmp.of.read.and.written.files.ktest:local diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index fd5e29f6db55..1c424da7b34c 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -1729,7 +1729,6 @@ sub setup_ktest workgroup = KTEST realm = ktest.samba.example.com security = ads - username map = $prefix/lib/username.map server signing = required server min protocol = SMB3_00 client max protocol = SMB3 @@ -1737,6 +1736,10 @@ sub setup_ktest # This disables NTLM auth against the local SAM, which # we use can then test this setting by. ntlm auth = disabled + + idmap config * : backend = autorid + idmap config * : range = 1000000-1999999 + idmap config * : rangesize = 100000 "; my $ret = $self->provision( @@ -1762,12 +1765,6 @@ sub setup_ktest $ret->{KRB5_CONFIG} = $ctx->{krb5_conf}; - open(USERMAP, ">$prefix/lib/username.map") or die("Unable to open $prefix/lib/username.map"); - print USERMAP " -$ret->{USERNAME} = KTEST\\Administrator -"; - close(USERMAP); - #This is the secrets.tdb created by 'net ads join' from Samba3 to a #Samba4 DC with the same parameters as are being used here. The #domain SID is S-1-5-21-1071277805-689288055-3486227160 @@ -1819,6 +1816,7 @@ $ret->{USERNAME} = KTEST\\Administrator if (not $self->check_or_start( env_vars => $ret, nmbd => "yes", + winbindd => "offline", smbd => "yes")) { return undef; } -- 2.25.1 From 9eead21065ac713ff7b0fb6fddebb97728bce7d6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 18:12:49 +0200 Subject: [PATCH 110/224] CVE-2020-25717: s3:auth: let auth3_generate_session_info_pac() reject a PAC in standalone mode We should be strict in standalone mode, that we only support MIT realms without a PAC in order to keep the code sane. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index a8390b1b1565..babda46fc92d 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -48,8 +48,6 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, { enum server_role server_role = lp_server_role(); TALLOC_CTX *tmp_ctx; - struct PAC_LOGON_INFO *logon_info = NULL; - struct netr_SamInfo3 *info3_copy = NULL; bool is_mapped; bool is_guest; char *ntuser; @@ -203,19 +201,20 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, /* This is the standalone legacy code path */ if (pac_blob != NULL) { -#ifdef HAVE_KRB5 - status = kerberos_pac_logon_info(tmp_ctx, *pac_blob, NULL, NULL, - NULL, NULL, 0, &logon_info); -#else - status = NT_STATUS_ACCESS_DENIED; -#endif + /* + * In standalone mode we don't expect a PAC! + * we only support MIT realms + */ + status = NT_STATUS_BAD_TOKEN_TYPE; + DBG_WARNING("Unexpected PAC for [%s] in standalone mode - %s\n", + princ_name, nt_errstr(status)); if (!NT_STATUS_IS_OK(status)) { goto done; } } status = get_user_from_kerberos_info(tmp_ctx, rhost, - princ_name, logon_info, + princ_name, NULL, &is_mapped, &is_guest, &ntuser, &ntdomain, &username, &pw); @@ -226,19 +225,9 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, goto done; } - /* Get the info3 from the PAC data if we have it */ - if (logon_info) { - status = create_info3_from_pac_logon_info(tmp_ctx, - logon_info, - &info3_copy); - if (!NT_STATUS_IS_OK(status)) { - goto done; - } - } - status = make_session_info_krb5(mem_ctx, ntuser, ntdomain, username, pw, - info3_copy, is_guest, is_mapped, NULL /* No session key for now, caller will sort it out */, + NULL, is_guest, is_mapped, NULL /* No session key for now, caller will sort it out */, session_info); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to map kerberos pac to server info (%s)\n", -- 2.25.1 From d72fa529cfe6d8143704cdd73a50a87650765f6b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 17:59:59 +0200 Subject: [PATCH 111/224] CVE-2020-25717: s3:auth: simplify get_user_from_kerberos_info() by removing the unused logon_info argument This code is only every called in standalone mode on a MIT realm, it means we never have a PAC and we also don't have winbindd arround. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 2 +- source3/auth/proto.h | 1 - source3/auth/user_krb5.c | 57 +++++++------------------------------ 3 files changed, 11 insertions(+), 49 deletions(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index babda46fc92d..8923aec0a4d2 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -214,7 +214,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, } status = get_user_from_kerberos_info(tmp_ctx, rhost, - princ_name, NULL, + princ_name, &is_mapped, &is_guest, &ntuser, &ntdomain, &username, &pw); diff --git a/source3/auth/proto.h b/source3/auth/proto.h index 097b17fee44a..46fae447347f 100644 --- a/source3/auth/proto.h +++ b/source3/auth/proto.h @@ -423,7 +423,6 @@ struct PAC_LOGON_INFO; NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, const char *cli_name, const char *princ_name, - struct PAC_LOGON_INFO *logon_info, bool *is_mapped, bool *mapped_to_guest, char **ntuser, diff --git a/source3/auth/user_krb5.c b/source3/auth/user_krb5.c index 074e8c7eb711..7b69ca6c222e 100644 --- a/source3/auth/user_krb5.c +++ b/source3/auth/user_krb5.c @@ -31,7 +31,6 @@ NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, const char *cli_name, const char *princ_name, - struct PAC_LOGON_INFO *logon_info, bool *is_mapped, bool *mapped_to_guest, char **ntuser, @@ -40,8 +39,8 @@ NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, struct passwd **_pw) { NTSTATUS status; - char *domain = NULL; - char *realm = NULL; + const char *domain = NULL; + const char *realm = NULL; char *user = NULL; char *p; char *fuser = NULL; @@ -62,55 +61,16 @@ NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, return NT_STATUS_NO_MEMORY; } - realm = talloc_strdup(talloc_tos(), p + 1); - if (!realm) { - return NT_STATUS_NO_MEMORY; - } + realm = p + 1; if (!strequal(realm, lp_realm())) { DEBUG(3, ("Ticket for foreign realm %s@%s\n", user, realm)); if (!lp_allow_trusted_domains()) { return NT_STATUS_LOGON_FAILURE; } - } - - if (logon_info && logon_info->info3.base.logon_domain.string) { - domain = talloc_strdup(mem_ctx, - logon_info->info3.base.logon_domain.string); - if (!domain) { - return NT_STATUS_NO_MEMORY; - } - DEBUG(10, ("Domain is [%s] (using PAC)\n", domain)); + domain = realm; } else { - - /* If we have winbind running, we can (and must) shorten the - username by using the short netbios name. Otherwise we will - have inconsistent user names. With Kerberos, we get the - fully qualified realm, with ntlmssp we get the short - name. And even w2k3 does use ntlmssp if you for example - connect to an ip address. */ - - wbcErr wbc_status; - struct wbcDomainInfo *info = NULL; - - DEBUG(10, ("Mapping [%s] to short name using winbindd\n", - realm)); - - wbc_status = wbcDomainInfo(realm, &info); - - if (WBC_ERROR_IS_OK(wbc_status)) { - domain = talloc_strdup(mem_ctx, - info->short_name); - wbcFreeMemory(info); - } else { - DEBUG(3, ("Could not find short name: %s\n", - wbcErrorString(wbc_status))); - domain = talloc_strdup(mem_ctx, realm); - } - if (!domain) { - return NT_STATUS_NO_MEMORY; - } - DEBUG(10, ("Domain is [%s] (using Winbind)\n", domain)); + domain = lp_workgroup(); } fuser = talloc_asprintf(mem_ctx, @@ -175,7 +135,11 @@ NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, return NT_STATUS_NO_MEMORY; } *ntuser = user; - *ntdomain = domain; + *ntdomain = talloc_strdup(mem_ctx, domain); + if (*ntdomain == NULL) { + return NT_STATUS_NO_MEMORY; + } + *_pw = pw; return NT_STATUS_OK; @@ -282,7 +246,6 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx, const char *cli_name, const char *princ_name, - struct PAC_LOGON_INFO *logon_info, bool *is_mapped, bool *mapped_to_guest, char **ntuser, -- 2.25.1 From 16cdca20d6211aa919471ebd677b394d5b4c52a0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 18:03:04 +0200 Subject: [PATCH 112/224] CVE-2020-25717: s3:auth: simplify make_session_info_krb5() by removing unused arguments This is only ever be called in standalone mode with an MIT realm, so we don't have a PAC/info3 structure. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556 Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- source3/auth/auth_generic.c | 2 +- source3/auth/proto.h | 2 -- source3/auth/user_krb5.c | 20 +------------------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c index 8923aec0a4d2..fa22a0b2339b 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -227,7 +227,7 @@ static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx, status = make_session_info_krb5(mem_ctx, ntuser, ntdomain, username, pw, - NULL, is_guest, is_mapped, NULL /* No session key for now, caller will sort it out */, + is_guest, is_mapped, session_info); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to map kerberos pac to server info (%s)\n", diff --git a/source3/auth/proto.h b/source3/auth/proto.h index 46fae447347f..fb7f663512b6 100644 --- a/source3/auth/proto.h +++ b/source3/auth/proto.h @@ -434,9 +434,7 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, char *ntdomain, char *username, struct passwd *pw, - const struct netr_SamInfo3 *info3, bool mapped_to_guest, bool username_was_mapped, - DATA_BLOB *session_key, struct auth_session_info **session_info); /* The following definitions come from auth/auth_samba4.c */ diff --git a/source3/auth/user_krb5.c b/source3/auth/user_krb5.c index 7b69ca6c222e..b8f37cbeee05 100644 --- a/source3/auth/user_krb5.c +++ b/source3/auth/user_krb5.c @@ -150,9 +150,7 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, char *ntdomain, char *username, struct passwd *pw, - const struct netr_SamInfo3 *info3, bool mapped_to_guest, bool username_was_mapped, - DATA_BLOB *session_key, struct auth_session_info **session_info) { NTSTATUS status; @@ -166,20 +164,6 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, return status; } - } else if (info3) { - /* pass the unmapped username here since map_username() - will be called again in make_server_info_info3() */ - - status = make_server_info_info3(mem_ctx, - ntuser, ntdomain, - &server_info, - info3); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(1, ("make_server_info_info3 failed: %s!\n", - nt_errstr(status))); - return status; - } - } else { /* * We didn't get a PAC, we have to make up the user @@ -231,7 +215,7 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, server_info->nss_token |= username_was_mapped; - status = create_local_token(mem_ctx, server_info, session_key, ntuser, session_info); + status = create_local_token(mem_ctx, server_info, NULL, ntuser, session_info); talloc_free(server_info); if (!NT_STATUS_IS_OK(status)) { DEBUG(10,("failed to create local token: %s\n", @@ -261,9 +245,7 @@ NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx, char *ntdomain, char *username, struct passwd *pw, - const struct netr_SamInfo3 *info3, bool mapped_to_guest, bool username_was_mapped, - DATA_BLOB *session_key, struct auth_session_info **session_info) { return NT_STATUS_NOT_IMPLEMENTED; -- 2.25.1 From d80d3fca492126e8fbfd38da205521a9d44b102d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 14:07:41 +1300 Subject: [PATCH 113/224] CVE-2020-25722 Add test for SPN deletion followed by addition BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Removed transaction hooks, these do nothing over remote LDAP] --- selftest/knownfail.d/acl-spn | 1 + source4/dsdb/tests/python/acl.py | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 selftest/knownfail.d/acl-spn diff --git a/selftest/knownfail.d/acl-spn b/selftest/knownfail.d/acl-spn new file mode 100644 index 000000000000..e68add920a61 --- /dev/null +++ b/selftest/knownfail.d/acl-spn @@ -0,0 +1 @@ +^samba4.ldap.acl.python.*AclSPNTests.test_delete_add_spn diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py index a859196ee940..e64d36e99c03 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -2190,6 +2190,54 @@ class AclSPNTests(AclTests): def test_spn_rodc(self): self.dc_spn_test(self.rodcctx) + def test_delete_add_spn(self): + # Grant Validated-SPN property. + mod = f'(OA;;SW;{security.GUID_DRS_VALIDATE_SPN};;{self.user_sid1})' + self.sd_utils.dacl_add_ace(self.computerdn, mod) + + spn_base = f'HOST/{self.computername}' + + allowed_spn = f'{spn_base}.{self.dcctx.dnsdomain}' + not_allowed_spn = f'{spn_base}/{self.dcctx.get_domain_name()}' + + # Ensure we are able to add an allowed SPN. + msg = Message(Dn(self.ldb_user1, self.computerdn)) + msg['servicePrincipalName'] = MessageElement(allowed_spn, + FLAG_MOD_ADD, + 'servicePrincipalName') + self.ldb_user1.modify(msg) + + # Ensure we are not able to add a disallowed SPN. + msg = Message(Dn(self.ldb_user1, self.computerdn)) + msg['servicePrincipalName'] = MessageElement(not_allowed_spn, + FLAG_MOD_ADD, + 'servicePrincipalName') + try: + self.ldb_user1.modify(msg) + except LdbError as e: + num, _ = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail(f'able to add disallowed SPN {not_allowed_spn}') + + # Ensure that deleting an existing SPN followed by adding a disallowed + # SPN fails. + msg = Message(Dn(self.ldb_user1, self.computerdn)) + msg['0'] = MessageElement([], + FLAG_MOD_DELETE, + 'servicePrincipalName') + msg['1'] = MessageElement(not_allowed_spn, + FLAG_MOD_ADD, + 'servicePrincipalName') + try: + self.ldb_user1.modify(msg) + except LdbError as e: + num, _ = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail(f'able to add disallowed SPN {not_allowed_spn}') + + # tests SEC_ADS_LIST vs. SEC_ADS_LIST_OBJECT @DynamicTestCase class AclVisibiltyTests(AclTests): -- 2.25.1 From 6652bcbbb136c86eb314a07f8b8277b44b61f136 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 8 Oct 2021 15:49:31 +1300 Subject: [PATCH 114/224] CVE-2020-25722 s4:dsdb:tests: Add missing self.fail() calls Without these calls the tests could pass if an expected error did not occur. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14832 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Included in backport as changing ACLs while ACL tests are not checking for unexpected success would be bad] --- source4/dsdb/tests/python/acl.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py index e64d36e99c03..f0e767c3598f 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -1647,6 +1647,8 @@ userPassword: thatsAcomplPASS1 except LdbError as e31: (num, _) = e31.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + pass # Not self.fail() as we normally want success. def test_reset_password3(self): """Grant WP and see what happens (unicodePwd)""" @@ -1708,6 +1710,8 @@ userPassword: thatsAcomplPASS1 except LdbError as e34: (num, _) = e34.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + pass # Not self.fail() as we normally want success class AclExtendedTests(AclTests): @@ -2024,6 +2028,8 @@ class AclSPNTests(AclTests): except LdbError as e39: (num, _) = e39.args self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + else: + self.fail() mod = "(OA;;SW;f3a64788-5306-11d1-a9c5-0000f80367c1;;%s)" % str(self.user_sid1) self.sd_utils.dacl_add_ace(ctx.acct_dn, mod) @@ -2062,29 +2068,39 @@ class AclSPNTests(AclTests): except LdbError as e40: (num, _) = e40.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, ctx.acct_dn, "ldap/%s.%s/DomainDnsZones.%s" % (ctx.myname, ctx.dnsdomain, ctx.dnsdomain)) except LdbError as e41: (num, _) = e41.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, ctx.acct_dn, "nosuchservice/%s/%s" % ("abcd", "abcd")) except LdbError as e42: (num, _) = e42.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, ctx.acct_dn, "GC/%s.%s/%s" % (ctx.myname, ctx.dnsdomain, netbiosdomain)) except LdbError as e43: (num, _) = e43.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, ctx.acct_dn, "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s" % (ctx.ntds_guid, ctx.dnsdomain)) except LdbError as e44: (num, _) = e44.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() def test_computer_spn(self): # with WP, any value can be set @@ -2130,6 +2146,8 @@ class AclSPNTests(AclTests): except LdbError as e45: (num, _) = e45.args self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + else: + self.fail() mod = "(OA;;SW;f3a64788-5306-11d1-a9c5-0000f80367c1;;%s)" % str(self.user_sid1) self.sd_utils.dacl_add_ace(self.computerdn, mod) @@ -2148,41 +2166,55 @@ class AclSPNTests(AclTests): except LdbError as e46: (num, _) = e46.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "HOST/%s.%s/%s" % (self.computername, self.dcctx.dnsdomain, netbiosdomain)) except LdbError as e47: (num, _) = e47.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "HOST/%s/%s" % (self.computername, self.dcctx.dnsdomain)) except LdbError as e48: (num, _) = e48.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "HOST/%s.%s/%s" % (self.computername, self.dcctx.dnsdomain, self.dcctx.dnsdomain)) except LdbError as e49: (num, _) = e49.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "GC/%s.%s/%s" % (self.computername, self.dcctx.dnsdomain, self.dcctx.dnsforest)) except LdbError as e50: (num, _) = e50.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "ldap/%s/%s" % (self.computername, netbiosdomain)) except LdbError as e51: (num, _) = e51.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() try: self.replace_spn(self.ldb_user1, self.computerdn, "ldap/%s.%s/ForestDnsZones.%s" % (self.computername, self.dcctx.dnsdomain, self.dcctx.dnsdomain)) except LdbError as e52: (num, _) = e52.args self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() def test_spn_rwdc(self): self.dc_spn_test(self.dcctx) -- 2.25.1 From 8dae179b5675a3c6b9b3fd402edc126cbd6dec61 Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Mon, 25 Oct 2021 14:54:56 +0300 Subject: [PATCH 115/224] CVE-2020-25722: s4-acl: test Control Access Rights honor the Applies-to attribute Validate Writes and Control Access Rights should only grant access if the object is of the type listed in the Right's appliesTo attribute. Tests to verify this behavior BUG: https://bugzilla.samba.org/show_bug.cgi?id=14832 Signed-off-by: Nadezhda Ivanova Reviewed-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/bug-14832 | 1 + source4/dsdb/tests/python/acl.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 selftest/knownfail.d/bug-14832 diff --git a/selftest/knownfail.d/bug-14832 b/selftest/knownfail.d/bug-14832 new file mode 100644 index 000000000000..059a1778e65f --- /dev/null +++ b/selftest/knownfail.d/bug-14832 @@ -0,0 +1 @@ +^samba4.ldap.acl.python\(.*\).__main__.AclSPNTests.test_user_spn\(.*\) \ No newline at end of file diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py index f0e767c3598f..df0fe12bf29d 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -1926,6 +1926,8 @@ class AclSPNTests(AclTests): self.computername = "testcomp8" self.test_user = "spn_test_user8" self.computerdn = "CN=%s,CN=computers,%s" % (self.computername, self.base_dn) + self.user_object = "user_with_spn" + self.user_object_dn = "CN=%s,CN=Users,%s" % (self.user_object, self.base_dn) self.dc_dn = "CN=%s,OU=Domain Controllers,%s" % (self.dcname, self.base_dn) self.site = "Default-First-Site-Name" self.rodcctx = DCJoinContext(server=host, creds=creds, lp=lp, @@ -1947,6 +1949,7 @@ class AclSPNTests(AclTests): self.dcctx.cleanup_old_join() delete_force(self.ldb_admin, "cn=%s,cn=computers,%s" % (self.computername, self.base_dn)) delete_force(self.ldb_admin, self.get_user_dn(self.test_user)) + delete_force(self.ldb_admin, self.user_object_dn) del self.ldb_user1 @@ -2222,6 +2225,20 @@ class AclSPNTests(AclTests): def test_spn_rodc(self): self.dc_spn_test(self.rodcctx) + def test_user_spn(self): + #grant SW to a regular user and try to set the spn on a user object + #should get ERR_INSUFFICIENT_ACCESS_RIGHTS, since Validate-SPN only applies to computer + self.ldb_admin.newuser(self.user_object, self.user_pass) + mod = "(OA;;SW;f3a64788-5306-11d1-a9c5-0000f80367c1;;%s)" % str(self.user_sid1) + self.sd_utils.dacl_add_ace(self.user_object_dn, mod) + try: + self.replace_spn(self.ldb_user1, self.user_object_dn, "nosuchservice/%s/%s" % ("abcd", "abcd")) + except LdbError as e60: + (num, _) = e60.args + self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + else: + self.fail() + def test_delete_add_spn(self): # Grant Validated-SPN property. mod = f'(OA;;SW;{security.GUID_DRS_VALIDATE_SPN};;{self.user_sid1})' -- 2.25.1 From 9a099a076ea35bc93869e85bedcb381889d86653 Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Mon, 18 Oct 2021 14:27:59 +0300 Subject: [PATCH 116/224] CVE-2020-25722: s4-acl: Make sure Control Access Rights honor the Applies-to attribute Validate Writes and Control Access Rights only grant access if the object is of the type listed in the Right's appliesTo attribute. For example, even though a Validated-SPN access may be granted to a user object in the SD, it should only pass if the object is of class computer This patch enforces the appliesTo attribute classes for access checks from within the ldb stack. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14832 Signed-off-by: Nadezhda Ivanova Reviewed-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/bug-14832 | 1 - source4/dsdb/common/util.c | 11 +++ source4/dsdb/samdb/ldb_modules/acl.c | 87 +++++++++++++++++++---- source4/dsdb/samdb/ldb_modules/acl_util.c | 40 +++++++++++ source4/dsdb/samdb/ldb_modules/dirsync.c | 13 +++- source4/dsdb/samdb/ldb_modules/samldb.c | 56 ++++++++------- 6 files changed, 168 insertions(+), 40 deletions(-) delete mode 100644 selftest/knownfail.d/bug-14832 diff --git a/selftest/knownfail.d/bug-14832 b/selftest/knownfail.d/bug-14832 deleted file mode 100644 index 059a1778e65f..000000000000 --- a/selftest/knownfail.d/bug-14832 +++ /dev/null @@ -1 +0,0 @@ -^samba4.ldap.acl.python\(.*\).__main__.AclSPNTests.test_user_spn\(.*\) \ No newline at end of file diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 08c18c485a3c..5ce4c0a5e332 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -1183,6 +1183,17 @@ struct ldb_dn *samdb_sites_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) return new_dn; } +struct ldb_dn *samdb_extended_rights_dn(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx) +{ + struct ldb_dn *new_dn; + + new_dn = ldb_dn_copy(mem_ctx, ldb_get_config_basedn(sam_ctx)); + if ( ! ldb_dn_add_child_fmt(new_dn, "CN=Extended-Rights")) { + talloc_free(new_dn); + return NULL; + } + return new_dn; +} /* work out the domain sid for the current open ldb */ diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 802969d50d5f..723c5ae07cab 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -701,7 +701,12 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_VALIDATE_SPN, SEC_ADS_SELF_WRITE, sid); @@ -914,7 +919,7 @@ static int acl_add(struct ldb_module *module, struct ldb_request *req) return ldb_next_request(module, req); } -/* ckecks if modifications are allowed on "Member" attribute */ +/* checks if modifications are allowed on "Member" attribute */ static int acl_check_self_membership(TALLOC_CTX *mem_ctx, struct ldb_module *module, struct ldb_request *req, @@ -928,6 +933,16 @@ static int acl_check_self_membership(TALLOC_CTX *mem_ctx, struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_dn *user_dn; struct ldb_message_element *member_el; + const struct ldb_message *msg = NULL; + + if (req->operation == LDB_MODIFY) { + msg = req->op.mod.message; + } else if (req->operation == LDB_ADD) { + msg = req->op.add.message; + } else { + return LDB_ERR_OPERATIONS_ERROR; + } + /* if we have wp, we can do whatever we like */ if (acl_check_access_on_attribute(module, mem_ctx, @@ -938,13 +953,13 @@ static int acl_check_self_membership(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } /* if we are adding/deleting ourselves, check for self membership */ - ret = dsdb_find_dn_by_sid(ldb, mem_ctx, - &acl_user_token(module)->sids[PRIMARY_USER_SID_INDEX], + ret = dsdb_find_dn_by_sid(ldb, mem_ctx, + &acl_user_token(module)->sids[PRIMARY_USER_SID_INDEX], &user_dn); if (ret != LDB_SUCCESS) { return ret; } - member_el = ldb_msg_find_element(req->op.mod.message, "member"); + member_el = ldb_msg_find_element(msg, "member"); if (!member_el) { return ldb_operr(ldb); } @@ -958,13 +973,18 @@ static int acl_check_self_membership(TALLOC_CTX *mem_ctx, return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } } - ret = acl_check_extended_right(mem_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(mem_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_SELF_MEMBERSHIP, SEC_ADS_SELF_WRITE, sid); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { dsdb_acl_debug(sd, acl_user_token(module), - req->op.mod.message->dn, + msg->dn, true, 10); } @@ -1024,6 +1044,9 @@ static int acl_check_password_rights( * so we don't have to strict verification of the input. */ ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, sd, acl_user_token(module), GUID_DRS_USER_CHANGE_PASSWORD, @@ -1047,7 +1070,12 @@ static int acl_check_password_rights( * the only caller is samdb_set_password_internal(), * so we don't have to strict verification of the input. */ - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_FORCE_CHANGE_PASSWORD, SEC_ADS_CONTROL_ACCESS, sid); @@ -1100,7 +1128,12 @@ static int acl_check_password_rights( if (rep_attr_cnt > 0) { pav->pwd_reset = true; - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_FORCE_CHANGE_PASSWORD, SEC_ADS_CONTROL_ACCESS, sid); @@ -1110,7 +1143,12 @@ static int acl_check_password_rights( if (add_attr_cnt != del_attr_cnt) { pav->pwd_reset = true; - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_FORCE_CHANGE_PASSWORD, SEC_ADS_CONTROL_ACCESS, sid); @@ -1120,7 +1158,12 @@ static int acl_check_password_rights( if (add_val_cnt == 1 && del_val_cnt == 1) { pav->pwd_reset = false; - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_USER_CHANGE_PASSWORD, SEC_ADS_CONTROL_ACCESS, sid); @@ -1134,7 +1177,12 @@ static int acl_check_password_rights( if (add_val_cnt == 1 && del_val_cnt == 0) { pav->pwd_reset = true; - ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_FORCE_CHANGE_PASSWORD, SEC_ADS_CONTROL_ACCESS, sid); @@ -1689,6 +1737,9 @@ static int acl_check_reanimate_tombstone(TALLOC_CTX *mem_ctx, struct ldb_result *acl_res; struct security_descriptor *sd = NULL; struct dom_sid *sid = NULL; + const struct dsdb_schema *schema = NULL; + const struct dsdb_class *objectclass = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(module); static const char *acl_attrs[] = { "nTSecurityDescriptor", "objectClass", @@ -1709,10 +1760,20 @@ static int acl_check_reanimate_tombstone(TALLOC_CTX *mem_ctx, ret = dsdb_get_sd_from_ldb_message(mem_ctx, req, acl_res->msgs[0], &sd); sid = samdb_result_dom_sid(mem_ctx, acl_res->msgs[0], "objectSid"); + schema = dsdb_get_schema(ldb, req); + if (!schema) { + return LDB_ERR_OPERATIONS_ERROR; + } + objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]); if (ret != LDB_SUCCESS || !sd) { return ldb_operr(ldb_module_get_ctx(module)); } - return acl_check_extended_right(mem_ctx, sd, acl_user_token(module), + return acl_check_extended_right(mem_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), GUID_DRS_REANIMATE_TOMBSTONE, SEC_ADS_CONTROL_ACCESS, sid); } diff --git a/source4/dsdb/samdb/ldb_modules/acl_util.c b/source4/dsdb/samdb/ldb_modules/acl_util.c index f917d99517a4..08a95c1c310e 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_util.c +++ b/source4/dsdb/samdb/ldb_modules/acl_util.c @@ -197,6 +197,9 @@ fail: /* checks for validated writes */ int acl_check_extended_right(TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + const struct dsdb_class *objectclass, struct security_descriptor *sd, struct security_token *token, const char *ext_right, @@ -208,6 +211,43 @@ int acl_check_extended_right(TALLOC_CTX *mem_ctx, uint32_t access_granted; struct object_tree *root = NULL; TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + static const char *no_attrs[] = { NULL }; + struct ldb_result *extended_rights_res = NULL; + struct ldb_dn *extended_rights_dn = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(module); + int ret = 0; + + /* + * Find the extended right and check if applies to + * the objectclass of the object + */ + extended_rights_dn = samdb_extended_rights_dn(ldb, req); + if (!extended_rights_dn) { + ldb_set_errstring(ldb, + "access_check: CN=Extended-Rights dn could not be generated!"); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Note: we are checking only the structural object class. */ + ret = dsdb_module_search(module, req, &extended_rights_res, + extended_rights_dn, LDB_SCOPE_ONELEVEL, + no_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM, + req, + "(&(rightsGuid=%s)(appliesTo=%s))", + ext_right, + GUID_string(tmp_ctx, + &(objectclass->schemaIDGUID))); + + if (ret != LDB_SUCCESS) { + return ret; + } else if (extended_rights_res->count == 0 ) { + ldb_debug(ldb, LDB_DEBUG_TRACE, + "acl_check_extended_right: Could not find appliesTo for %s\n", + ext_right); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } GUID_from_string(ext_right, &right); diff --git a/source4/dsdb/samdb/ldb_modules/dirsync.c b/source4/dsdb/samdb/ldb_modules/dirsync.c index bafa7dd1b874..e61ade827b86 100644 --- a/source4/dsdb/samdb/ldb_modules/dirsync.c +++ b/source4/dsdb/samdb/ldb_modules/dirsync.c @@ -1066,7 +1066,9 @@ static int dirsync_ldb_search(struct ldb_module *module, struct ldb_request *req if (!(dirsync_ctl->flags & LDAP_DIRSYNC_OBJECT_SECURITY)) { struct dom_sid *sid; struct security_descriptor *sd = NULL; - const char *acl_attrs[] = { "nTSecurityDescriptor", "objectSid", NULL }; + const char *acl_attrs[] = { "nTSecurityDescriptor", "objectSid", "objectClass", NULL }; + const struct dsdb_schema *schema = NULL; + const struct dsdb_class *objectclass = NULL; /* * If we don't have the flag and if we have the "replicate directory change" granted * then we upgrade ourself to system to not be blocked by the acl @@ -1096,7 +1098,14 @@ static int dirsync_ldb_search(struct ldb_module *module, struct ldb_request *req if (ret != LDB_SUCCESS) { return ret; } - ret = acl_check_extended_right(dsc, sd, acl_user_token(module), GUID_DRS_GET_CHANGES, SEC_ADS_CONTROL_ACCESS, sid); + schema = dsdb_get_schema(ldb, req); + if (!schema) { + return LDB_ERR_OPERATIONS_ERROR; + } + objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]); + ret = acl_check_extended_right(dsc, module, req, objectclass, + sd, acl_user_token(module), + GUID_DRS_GET_CHANGES, SEC_ADS_CONTROL_ACCESS, sid); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { return ret; diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index ad12ee6dba3b..325d97ff3971 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2196,12 +2196,15 @@ static int samldb_check_user_account_control_objectclass_invariants( return LDB_SUCCESS; } -static int samldb_get_domain_secdesc(struct samldb_ctx *ac, - struct security_descriptor **domain_sd) +static int samldb_get_domain_secdesc_and_oc(struct samldb_ctx *ac, + struct security_descriptor **domain_sd, + const struct dsdb_class **objectclass) { - const char * const sd_attrs[] = {"ntSecurityDescriptor", NULL}; + const char * const sd_attrs[] = {"ntSecurityDescriptor", "objectClass", NULL}; struct ldb_result *res; struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module)); + const struct dsdb_schema *schema = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); int ret = dsdb_module_search_dn(ac->module, ac, &res, domain_dn, sd_attrs, @@ -2214,6 +2217,11 @@ static int samldb_get_domain_secdesc(struct samldb_ctx *ac, return ldb_module_operr(ac->module); } + schema = dsdb_get_schema(ldb, ac->req); + if (!schema) { + return ldb_module_operr(ac->module);; + } + *objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]); return dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(ac->module), ac, res->msgs[0], domain_sd); @@ -2233,6 +2241,7 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, bool need_acl_check = false; struct security_token *user_token; struct security_descriptor *domain_sd; + const struct dsdb_class *objectclass = NULL; const struct uac_to_guid { uint32_t uac; uint32_t priv_to_change_from; @@ -2318,7 +2327,7 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } - ret = samldb_get_domain_secdesc(ac, &domain_sd); + ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass); if (ret != LDB_SUCCESS) { return ret; } @@ -2349,7 +2358,11 @@ static int samldb_check_user_account_control_acl(struct samldb_ctx *ac, ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } } else if (map[i].guid) { - ret = acl_check_extended_right(ac, domain_sd, + ret = acl_check_extended_right(ac, + ac->module, + ac->req, + objectclass, + domain_sd, user_token, map[i].guid, SEC_ADS_CONTROL_ACCESS, @@ -2689,12 +2702,11 @@ static int samldb_check_pwd_last_set_acl(struct samldb_ctx *ac, { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); int ret = 0; - struct ldb_result *res = NULL; - const char * const sd_attrs[] = {"ntSecurityDescriptor", NULL}; struct security_token *user_token = NULL; struct security_descriptor *domain_sd = NULL; struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module)); const char *operation = ""; + const struct dsdb_class *objectclass = NULL; if (dsdb_module_am_system(ac->module)) { return LDB_SUCCESS; @@ -2716,24 +2728,15 @@ static int samldb_check_pwd_last_set_acl(struct samldb_ctx *ac, return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } - ret = dsdb_module_search_dn(ac->module, ac, &res, - domain_dn, - sd_attrs, - DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, - ac->req); - if (ret != LDB_SUCCESS) { - return ret; - } - if (res->count != 1) { - return ldb_module_operr(ac->module); - } - - ret = dsdb_get_sd_from_ldb_message(ldb, ac, res->msgs[0], &domain_sd); + ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass); if (ret != LDB_SUCCESS) { return ret; } - - ret = acl_check_extended_right(ac, domain_sd, + ret = acl_check_extended_right(ac, + ac->module, + ac->req, + objectclass, + domain_sd, user_token, GUID_DRS_UNEXPIRE_PASSWORD, SEC_ADS_CONTROL_ACCESS, @@ -3763,16 +3766,21 @@ static int samldb_check_sensitive_attributes(struct samldb_ctx *ac) el = ldb_msg_find_element(ac->msg, "msDS-SecondaryKrbTgtNumber"); if (el) { struct security_descriptor *domain_sd; + const struct dsdb_class *objectclass = NULL; /* * msDS-SecondaryKrbTgtNumber allows the creator to * become an RODC, this is trusted as an RODC * account */ - ret = samldb_get_domain_secdesc(ac, &domain_sd); + ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass); if (ret != LDB_SUCCESS) { return ret; } - ret = acl_check_extended_right(ac, domain_sd, + ret = acl_check_extended_right(ac, + ac->module, + ac->req, + objectclass, + domain_sd, user_token, GUID_DRS_DS_INSTALL_REPLICA, SEC_ADS_CONTROL_ACCESS, -- 2.25.1 From fc6fdd45239a8e1799f0fba8da67d5808406e0fe Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 1 Nov 2021 17:19:29 +1300 Subject: [PATCH 117/224] CVE-2020-25722 Check all elements in acl_check_spn() not just the first one Thankfully we are aleady in a loop over all the message elements in acl_modify() so this is an easy and safe change to make. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- selftest/knownfail.d/acl-spn | 1 - source4/dsdb/samdb/ldb_modules/acl.c | 31 +++++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) delete mode 100644 selftest/knownfail.d/acl-spn diff --git a/selftest/knownfail.d/acl-spn b/selftest/knownfail.d/acl-spn deleted file mode 100644 index e68add920a61..000000000000 --- a/selftest/knownfail.d/acl-spn +++ /dev/null @@ -1 +0,0 @@ -^samba4.ldap.acl.python.*AclSPNTests.test_delete_add_spn diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 723c5ae07cab..1e4764cdbd7c 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -656,9 +656,14 @@ success: return LDB_SUCCESS; } +/* + * Passing in 'el' is critical, we want to check all the values. + * + */ static int acl_check_spn(TALLOC_CTX *mem_ctx, struct ldb_module *module, struct ldb_request *req, + const struct ldb_message_element *el, struct security_descriptor *sd, struct dom_sid *sid, const struct dsdb_attribute *attr, @@ -670,7 +675,6 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_result *acl_res; struct ldb_result *netbios_res; - struct ldb_message_element *el; struct ldb_dn *partitions_dn = samdb_partitions_dn(ldb, tmp_ctx); uint32_t userAccountControl; const char *samAccountName; @@ -720,6 +724,23 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, return ret; } + /* + * If we have "validated write spn", allow delete of any + * existing value (this keeps constrained delete to the same + * rules as unconstrained) + */ + if (req->operation == LDB_MODIFY) { + /* + * If not add or replace (eg delete), + * return success + */ + if ((el->flags + & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE)) == 0) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + } + ret = dsdb_module_search_dn(module, tmp_ctx, &acl_res, req->op.mod.message->dn, acl_attrs, @@ -748,13 +769,6 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, netbios_name = ldb_msg_find_attr_as_string(netbios_res->msgs[0], "nETBIOSName", NULL); - el = ldb_msg_find_element(req->op.mod.message, "servicePrincipalName"); - if (!el) { - talloc_free(tmp_ctx); - return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, - "Error finding element for servicePrincipalName."); - } - /* NTDSDSA objectGuid of object we are checking SPN for */ if (userAccountControl & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { ret = dsdb_module_find_ntdsguid_for_computer(module, tmp_ctx, @@ -1513,6 +1527,7 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) ret = acl_check_spn(tmp_ctx, module, req, + el, sd, sid, attr, -- 2.25.1 From 0eaa1b0b1a05d0280e43f3a35c1a8821a89fd22c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 1 Nov 2021 17:21:16 +1300 Subject: [PATCH 118/224] CVE-2020-25722 Check for all errors from acl_check_extended_right() in acl_check_spn() We should not fail open on error. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/dsdb/samdb/ldb_modules/acl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 1e4764cdbd7c..21e83276bfd0 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -715,7 +715,7 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, SEC_ADS_SELF_WRITE, sid); - if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + if (ret != LDB_SUCCESS) { dsdb_acl_debug(sd, acl_user_token(module), req->op.mod.message->dn, true, -- 2.25.1 From 9d4c78b9a9f8bb1354123d987a990d938cad0308 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Mon, 4 Oct 2021 12:56:42 +1300 Subject: [PATCH 119/224] CVE-2020-25722 pytests: add reverse lookup dict for LDB error codes You can give ldb_err() it a number, an LdbError, or a sequence of numbers, and it will return the corresponding strings. Examples: ldb_err(68) # "LDB_ERR_ENTRY_ALREADY_EXISTS" LDB_ERR_LUT[68] # "LDB_ERR_ENTRY_ALREADY_EXISTS" expected = (ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, ldb.ERR_INVALID_CREDENTIALS) try: foo() except ldb.LdbError as e: self.fail(f"got {ldb_err(e)}, expected one of {ldb_err(expected)}") BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/tests/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index 60c6329c4eb3..a6d15f54548c 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -62,6 +62,22 @@ BINDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), HEXDUMP_FILTER = bytearray([x if ((len(repr(chr(x))) == 3) and (x < 127)) else ord('.') for x in range(256)]) +LDB_ERR_LUT = {v: k for k,v in vars(ldb).items() if k.startswith('ERR_')} + +def ldb_err(v): + if isinstance(v, ldb.LdbError): + v = v.args[0] + + if v in LDB_ERR_LUT: + return LDB_ERR_LUT[v] + + try: + return f"[{', '.join(LDB_ERR_LUT.get(x, x) for x in v)}]" + except TypeError as e: + print(e) + return v + + def DynamicTestCase(cls): cls.setUpDynamicTestCases() return cls -- 2.25.1 From ef0d7b445866a5c6e3b55a3bdb1c5021cb673046 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Sun, 24 Oct 2021 15:18:05 +1300 Subject: [PATCH 120/224] CVE-2020-25722 pytest: assertRaisesLdbError invents a message if you're lazy This makes it easier to convert tests that don't have good messages. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/tests/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index a6d15f54548c..2c4c8fad83e4 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -209,6 +209,8 @@ class TestCase(unittest.TestCase): def assertRaisesLdbError(self, errcode, message, f, *args, **kwargs): """Assert a function raises a particular LdbError.""" + if message is None: + message = f"{f.__name__}(*{args}, **{kwargs})" try: f(*args, **kwargs) except ldb.LdbError as e: -- 2.25.1 From 5c3455b586b228c8b36e72b83944fce30e6b4cae Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 11 Aug 2021 16:56:07 +1200 Subject: [PATCH 121/224] CVE-2020-25722 s4/dsdb/cracknames: always free tmp_ctx in spn_alias BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/cracknames.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source4/dsdb/samdb/cracknames.c b/source4/dsdb/samdb/cracknames.c index 544a5f6a88c8..72d5e45f44d1 100644 --- a/source4/dsdb/samdb/cracknames.c +++ b/source4/dsdb/samdb/cracknames.c @@ -101,10 +101,12 @@ static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(krb5_context context, stru service_dn = ldb_dn_new(tmp_ctx, ldb_ctx, "CN=Directory Service,CN=Windows NT,CN=Services"); if ( ! ldb_dn_add_base(service_dn, ldb_get_config_basedn(ldb_ctx))) { + talloc_free(tmp_ctx); return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; } service_dn_str = ldb_dn_alloc_linearized(tmp_ctx, service_dn); if ( ! service_dn_str) { + talloc_free(tmp_ctx); return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; } @@ -113,13 +115,15 @@ static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(krb5_context context, stru if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) { DEBUG(1, ("ldb_search: dn: %s not found: %s\n", service_dn_str, ldb_errstring(ldb_ctx))); + talloc_free(tmp_ctx); return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; } else if (ret == LDB_ERR_NO_SUCH_OBJECT) { DEBUG(1, ("ldb_search: dn: %s not found\n", service_dn_str)); + talloc_free(tmp_ctx); return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; } else if (res->count != 1) { - talloc_free(res); DEBUG(1, ("ldb_search: dn: %s not found\n", service_dn_str)); + talloc_free(tmp_ctx); return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; } -- 2.25.1 From 72b32f55e826637b955a4b407e14a5199c932aa6 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Tue, 10 Aug 2021 23:02:36 +0000 Subject: [PATCH 122/224] CVE-2020-25722 s4/cracknames: lookup_spn_alias doesn't need krb5 context BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/cracknames.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source4/dsdb/samdb/cracknames.c b/source4/dsdb/samdb/cracknames.c index 72d5e45f44d1..3454cf7dc35a 100644 --- a/source4/dsdb/samdb/cracknames.c +++ b/source4/dsdb/samdb/cracknames.c @@ -74,9 +74,9 @@ static WERROR dns_domain_from_principal(TALLOC_CTX *mem_ctx, struct smb_krb5_con info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY; return WERR_OK; -} +} -static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(krb5_context context, struct ldb_context *ldb_ctx, +static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(struct ldb_context *ldb_ctx, TALLOC_CTX *mem_ctx, const char *alias_from, char **alias_to) @@ -221,8 +221,7 @@ static WERROR DsCrackNameSPNAlias(struct ldb_context *sam_ctx, TALLOC_CTX *mem_c dns_name = (const char *)component->data; /* MAP it */ - namestatus = LDB_lookup_spn_alias(smb_krb5_context->krb5_context, - sam_ctx, mem_ctx, + namestatus = LDB_lookup_spn_alias(sam_ctx, mem_ctx, service, &new_service); if (namestatus == DRSUAPI_DS_NAME_STATUS_NOT_FOUND) { -- 2.25.1 From 1dacc58eb3d84b98f195645c92c378fb4e34e157 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 28 Jul 2021 05:38:50 +0000 Subject: [PATCH 123/224] CVE-2020-25722 samba-tool spn: accept -H for database url Following the convention and making testing easier BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/netcmd/spn.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/python/samba/netcmd/spn.py b/python/samba/netcmd/spn.py index f0069460e3ef..46e9c59272af 100644 --- a/python/samba/netcmd/spn.py +++ b/python/samba/netcmd/spn.py @@ -18,7 +18,6 @@ import samba.getopt as options import ldb -from samba import provision from samba.samdb import SamDB from samba.auth import system_session from samba.netcmd.common import _get_user_realm_domain @@ -40,14 +39,20 @@ class cmd_spn_list(Command): "credopts": options.CredentialsOptions, "versionopts": options.VersionOptions, } + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] takes_args = ["user"] - def run(self, user, credopts=None, sambaopts=None, versionopts=None): + def run(self, user, H=None, + credopts=None, + sambaopts=None, + versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) - paths = provision.provision_paths_from_lp(lp, lp.get("realm")) - sam = SamDB(paths.samdb, session_info=system_session(), + sam = SamDB(H, session_info=system_session(), credentials=creds, lp=lp) # TODO once I understand how, use the domain info to naildown # to the correct domain @@ -82,17 +87,20 @@ class cmd_spn_add(Command): "versionopts": options.VersionOptions, } takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), Option("--force", help="Force the addition of the spn" " even it exists already", action="store_true"), - ] + ] takes_args = ["name", "user"] - def run(self, name, user, force=False, credopts=None, sambaopts=None, + def run(self, name, user, H=None, force=False, + credopts=None, + sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) - paths = provision.provision_paths_from_lp(lp, lp.get("realm")) - sam = SamDB(paths.samdb, session_info=system_session(), + sam = SamDB(H, session_info=system_session(), credentials=creds, lp=lp) res = sam.search( expression="servicePrincipalName=%s" % ldb.binary_encode(name), @@ -141,15 +149,18 @@ class cmd_spn_delete(Command): "credopts": options.CredentialsOptions, "versionopts": options.VersionOptions, } + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] takes_args = ["name", "user?"] - def run(self, name, user=None, credopts=None, sambaopts=None, + def run(self, name, user=None, H=None, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) - paths = provision.provision_paths_from_lp(lp, lp.get("realm")) - sam = SamDB(paths.samdb, session_info=system_session(), + sam = SamDB(H, session_info=system_session(), credentials=creds, lp=lp) res = sam.search( expression="servicePrincipalName=%s" % ldb.binary_encode(name), -- 2.25.1 From 7c07e43111e9b659ae4c9d683d3b065167a24987 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 27 Aug 2021 11:36:42 +1200 Subject: [PATCH 124/224] CVE-2020-25722 samba-tool spn add: remove --force option This did not actually *force* the creation of a duplicate SPN, it just ignored the client-side check for the existing copy. Soon we are going to enforce SPN uniqueness on the server side, and this --force will not work. This will make the --force test fail, and if that tests fail, so will others that depend the duplicate values. So we remove those tests. It is wrong-headed to try to make duplicate SPNs in any case, which is probably why there is no sign of anyone ever having used this option. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/netcmd/spn.py | 6 ++---- source4/setup/tests/blackbox_spn.sh | 5 +---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/python/samba/netcmd/spn.py b/python/samba/netcmd/spn.py index 46e9c59272af..2676ff34fac3 100644 --- a/python/samba/netcmd/spn.py +++ b/python/samba/netcmd/spn.py @@ -89,12 +89,10 @@ class cmd_spn_add(Command): takes_options = [ Option("-H", "--URL", help="LDB URL for database or target server", type=str, metavar="URL", dest="H"), - Option("--force", help="Force the addition of the spn" - " even it exists already", action="store_true"), ] takes_args = ["name", "user"] - def run(self, name, user, H=None, force=False, + def run(self, name, user, H=None, credopts=None, sambaopts=None, versionopts=None): @@ -105,7 +103,7 @@ class cmd_spn_add(Command): res = sam.search( expression="servicePrincipalName=%s" % ldb.binary_encode(name), scope=ldb.SCOPE_SUBTREE) - if len(res) != 0 and not force: + if len(res) != 0: raise CommandError("Service principal %s already" " affected to another user" % name) diff --git a/source4/setup/tests/blackbox_spn.sh b/source4/setup/tests/blackbox_spn.sh index 429ace9494f4..764ded4c88b7 100755 --- a/source4/setup/tests/blackbox_spn.sh +++ b/source4/setup/tests/blackbox_spn.sh @@ -22,11 +22,8 @@ testit "addspn" $PYTHON $samba_tool spn add FOO/bar Administrator $CONFIG testit "delspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG testit "readdspn" $PYTHON $samba_tool spn add FOO/bar Administrator $CONFIG testit_expect_failure "failexistingspn" $PYTHON $samba_tool spn add FOO/bar Guest $CONFIG -testit "existingspnforce" $PYTHON $samba_tool spn add --force FOO/bar Guest $CONFIG testit_expect_failure "faildelspnnotgooduser" $PYTHON $samba_tool spn delete FOO/bar krbtgt $CONFIG -testit_expect_failure "faildelspnmoreoneuser" $PYTHON $samba_tool spn delete FOO/bar $CONFIG -testit "deluserspn" $PYTHON $samba_tool spn delete FOO/bar Guest $CONFIG -testit "dellastuserspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG +testit "deluserspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG testit_expect_failure "faildelspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG testit_expect_failure "failaddspn" $PYTHON $samba_tool spn add FOO/bar nonexistinguser $CONFIG -- 2.25.1 From 6687350713682779a7ac8b7df09d9e40d25a1591 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 1 Sep 2021 18:35:02 +1200 Subject: [PATCH 125/224] CVE-2020-25722 tests: blackbox samba-tool spn non-admin test It is soon going to be impossible to add duplicate SPNs (short of going behind DSDB's back on the local filesystem). Our test of adding SPNs on non-admin users doubled as the test for adding a duplicate (using --force). As --force is gone, we add these tests on Guest after the SPN on Administrator is gone. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/setup/tests/blackbox_spn.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source4/setup/tests/blackbox_spn.sh b/source4/setup/tests/blackbox_spn.sh index 764ded4c88b7..8f0258d0db87 100755 --- a/source4/setup/tests/blackbox_spn.sh +++ b/source4/setup/tests/blackbox_spn.sh @@ -24,6 +24,8 @@ testit "readdspn" $PYTHON $samba_tool spn add FOO/bar Administrator $CONFIG testit_expect_failure "failexistingspn" $PYTHON $samba_tool spn add FOO/bar Guest $CONFIG testit_expect_failure "faildelspnnotgooduser" $PYTHON $samba_tool spn delete FOO/bar krbtgt $CONFIG testit "deluserspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG +testit "readd_spn_guest" $PYTHON $samba_tool spn add FOO/bar Guest $CONFIG +testit "deluserspn_guest" $PYTHON $samba_tool spn delete FOO/bar Guest $CONFIG testit_expect_failure "faildelspn" $PYTHON $samba_tool spn delete FOO/bar $CONFIG testit_expect_failure "failaddspn" $PYTHON $samba_tool spn add FOO/bar nonexistinguser $CONFIG -- 2.25.1 From b9fffc70da05cef3cd2ce2951b722311508b6979 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 28 Oct 2021 09:45:36 +1300 Subject: [PATCH 126/224] CVE-2020-25722 s4/provision: add host/ SPNs at the start There are two reasons for this. Firstly, leaving SPNs unclaimed is dangerous, as someone else could grab them first. Secondly, in some circumstances (self join) we try to add a DNS/ SPN a little bit later in provision. Under the rules we are introducing for CVE-2020-25722, this will make our later attempts to add HOST/ fail. This causes a few errors in samba4.blackbox.dbcheck.* tests, which assert that revivified old domains match stored reference versions. Now they don't, because they have servicePrincipalNames. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/cve-2020-25722-provision | 4 ++++ source4/setup/provision_self_join.ldif | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 selftest/knownfail.d/cve-2020-25722-provision diff --git a/selftest/knownfail.d/cve-2020-25722-provision b/selftest/knownfail.d/cve-2020-25722-provision new file mode 100644 index 000000000000..7fd4b4b3763a --- /dev/null +++ b/selftest/knownfail.d/cve-2020-25722-provision @@ -0,0 +1,4 @@ +samba4.blackbox.dbcheck.release-4-0-0 +samba4.blackbox.dbcheck.release-4-0-0.quick +samba4.blackbox.upgradeprovision.release-4-0-0 +samba4.blackbox.functionalprep.check_databases_same diff --git a/source4/setup/provision_self_join.ldif b/source4/setup/provision_self_join.ldif index f77ac5710ec3..92bf4d9cf8fc 100644 --- a/source4/setup/provision_self_join.ldif +++ b/source4/setup/provision_self_join.ldif @@ -15,11 +15,16 @@ localPolicyFlags: 0 operatingSystem: Samba operatingSystemVersion: ${SAMBA_VERSION_STRING} sAMAccountName: ${NETBIOSNAME}$ -# The "servicePrincipalName" updates are now handled by the "samba_spnupdate" -# script userAccountControl: 532480 clearTextPassword:: ${MACHINEPASS_B64} objectSid: ${DOMAINSID}-${DCRID} +# While some "servicePrincipalName" updates might be handled by the +# "samba_spnupdate" script, we need to get the basics in here before +# we add any others. +servicePrincipalName: HOST/${DNSNAME} +servicePrincipalName: HOST/${NETBIOSNAME} +servicePrincipalName: HOST/${DNSNAME}/${DNSNAME} + dn: CN=RID Set,CN=${NETBIOSNAME},OU=Domain Controllers,${DOMAINDN} objectClass: rIDSet -- 2.25.1 From 6732d3f4b9f867c565bf9a7c55fa41b7a97fe629 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 28 Oct 2021 13:07:01 +1300 Subject: [PATCH 127/224] CVE-2020-25722 blackbox/upgrades tests: ignore SPN for ldapcmp We need to have the SPNs there before someone else nabs them, which makes the re-provisioned old releases different from the reference versions that we keep for this comparison. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/cve-2020-25722-provision | 4 ---- source4/setup/tests/blackbox_upgradeprovision.sh | 8 ++++---- testprogs/blackbox/dbcheck-oldrelease.sh | 4 ++-- testprogs/blackbox/functionalprep.sh | 2 +- testprogs/blackbox/upgradeprovision-oldrelease.sh | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) delete mode 100644 selftest/knownfail.d/cve-2020-25722-provision diff --git a/selftest/knownfail.d/cve-2020-25722-provision b/selftest/knownfail.d/cve-2020-25722-provision deleted file mode 100644 index 7fd4b4b3763a..000000000000 --- a/selftest/knownfail.d/cve-2020-25722-provision +++ /dev/null @@ -1,4 +0,0 @@ -samba4.blackbox.dbcheck.release-4-0-0 -samba4.blackbox.dbcheck.release-4-0-0.quick -samba4.blackbox.upgradeprovision.release-4-0-0 -samba4.blackbox.functionalprep.check_databases_same diff --git a/source4/setup/tests/blackbox_upgradeprovision.sh b/source4/setup/tests/blackbox_upgradeprovision.sh index 5222b5ca924c..3fe33f9eaffd 100755 --- a/source4/setup/tests/blackbox_upgradeprovision.sh +++ b/source4/setup/tests/blackbox_upgradeprovision.sh @@ -42,19 +42,19 @@ upgradeprovision_full() { # really doesn't change anything. ldapcmp() { - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --skip-missing-dn --filter=servicePrincipalName } ldapcmp_full() { - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision_full/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision_full/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --skip-missing-dn --filter=servicePrincipalName } ldapcmp_sd() { - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --sd --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --sd --skip-missing-dn --filter=servicePrincipalName } ldapcmp_full_sd() { - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision_full/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --sd --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX/upgradeprovision_full/private/sam.ldb tdb://$PREFIX/upgradeprovision_reference/private/sam.ldb --two --sd --skip-missing-dn --filter=servicePrincipalName } testit "upgradeprovision" upgradeprovision diff --git a/testprogs/blackbox/dbcheck-oldrelease.sh b/testprogs/blackbox/dbcheck-oldrelease.sh index 64c08c579812..e9913640be78 100755 --- a/testprogs/blackbox/dbcheck-oldrelease.sh +++ b/testprogs/blackbox/dbcheck-oldrelease.sh @@ -483,13 +483,13 @@ referenceprovision() { ldapcmp() { if [ x$RELEASE = x"release-4-0-0" ]; then - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --two --skip-missing-dn --filter=dnsRecord,displayName,msDS-SupportedEncryptionTypes + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --two --skip-missing-dn --filter=dnsRecord,displayName,msDS-SupportedEncryptionTypes,servicePrincipalName fi } ldapcmp_sd() { if [ x$RELEASE = x"release-4-0-0" ]; then - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --two --sd --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --two --sd --skip-missing-dn --filter=servicePrincipalName fi } diff --git a/testprogs/blackbox/functionalprep.sh b/testprogs/blackbox/functionalprep.sh index a5ac4b8bc7f5..e9ab0854cff7 100755 --- a/testprogs/blackbox/functionalprep.sh +++ b/testprogs/blackbox/functionalprep.sh @@ -72,7 +72,7 @@ provision_2012r2() { ldapcmp_ignore() { # At some point we will need to ignore, but right now, it should be perfect IGNORE_ATTRS=$1 - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/$2/private/sam.ldb tdb://$PREFIX_ABS/$3/private/sam.ldb --two --skip-missing-dn --filter msDS-SupportedEncryptionTypes + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/$2/private/sam.ldb tdb://$PREFIX_ABS/$3/private/sam.ldb --two --skip-missing-dn --filter msDS-SupportedEncryptionTypes,servicePrincipalName } ldapcmp() { diff --git a/testprogs/blackbox/upgradeprovision-oldrelease.sh b/testprogs/blackbox/upgradeprovision-oldrelease.sh index 5b095fca05e6..d40830f3e017 100755 --- a/testprogs/blackbox/upgradeprovision-oldrelease.sh +++ b/testprogs/blackbox/upgradeprovision-oldrelease.sh @@ -182,12 +182,12 @@ referenceprovision() { ldapcmp() { if [ x$RELEASE != x"alpha13" ]; then - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_upgrade_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}_upgrade/private/sam.ldb --two --skip-missing-dn --filter=dnsRecord,displayName,msDS-SupportedEncryptionTypes + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_upgrade_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}_upgrade/private/sam.ldb --two --skip-missing-dn --filter=dnsRecord,displayName,msDS-SupportedEncryptionTypes,servicePrincipalName fi } ldapcmp_full() { - $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_upgrade_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}_upgrade_full/private/sam.ldb --two --filter=dNSProperty,dnsRecord,cn,displayName,versionNumber,systemFlags,msDS-HasInstantiatedNCs --skip-missing-dn + $PYTHON $BINDIR/samba-tool ldapcmp tdb://$PREFIX_ABS/${RELEASE}_upgrade_reference/private/sam.ldb tdb://$PREFIX_ABS/${RELEASE}_upgrade_full/private/sam.ldb --two --filter=dNSProperty,dnsRecord,cn,displayName,versionNumber,systemFlags,msDS-HasInstantiatedNCs,servicePrincipalName --skip-missing-dn } ldapcmp_sd() { -- 2.25.1 From c57eedd1265247246b65f5e67c0af79b7d93e5ec Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Mon, 13 Sep 2021 14:15:09 +1200 Subject: [PATCH 128/224] CVE-2020-25722 pytest: test sAMAccountName/userPrincipalName over ldap Because the sam account name + the dns host name is used as the default user principal name, we need to check for collisions between these. Fixes are coming in upcoming patches. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/tests/ldap_upn_sam_account.py | 510 +++++++++++++++++++++ selftest/knownfail.d/ldap_upn_sam_account | 16 + source4/selftest/tests.py | 9 + 3 files changed, 535 insertions(+) create mode 100644 python/samba/tests/ldap_upn_sam_account.py create mode 100644 selftest/knownfail.d/ldap_upn_sam_account diff --git a/python/samba/tests/ldap_upn_sam_account.py b/python/samba/tests/ldap_upn_sam_account.py new file mode 100644 index 000000000000..cc1cce9b6c3f --- /dev/null +++ b/python/samba/tests/ldap_upn_sam_account.py @@ -0,0 +1,510 @@ +# Unix SMB/CIFS implementation. +# +# Copyright 2021 (C) Catalyst IT Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import os +import sys +from samba.samdb import SamDB +from samba.auth import system_session +import ldb +from samba.tests.subunitrun import SubunitOptions, TestProgram +from samba.tests import TestCase, ldb_err +from samba.tests import DynamicTestCase +import samba.getopt as options +import optparse +from samba.colour import c_RED, c_GREEN, c_DARK_YELLOW +import re +import pprint +from samba.dsdb import ( + UF_SERVER_TRUST_ACCOUNT, + UF_TRUSTED_FOR_DELEGATION, +) + + +# bad sAMAccountName characters from [MS-SAMR] +# "3.1.1.6 Attribute Constraints for Originating Updates" +BAD_SAM_CHARS = (''.join(chr(x) for x in range(0, 32)) + + '"/\\[]:|<>+=;?,*') + +# 0x7f is *said* to be bad, but turns out to be fine. +ALLEGED_BAD_SAM_CHARS = chr(127) + +LATIN1_BAD_CHARS = set([chr(x) for x in range(129, 160)] + + list("ªºÿ") + + [chr(x) for x in range(0xc0, 0xc6)] + + [chr(x) for x in range(0xc7, 0xd7)] + + [chr(x) for x in range(0xd8, 0xde)] + + [chr(x) for x in range(0xe0, 0xe6)] + + [chr(x) for x in range(0xe7, 0xf7)] + + [chr(x) for x in range(0xf8, 0xfe)]) + + +LATIN_EXTENDED_A_NO_CLASH = {306, 307, 330, 331, 338, 339, 358, 359, 383} + +#XXX does '\x00' just truncate the string though? +#XXX elsewhere we see "[\\\"|,/:<>+=;?*']" with "'" + + +## UPN limits +# max length 1024 UTF-8 bytes, following "rfc822" +# for o365 sync https://docs.microsoft.com/en-us/microsoft-365/enterprise/prepare-for-directory-synchronization?view=o365-worldwide +# max length is 113 [64 before @] "@" [48 after @] +# invalid chars: '\\%&*+/=?{}|<>();:,[]"' +# allowed chars: A – Z, a - z, 0 – 9, ' . - _ ! # ^ ~ +# "Letters with diacritical marks, such as umlauts, accents, and tildes, are invalid characters." +# +# "@" can't be first +# "The username cannot end with a period (.), an ampersand (&), a space, or an at sign (@)." +# + +# per RFC 822, «"a b" @ example.org» is + + +ok = True +bad = False +report = 'report' +exists = ldb.ERR_ENTRY_ALREADY_EXISTS + + +if sys.stdout.isatty(): + c_doc = c_DARK_YELLOW +else: + c_doc = lambda x: x + + +def get_samdb(): + return SamDB(url=f"ldap://{SERVER}", + lp=LP, + session_info=system_session(), + credentials=CREDS) + + +def format(s): + if type(s) is str: + s = s.format(realm=REALM.upper(), + lrealm=REALM.lower(), + other_realm=(REALM + ".another.example.net")) + return s + + +class LdapUpnSamTestBase(TestCase): + """Make sure we can't add userPrincipalNames or sAMAccountNames that + implicitly collide. + """ + _disabled = False + + @classmethod + def setUpDynamicTestCases(cls): + if getattr(cls, '_disabled', False): + return + for doc, *rows in cls.cases: + name = re.sub(r'\W+', '_', doc) + cls.generate_dynamic_test("test_upn_sam", name, rows, doc) + + def setup_objects(self, rows): + objects = set(r[0] for r in rows) + for name in objects: + if ':' in name: + objtype, name = name.split(':', 1) + else: + objtype = 'user' + getattr(self, f'add_{objtype}')(name) + self.addCleanup(self.remove_object, name) + + def _test_upn_sam_with_args(self, rows, doc): + self.setup_objects(rows) + cdoc = c_doc(doc) + + for i, row in enumerate(rows): + if len(row) == 4: + obj, data, expected, op = row + else: + obj, data, expected = row + op = ldb.FLAG_MOD_REPLACE + + dn, dnsname = self.objects[obj] + sam, upn = None, None + if isinstance(data, dict): + sam = data.get('sam') + upn = data.get('upn') + elif isinstance(data, str): + if '@' in data: + upn = data + else: + sam = data + else: # bytes + if b'@' in data: + upn = data + else: + sam = data + + m = {"dn": dn} + + if upn is not None: + m["userPrincipalName"] = format(upn) + + if sam is not None: + m["sAMAccountName"] = format(sam) + + msg = ldb.Message.from_dict(self.samdb, m, op) + + if expected is bad: + try: + self.samdb.modify(msg) + except ldb.LdbError as e: + print(f"row {i+1} of '{cdoc}' failed as expected with " + f"{ldb_err(e)}\n") + continue + self.fail(f"row {i+1} of '{cdoc}' should have failed:\n" + f"{pprint.pformat(m)} on {obj}") + elif expected is ok: + try: + self.samdb.modify(msg) + except ldb.LdbError as e: + raise AssertionError( + f"row {i+1} of '{cdoc}' failed with {ldb_err(e)}:\n" + f"{pprint.pformat(m)} on {obj}") from None + elif expected is report: + try: + self.samdb.modify(msg) + print(f"row {i+1} of '{cdoc}' SUCCEEDED:\n" + f"{pprint.pformat(m)} on {obj}") + except ldb.LdbError as e: + print(f"row {i+1} of '{cdoc}' FAILED " + f"with {ldb_err(e)}:\n" + f"{pprint.pformat(m)} on {obj}") + + else: + try: + self.samdb.modify(msg) + except ldb.LdbError as e: + if hasattr(expected, '__contains__'): + if e.args[0] in expected: + continue + + if e.args[0] == expected: + continue + + self.fail(f"row {i+1} of '{cdoc}' " + f"should have failed with {ldb_err(expected)} " + f"but instead failed with {ldb_err(e)}:\n" + f"{pprint.pformat(m)} on {obj}") + self.fail(f"row {i+1} of '{cdoc}' " + f"should have failed with {ldb_err(expected)}:\n" + f"{pprint.pformat(m)} on {obj}") + + def add_dc(self, name): + dn = f"CN={name},OU=Domain Controllers,{self.base_dn}" + dnsname = f"{name}.{REALM}".lower() + self.samdb.add({ + "dn": dn, + "objectclass": "computer", + "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT | + UF_TRUSTED_FOR_DELEGATION), + "dnsHostName": dnsname, + "carLicense": self.id() + }) + self.objects[name] = (dn, dnsname) + + def add_user(self, name): + dn = f"CN={name},{self.ou}" + self.samdb.add({ + "dn": dn, + "name": name, + "objectclass": "user", + "carLicense": self.id() + }) + self.objects[name] = (dn, None) + + def remove_object(self, name): + dn, dnsname = self.objects.pop(name) + self.samdb.delete(dn) + + def setUp(self): + super().setUp() + self.samdb = get_samdb() + self.base_dn = self.samdb.get_default_basedn() + self.short_id = self.id().rsplit('.', 1)[1][:63] + self.objects = {} + self.ou = f"OU={ self.short_id },{ self.base_dn }" + self.addCleanup(self.samdb.delete, self.ou, ["tree_delete:1"]) + self.samdb.add({"dn": self.ou, "objectclass": "organizationalUnit"}) + + +@DynamicTestCase +class LdapUpnSamTest(LdapUpnSamTestBase): + cases = [ + # The structure is + # ( «documentation/message that becomes test name», + # («short object id», «upn or sam or mapping», «expected»), + # («short object id», «upn or sam or mapping», «expected»), + # ..., + # ) + # + # where the first item is a one line string explaining the + # test, and subsequent items describe database modifications, + # to be applied in series. + # + # First is a short ID, which maps to an object DN. Second is + # either a string or a dictionary. + # + # * If a string, if it contains '@', it is a UPN, otherwise a + # samaccountname. + # + # * If a dictionary, it is a mapping of some of ['sam', 'upn'] + # to strings (in this way, you can add two attributes in one + # mesage, or attempt a samaccountname with '@'). + # + # expected can be «ok», «bad» (mapped to True and False, + # respectively), or a specific LDB error code, if that exact + # exception is wanted. + ("add good UPN", + ('A', 'a@{realm}', ok), + ), + ("add the same upn to different objects", + ('A', 'a@{realm}', ok), + ('B', 'a@{realm}', ldb.ERR_CONSTRAINT_VIOLATION), + ('B', 'a@{lrealm}', ldb.ERR_CONSTRAINT_VIOLATION), # lowercase realm + ), + ("replace UPN with itself", + ('A', 'a@{realm}', ok), + ('A', 'a@{realm}', ok), + ('A', 'a@{lrealm}', ok), + ), + ("replace SAM with itself", + ('A', 'a', ok), + ('A', 'a', ok), + ), + ("replace UPN realm", + ('A', 'a@{realm}', ok), + ('A', 'a@{other_realm}', ok), + ), + ("matching SAM and UPN", + ('A', 'a', ok), + ('A', 'a@{realm}', ok), + ), + ("matching SAM and UPN, other realm", + ('A', 'a', ok), + ('A', 'a@{other_realm}', ok), + ), + ("matching SAM and UPN, single message", + ('A', {'sam': 'a', 'upn': 'a@{realm}'}, ok), + ('A', {'sam': 'a', 'upn': 'a@{other_realm}'}, ok), + ), + ("different objects, different realms", + ('A', 'a@{realm}', ok), + ('B', 'a@{other_realm}', ok), + ), + ("different objects, same UPN, different case", + ('A', 'a@{realm}', ok), + ('B', 'A@{realm}', ldb.ERR_CONSTRAINT_VIOLATION), + ), + ("different objects, SAM after UPN", + ('A', 'a@{realm}', ok), + ('B', 'a', ldb.ERR_CONSTRAINT_VIOLATION), + ), + ("different objects, SAM before UPN", + ('A', 'a', ok), + ('B', 'a@{realm}', exists), + ), + ("different objects, SAM account clash", + ('A', 'a', ok), + ('B', 'a', exists), + ), + ("different objects, SAM account clash, different case", + ('A', 'a', ok), + ('B', 'A', exists), + ), + ("two way clash", + ('A', {'sam': 'x', 'upn': 'y@{realm}'}, ok), + # The sam account raises EXISTS while the UPN raises + # CONSTRAINT_VIOLATION. We don't really care in which order + # they are checked, so either error is ok. + ('B', {'sam': 'y', 'upn': 'x@{realm}'}, + (exists, ldb.ERR_CONSTRAINT_VIOLATION)), + ), + ("two way clash, other realm", + ('A', {'sam': 'x', 'upn': 'y@{other_realm}'}, ok), + ('B', {'sam': 'y', 'upn': 'x@{other_realm}'}, ok), + ), + # UPN versions of bad sam account names + ("UPN clash on other realm", + ('A', 'a@x.x', ok), + ('B', 'a@x.x', ldb.ERR_CONSTRAINT_VIOLATION), + ), + ("UPN same but for trailing spaces", + ('A', 'a@{realm}', ok), + ('B', 'a @{realm}', ok), + ), + # UPN has no at + ("UPN has no at", + ('A', {'upn': 'noat'}, ok), + ('B', {'upn': 'noat'}, ldb.ERR_CONSTRAINT_VIOLATION), + ('C', {'upn': 'NOAT'}, ldb.ERR_CONSTRAINT_VIOLATION), + ), + # UPN has non-ascii at, followed by real at. + ("UPN with non-ascii at vs real at", + ('A', {'upn': 'smallat﹫{realm}'}, ok), + ('B', {'upn': 'smallat@{realm}'}, ok), + ('C', {'upn': 'tagat\U000e0040{realm}'}, ok), + ('D', {'upn': 'tagat@{realm}'}, ok), + ), + ("UPN with unicode at vs real at, real at first", + ('B', {'upn': 'smallat@{realm}'}, ok), + ('A', {'upn': 'smallat﹫{realm}'}, ok), + ('D', {'upn': 'tagat@{realm}'}, ok), + ('C', {'upn': 'tagat\U000e0040{realm}'}, ok), + ), + ("UPN username too long", + # SPN soft limit 20; hard limit 256, overall UPN 1024 + ('A', 'a' * 25 + '@b.c', ok), + ('A', 'a' * 65 + '@b.c', ok), # Azure AD limit is 64 + ('A', 'a' * 257 + '@b.c', ok), # 256 is sam account name limit + ), + ("sam account name 20 long", + # SPN soft limit 20 + ('A', 'a' * 20, ok), + ), + ("UPN has two at signs", + ('A', 'a@{realm}', ok), + ('A', 'a@{realm}@{realm}', ok), + ('A', 'a@a.b', ok), + ('A', 'a@a@a.b', ok), + ), + ("SAM has at signs clashing upn second, non-realm", + ('A', {'sam': 'a@a.b'}, ok), + ('B', 'a@a.b@a.b', ok), # UPN won't clash with SAM, because realm + ), + ("SAM has at signs clashing upn second", + ('A', {'sam': 'a@{realm}'}, ok), + ('B', 'a@{realm}@{realm}', bad), # UPN would clashes with SAM + ), + ("SAM has at signs clashing upn first", + ('B', 'a@{realm}@{realm}', ok), + ('A', {'sam': 'a@{realm}'}, bad), + ), + ("spaces around at", + ('A', 'a name @ {realm}', ok), + ('B', 'a name @ {realm}', ldb.ERR_CONSTRAINT_VIOLATION), + ('B', 'a name @{realm}', ok), # because realm looks different + ('C', 'a name@{realm}', ok), + ('D', 'a name', ldb.ERR_CONSTRAINT_VIOLATION), + ('D', 'a name ', (exists, ldb.ERR_CONSTRAINT_VIOLATION)), # matches B + ), + ("SAM starts with at", + ('A', {'sam': '@{realm}'}, ok), + ('B', {'sam': '@a'}, ok), + ('C', {'sam': '@{realm}'}, exists), + ('C', {'sam': '@a'}, exists), + ('C', {'upn': '@{realm}@{realm}'}, bad), + ('C', {'upn': '@a@{realm}'}, bad), + ), + ("UPN starts with at", + ('A', {'upn': '@{realm}'}, ok), + ('B', {'upn': '@a@{realm}'}, ok), + ('C', {'upn': '@{realm}'}, bad), + ('C', {'sam': '@a'}, bad), + ), + ("SAM ends with at", + ('A', {'sam': '{realm}@'}, ok), + ('B', {'sam': 'a@'}, ok), + ('C', {'sam': '{realm}@'}, exists), + ('C', {'sam': 'a@'}, exists), + ('C', {'upn': 'a@@{realm}'}, bad), + ('C', {'upn': '{realm}@@{realm}'}, bad), + ), + ("UPN ends with at", + ('A', {'upn': '{realm}@'}, ok), + ('B', {'upn': '@a@{realm}@'}, ok), + ('C', {'upn': '{realm}@'}, bad), + ('C', {'sam': '@a@{realm}'}, ok), # not like B, because other realm + ), + ] + + +@DynamicTestCase +class LdapUpnSamSambaOnlyTest(LdapUpnSamTestBase): + # We don't run these ones outside of selftest, where we are + # probably testing against Windows and these are known failures. + _disabled = 'SAMBA_SELFTEST' not in os.environ + cases = [ + ("sam account name too long", + # SPN soft limit 20 + ('A', 'a' * 19, ok), + ('A', 'a' * 20, ok), + ('A', 'a' * 65, ok), + ('A', 'a' * 255, ok), + ('A', 'a' * 256, ok), + ('A', 'a' * 257, ldb.ERR_INVALID_ATTRIBUTE_SYNTAX), + ), + ("UPN username too long", + ('A', 'a' * 254 + '@' + 'b.c' * 257, + ldb.ERR_INVALID_ATTRIBUTE_SYNTAX), # 1024 is alleged UPN limit + ), + ("UPN same but for internal spaces", + ('A', 'a b@x.x', ok), + ('B', 'a b@x.x', ldb.ERR_CONSTRAINT_VIOLATION), + ), + ("SAM contains delete", + # forbidden according to documentation, but works in practice on Windows + ('A', 'a\x7f', ldb.ERR_CONSTRAINT_VIOLATION), + ('A', 'a\x7f'.encode(), ldb.ERR_CONSTRAINT_VIOLATION), + ('A', 'a\x7fb', ldb.ERR_CONSTRAINT_VIOLATION), + ('A', 'a\x7fb'.encode(), ldb.ERR_CONSTRAINT_VIOLATION), + ('A', '\x7fb', ldb.ERR_CONSTRAINT_VIOLATION), + ('A', '\x7fb'.encode(), ldb.ERR_CONSTRAINT_VIOLATION), + ), + # The wide at symbol ('@' U+FF20) does not count as '@' for Samba + # so it will look like a string with no @s. + ("UPN with unicode wide at vs real at", + ('A', {'upn': 'wideat@{realm}'}, ok), + ('B', {'upn': 'wideat@{realm}'}, ok), + ), + ("UPN with real at vs wide at", + ('B', {'upn': 'wideat@{realm}'}, ok), + ('A', {'upn': 'wideat@{realm}'}, ok) + ), + ] + + +def main(): + global LP, CREDS, SERVER, REALM + + parser = optparse.OptionParser( + "python3 ldap_upn_sam_account_name.py [options]") + sambaopts = options.SambaOptions(parser) + parser.add_option_group(sambaopts) + + # use command line creds if available + credopts = options.CredentialsOptions(parser) + parser.add_option_group(credopts) + subunitopts = SubunitOptions(parser) + parser.add_option_group(subunitopts) + + opts, args = parser.parse_args() + if len(args) != 1: + parser.print_usage() + sys.exit(1) + + LP = sambaopts.get_loadparm() + CREDS = credopts.get_credentials(LP) + SERVER = args[0] + REALM = CREDS.get_realm() + + TestProgram(module=__name__, opts=subunitopts) + +main() diff --git a/selftest/knownfail.d/ldap_upn_sam_account b/selftest/knownfail.d/ldap_upn_sam_account new file mode 100644 index 000000000000..c4d494968b2a --- /dev/null +++ b/selftest/knownfail.d/ldap_upn_sam_account @@ -0,0 +1,16 @@ +samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_SAM_contains_delete +samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_UPN_same_but_for_internal_spaces +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_ends_with_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_first +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_second +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_starts_with_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_clash_on_other_realm +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_ends_with_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_has_no_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_starts_with_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_add_the_same_upn_to_different_objects +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_after_UPN +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_before_UPN +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_same_UPN_different_case +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_spaces_around_at +samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_two_way_clash diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 567fff736b5a..88a28d2f8d84 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1073,6 +1073,15 @@ planoldpythontestsuite("ad_dc", extra_args=['-U"$USERNAME%$PASSWORD"'], environ={'TEST_ENV': 'ad_dc'}) +plantestsuite_loadlist("samba.tests.ldap_upn_sam_account", "ad_dc", + [python, + f"{srcdir()}/python/samba/tests/ldap_upn_sam_account.py", + '$SERVER', + '-U"$USERNAME%$PASSWORD"', + '--workgroup=$DOMAIN', + '$LOADLIST', '$LISTOPT']) + + plantestsuite_loadlist("samba4.tokengroups.krb5.python(ad_dc_default)", "ad_dc_default:local", [python, os.path.join(DSDB_PYTEST_DIR, "token_group.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '-k', 'yes', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.tokengroups.ntlm.python(ad_dc_default)", "ad_dc_default:local", [python, os.path.join(DSDB_PYTEST_DIR, "token_group.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '-k', 'no', '$LOADLIST', '$LISTOPT']) plantestsuite("samba4.sam.python(fl2008r2dc)", "fl2008r2dc", [python, os.path.join(DSDB_PYTEST_DIR, "sam.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN']) -- 2.25.1 From f2e1e622b944e4b30c4171d9d78635eb681877b2 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 6 Aug 2021 12:03:18 +1200 Subject: [PATCH 129/224] CVE-2020-25722 pytest: test setting servicePrincipalName over ldap BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- python/samba/tests/ldap_spn.py | 917 +++++++++++++++++++++++++++++++++ selftest/knownfail.d/ldap_spn | 26 + source4/selftest/tests.py | 10 +- 3 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 python/samba/tests/ldap_spn.py create mode 100644 selftest/knownfail.d/ldap_spn diff --git a/python/samba/tests/ldap_spn.py b/python/samba/tests/ldap_spn.py new file mode 100644 index 000000000000..8a398ffaa491 --- /dev/null +++ b/python/samba/tests/ldap_spn.py @@ -0,0 +1,917 @@ +# Unix SMB/CIFS implementation. +# +# Copyright 2021 (C) Catalyst IT Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import sys +import os +import pprint +import re +from samba.samdb import SamDB +from samba.auth import system_session +import ldb +from samba.sd_utils import SDUtils +from samba.credentials import DONT_USE_KERBEROS, Credentials +from samba.gensec import FEATURE_SEAL +from samba.tests.subunitrun import SubunitOptions, TestProgram +from samba.tests import TestCase, ldb_err +from samba.tests import DynamicTestCase +import samba.getopt as options +import optparse +from samba.colour import c_RED, c_GREEN, c_DARK_YELLOW +from samba.dsdb import ( + UF_SERVER_TRUST_ACCOUNT, + UF_TRUSTED_FOR_DELEGATION, +) + + +SPN_GUID = 'f3a64788-5306-11d1-a9c5-0000f80367c1' + +RELEVANT_ATTRS = {'dNSHostName', + 'servicePrincipalName', + 'sAMAccountName', + 'dn'} + +ok = True +bad = False +report = 'report' + +operr = ldb.ERR_OPERATIONS_ERROR +denied = ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS +constraint = ldb.ERR_CONSTRAINT_VIOLATION +exists = ldb.ERR_ENTRY_ALREADY_EXISTS + +add = ldb.FLAG_MOD_ADD +replace = ldb.FLAG_MOD_REPLACE +delete = ldb.FLAG_MOD_DELETE + +try: + breakpoint +except NameError: + # for python <= 3.6 + def breakpoint(): + import pdb + pdb.set_trace() + + +def init(): + # This needs to happen before the class definition, and we put it + # in a function to keep the namespace clean. + global LP, CREDS, SERVER, REALM, COLOUR_TEXT, subunitopts, FILTER + + parser = optparse.OptionParser( + "python3 ldap_spn.py [options]") + sambaopts = options.SambaOptions(parser) + parser.add_option_group(sambaopts) + + # use command line creds if available + credopts = options.CredentialsOptions(parser) + parser.add_option_group(credopts) + subunitopts = SubunitOptions(parser) + parser.add_option_group(subunitopts) + + parser.add_option('--colour', action="store_true", + help="use colour text", + default=sys.stdout.isatty()) + + parser.add_option('--filter', help="only run tests matching this regex") + + opts, args = parser.parse_args() + if len(args) != 1: + parser.print_usage() + sys.exit(1) + + LP = sambaopts.get_loadparm() + CREDS = credopts.get_credentials(LP) + SERVER = args[0] + REALM = CREDS.get_realm() + COLOUR_TEXT = opts.colour + FILTER = opts.filter + + +init() + + +def colour_text(x, state=None): + if not COLOUR_TEXT: + return x + if state == 'error': + return c_RED(x) + if state == 'pass': + return c_GREEN(x) + + return c_DARK_YELLOW(x) + + +def get_samdb(creds=None): + if creds is None: + creds = CREDS + session = system_session() + else: + session = None + + return SamDB(url=f"ldap://{SERVER}", + lp=LP, + session_info=session, + credentials=creds) + + +def add_unpriv_user(samdb, ou, username, + writeable_objects=None, + password="samba123@"): + creds = Credentials() + creds.set_username(username) + creds.set_password(password) + creds.set_domain(CREDS.get_domain()) + creds.set_realm(CREDS.get_realm()) + creds.set_workstation(CREDS.get_workstation()) + creds.set_gensec_features(CREDS.get_gensec_features() | FEATURE_SEAL) + creds.set_kerberos_state(DONT_USE_KERBEROS) + dnstr = f"CN={username},{ou}" + + # like, WTF, samdb.newuser(), this is what you make us do. + short_ou = ou.split(',', 1)[0] + + samdb.newuser(username, password, userou=short_ou) + + if writeable_objects: + sd_utils = SDUtils(samdb) + sid = sd_utils.get_object_sid(dnstr) + for obj in writeable_objects: + mod = f"(OA;CI;WP;{ SPN_GUID };;{ sid })" + sd_utils.dacl_add_ace(obj, mod) + + unpriv_samdb = get_samdb(creds=creds) + return unpriv_samdb + + +class LdapSpnTestBase(TestCase): + _disabled = False + + @classmethod + def setUpDynamicTestCases(cls): + if getattr(cls, '_disabled', False): + return + for doc, *rows in cls.cases: + if FILTER: + if not re.search(FILTER, doc): + continue + name = re.sub(r'\W+', '_', doc) + cls.generate_dynamic_test("test_spn", name, rows, doc) + + def setup_objects(self, rows): + objects = set(r[0] for r in rows) + for name in objects: + if ':' in name: + objtype, name = name.split(':', 1) + else: + objtype = 'dc' + getattr(self, f'add_{objtype}')(name) + + def setup_users(self, rows): + # When you are adding an SPN that aliases (or would be aliased + # by) another SPN on another object, you need to have write + # permission on that other object too. + # + # To test this negatively and positively, we need to have + # users with various combinations of write permission, which + # means fiddling with SDs on the objects. + # + # The syntax is: + # '' : user with no special permissions + # '*' : admin user + # 'A' : user can write to A only + # 'A,C' : user can write to A and C + # 'C,A' : same, but makes another user + self.userdbs = { + '*': self.samdb + } + + permissions = set(r[2] for r in rows) + for p in permissions: + if p == '*': + continue + if p == '': + user = 'nobody' + writeable_objects = None + else: + user = 'writes_' + p.replace(",", '_') + writeable_objects = [self.objects[x][0] for x in p.split(',')] + + self.userdbs[p] = add_unpriv_user(self.samdb, self.ou, user, + writeable_objects) + + def _test_spn_with_args(self, rows, doc): + cdoc = colour_text(doc) + edoc = colour_text(doc, 'error') + pdoc = colour_text(doc, 'pass') + + if COLOUR_TEXT: + sys.stderr.flush() + print('\n', c_DARK_YELLOW('#' * 10), f'starting «{cdoc}»\n') + sys.stdout.flush() + + self.samdb = get_samdb() + self.base_dn = self.samdb.get_default_basedn() + self.short_id = self.id().rsplit('.', 1)[1][:63] + self.objects = {} + self.ou = f"OU={ self.short_id },{ self.base_dn }" + self.addCleanup(self.samdb.delete, self.ou, ["tree_delete:1"]) + self.samdb.create_ou(self.ou) + + self.setup_objects(rows) + self.setup_users(rows) + + for i, row in enumerate(rows): + if len(row) == 5: + obj, data, rights, expected, op = row + else: + obj, data, rights, expected = row + op = ldb.FLAG_MOD_REPLACE + + # We use this DB with possibly restricted rights for this row + samdb = self.userdbs[rights] + + if ':' in obj: + objtype, obj = obj.split(':', 1) + else: + objtype = 'dc' + + dn, dnsname = self.objects[obj] + m = {"dn": dn} + + if isinstance(data, dict): + m.update(data) + else: + m['servicePrincipalName'] = data + + # for python's sake (and our sanity) we try to ensure we + # have consistent canonical case in our attributes + keys = set(m.keys()) + if not keys.issubset(RELEVANT_ATTRS): + raise ValueError(f"unexpected attr {keys - RELEVANT_ATTRS}. " + "Casefold typo?") + + for k in ('dNSHostName', 'servicePrincipalName'): + if isinstance(m.get(k), str): + m[k] = m[k].format(dnsname=f"x.{REALM}") + + msg = ldb.Message.from_dict(samdb, m, op) + + if expected is bad: + try: + samdb.modify(msg) + except ldb.LdbError as e: + print(f"row {i+1} of '{pdoc}' failed as expected with " + f"{ldb_err(e)}\n") + continue + self.fail(f"row {i+1}: " + f"{rights} {pprint.pformat(m)} on {objtype} {obj} " + f"should fail ({edoc})") + + elif expected is ok: + try: + samdb.modify(msg) + except ldb.LdbError as e: + self.fail(f"row {i+1} of {edoc} failed with {ldb_err(e)}:\n" + f"{rights} {pprint.pformat(m)} on {objtype} {obj}") + + elif expected is report: + try: + self.samdb.modify(msg) + print(f"row {i+1} " + f"of '{cdoc}' {colour_text('SUCCEEDED', 'pass')}:\n" + f"{pprint.pformat(m)} on {obj}") + except ldb.LdbError as e: + print(f"row {i+1} " + f"of '{cdoc}' {colour_text('FAILED', 'error')} " + f"with {ldb_err(e)}:\n{pprint.pformat(m)} on {obj}") + + elif expected is breakpoint: + try: + breakpoint() + samdb.modify(msg) + except ldb.LdbError as e: + print(f"row {i+1} of '{pdoc}' FAILED with {ldb_err(e)}\n") + + else: # an ldb error number + try: + samdb.modify(msg) + except ldb.LdbError as e: + if e.args[0] == expected: + continue + self.fail(f"row {i+1} of '{edoc}' " + f"should have failed with {ldb_err(expected)}:\n" + f"not {ldb_err(e)}:\n" + f"{rights} {pprint.pformat(m)} on {objtype} {obj}") + self.fail(f"row {i+1} of '{edoc}' " + f"should have failed with {ldb_err(expected)}:\n" + f"{rights} {pprint.pformat(m)} on {objtype} {obj}") + + def add_dc(self, name): + dn = f"CN={name},OU=Domain Controllers,{self.base_dn}" + dnsname = f"{name}.{REALM}".lower() + self.samdb.add({ + "dn": dn, + "objectclass": "computer", + "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT | + UF_TRUSTED_FOR_DELEGATION), + "dnsHostName": dnsname, + "carLicense": self.id() + }) + self.addCleanup(self.remove_object, name) + self.objects[name] = (dn, dnsname) + + def add_user(self, name): + dn = f"CN={name},{self.ou}" + self.samdb.add({ + "dn": dn, + "name": name, + "samAccountName": name, + "objectclass": "user", + "carLicense": self.id() + }) + self.addCleanup(self.remove_object, name) + self.objects[name] = (dn, None) + + def remove_object(self, name): + dn, dnsname = self.objects.pop(name) + self.samdb.delete(dn) + + +@DynamicTestCase +class LdapSpnTest(LdapSpnTestBase): + """Make sure we can't add clashing servicePrincipalNames. + + This would be possible using sPNMappings aliases — for example, if + the mapping maps host/ to cifs/, we should not be able to add + different addresses for each. + """ + + # default sPNMappings: host=alerter, appmgmt, cisvc, clipsrv, + # browser, dhcp, dnscache, replicator, eventlog, eventsystem, + # policyagent, oakley, dmserver, dns, mcsvc, fax, msiserver, ias, + # messenger, netlogon, netman, netdde, netddedsm, nmagent, + # plugplay, protectedstorage, rasman, rpclocator, rpc, rpcss, + # remoteaccess, rsvp, samss, scardsvr, scesrv, seclogon, scm, + # dcom, cifs, spooler, snmp, schedule, tapisrv, trksvr, trkwks, + # ups, time, wins, www, http, w3svc, iisadmin, msdtc + # + # I think in practice this is rarely if ever changed or added to. + + cases = [ + ("add one as admin", + ('A', 'host/{dnsname}', '*', ok), + ), + ("add one as rightful user", + ('A', 'host/{dnsname}', 'A', ok), + ), + ("attempt to add one as nobody", + ('A', 'host/{dnsname}', '', denied), + ), + + ("add and replace as admin", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/x.{dnsname}', '*', ok), + ), + ("replace as rightful user", + ('A', 'host/{dnsname}', 'A', ok), + ('A', 'host/x.{dnsname}', 'A', ok), + ), + ("attempt to replace one as nobody", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/x.{dnsname}', '', denied), + ), + + ("add second as admin", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/x.{dnsname}', '*', ok, add), + ), + ("add second as rightful user", + ('A', 'host/{dnsname}', 'A', ok), + ('A', 'host/x.{dnsname}', 'A', ok, add), + ), + ("attempt to add second as nobody", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/x.{dnsname}', '', denied, add), + ), + + ("add the same one twice, simple duplicate error", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '*', bad, add), + ), + ("simple duplicate attributes, as non-admin", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', 'A', bad, add), + ), + + ("add the same one twice, identical duplicate", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '*', bad, add), + ), + + ("add a conflict, host first, as nobody", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', '', denied), + ), + + ("add a conflict, service first, as nobody", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'host/{dnsname}', '', denied), + ), + + + ("three way conflict, host first, as admin", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', '*', ok), + ('C', 'www/z.{dnsname}', '*', ok), + ), + ("three way conflict, host first, with sufficient rights", + ('A', 'host/z.{dnsname}', 'A', ok), + ('B', 'cifs/z.{dnsname}', 'B,A', ok), + ('C', 'www/z.{dnsname}', 'C,A', ok), + ), + ("three way conflict, host first, adding duplicate", + ('A', 'host/z.{dnsname}', 'A', ok), + ('B', 'cifs/z.{dnsname}', 'B,A', ok), + ('C', 'cifs/z.{dnsname}', 'C,A', bad), + ), + ("three way conflict, host first, adding duplicate, full rights", + ('A', 'host/z.{dnsname}', 'A', ok), + ('B', 'cifs/z.{dnsname}', 'B,A', ok), + ('C', 'cifs/z.{dnsname}', 'C,B,A', bad), + ), + + ("three way conflict, host first, with other write rights", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', 'A,B', ok), + ('C', 'cifs/z.{dnsname}', 'A,B', bad), + + ), + ("three way conflict, host first, as nobody", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', '*', ok), + ('C', 'www/z.{dnsname}', '', denied), + ), + + ("three way conflict, services first, as admin", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'www/{dnsname}', '*', ok), + ('C', 'host/{dnsname}', '*', constraint), + ), + ("three way conflict, services first, with service write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'www/{dnsname}', '*', ok), + ('C', 'host/{dnsname}', 'A,B', bad), + ), + + ("three way conflict, service first, as nobody", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'www/{dnsname}', '*', ok), + ('C', 'host/{dnsname}', '', denied), + ), + ("replace host before specific", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok), + ), + ("replace host after specific, as nobody", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '', denied), + ), + + ("non-conflict host before specific", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok, add), + ), + ("non-conflict host after specific", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '*', ok, add), + ), + ("non-conflict host before specific, non-admin", + ('A', 'host/{dnsname}', 'A', ok), + ('A', 'cifs/{dnsname}', 'A', ok, add), + ), + ("non-conflict host after specific, as nobody", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '', denied, add), + ), + + ("add a conflict, host first on user, as admin", + ('user:C', 'host/{dnsname}', '*', ok), + ('B', 'cifs/{dnsname}', '*', ok), + ), + ("add a conflict, host first on user, host rights", + ('user:C', 'host/{dnsname}', '*', ok), + ('B', 'cifs/{dnsname}', 'C', denied), + ), + ("add a conflict, host first on user, both rights", + ('user:C', 'host/{dnsname}', '*', ok), + ('B', 'cifs/{dnsname}', 'B,C', ok), + ), + ("add a conflict, host first both on user", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}', '*', ok), + ), + ("add a conflict, host first both on user, host rights", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}', 'C', denied), + ), + ("add a conflict, host first both on user, both rights", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}', 'C,D', ok), + ), + ("add a conflict, host first both on user, as nobody", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}', '', denied), + ), + ("add a conflict, host first, with both write rights", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', 'A,B', ok), + ), + + ("add a conflict, host first, second on user, as admin", + ('A', 'host/{dnsname}', '*', ok), + ('user:D', 'cifs/{dnsname}', '*', ok), + ), + ("add a conflict, host first, second on user, with rights", + ('A', 'host/{dnsname}', '*', ok), + ('user:D', 'cifs/{dnsname}', 'A,D', ok), + ), + + ("nonsense SPNs, part 1, as admin", + ('A', 'a-b-c/{dnsname}', '*', ok), + ('A', 'rrrrrrrrrrrrr /{dnsname}', '*', ok), + ), + ("nonsense SPNs, part 1, as user", + ('A', 'a-b-c/{dnsname}', 'A', ok), + ('A', 'rrrrrrrrrrrrr /{dnsname}', 'A', ok), + ), + ("nonsense SPNs, part 1, as nobody", + ('A', 'a-b-c/{dnsname}', '', denied), + ('A', 'rrrrrrrrrrrrr /{dnsname}', '', denied), + ), + + ("add a conflict, using port", + ('A', 'dns/{dnsname}', '*', ok), + ('B', 'dns/{dnsname}:53', '*', ok), + ), + ("add a conflict, using port, port first", + ('user:C', 'dns/{dnsname}:53', '*', ok), + ('user:D', 'dns/{dnsname}', '*', ok), + ), + ("three part spns", + ('A', {'dNSHostName': '{dnsname}'}, '*', ok), + ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok), + ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', constraint), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok), + ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint), + ), + ("three part nonsense spns", + ('A', {'dNSHostName': 'bean'}, '*', ok), + ('A', 'cifs/bean/DomainDNSZones.bean', '*', ok), + ('B', 'cifs/bean/DomainDNSZones.bean', '*', constraint), + ('A', {'dNSHostName': 'y.bean'}, '*', ok), + ('B', 'cifs/bean/DomainDNSZones.bean', '*', ok), + ('B', 'cifs/y.bean/DomainDNSZones.bean', '*', constraint), + ('C', 'host/bean/bean', '*', ok), + ), + + ("one part spns (no slashes)", + ('A', '{dnsname}', '*', constraint), + ('B', 'cifs', '*', constraint), + ('B', 'cifs/', '*', ok), + ('B', ' ', '*', constraint), + ('user:C', 'host', '*', constraint), + ), + + ("dodgy spns", + # These tests pass on Windows. An SPN must have one or two + # slashes, with at least one character before the first one, + # UNLESS the first slash is followed by a good enough service + # name (e.g. "/host/x.y" rather than "sdfsd/x.y"). + ('A', '\\/{dnsname}', '*', ok), + ('B', 'cifs/\\\\{dnsname}', '*', ok), + ('B', r'cifs/\\\{dnsname}', '*', ok), + ('B', r'cifs/\\\{dnsname}/', '*', ok), + ('A', r'cīfs/\\\{dnsname}/', '*', constraint), # 'ī' maps to 'i' + # on the next two, full-width solidus (U+FF0F) does not work + # as '/'. + ('A', 'cifs/sfic', '*', constraint, add), + ('A', r'cifs/\\\{dnsname}', '*', constraint, add), + ('B', '\n', '*', constraint), + ('B', '\n/\n', '*', ok), + ('B', '\n/\n/\n', '*', ok), + ('B', '\n/\n/\n/\n', '*', constraint), + ('B', ' /* and so on */ ', '*', ok, add), + ('B', r'¯\_(ツ)_/¯', '*', ok, add), # ¯\_(ツ)_/¯ + # つ is hiragana for katakana ツ, so the next one fails for + # something analogous to casefold reasons. + ('A', r'¯\_(つ)_/¯', '*', constraint), + ('A', r'¯\_(㋡)_/¯', '*', constraint), # circled ツ + ('B', '//', '*', constraint), # all can't be empty, + ('B', ' //', '*', ok), # service can be space + ('B', '/host/{dnsname}', '*', ok), # or empty if others aren't + ('B', '/host/x.y.z', '*', ok), + ('B', '/ /x.y.z', '*', ok), + ('B', ' / / ', '*', ok), + ('user:C', b'host/', '*', ok), + ('user:C', ' /host', '*', ok), # service is ' ' (space) + ('B', ' /host', '*', constraint), # already on C + ('B', ' /HōST', '*', constraint), # ō equiv to O + ('B', ' /ħØşt', '*', constraint), # maps to ' /host' + ('B', ' /H0ST', '*', ok), # 0 is zero + ('B', ' /НoST', '*', ok), # Cyrillic Н (~N) + ('B', ' /host', '*', ok), # two space + ('B', '\u00a0/host', '*', ok), # non-breaking space + ('B', ' 2/HōST/⌷[ ][]¨(', '*', ok), + ('B', ' (//)', '*', ok, add), + ('B', ' ///', '*', constraint), + ('B', r' /\//', '*', constraint), # escape doesn't help + ('B', ' /\\//', '*', constraint), # double escape doesn't help + ('B', r'\//', '*', ok), + ('A', r'\\/\\/', '*', ok), + ('B', '|//|', '*', ok, add), + ('B', r'\/\/\\', '*', ok, add), + + ('A', ':', '*', constraint), + ('A', ':/:', '*', ok), + ('A', ':/:80', '*', ok), # port number syntax is not special + ('A', ':/:( ツ', '*', ok), + ('A', ':/:/:', '*', ok), + ('B', b'cifs/\x11\xaa\xbb\xcc\\example.com', '*', ok), + ('A', b':/\xcc\xcc\xcc\xcc', '*', ok), + ('A', b':/b\x00/b/b/b', '*', ok), # string handlng truncates at \x00 + ('A', b'a@b/a@b/a@b', '*', ok), + ('A', b'a/a@b/a@b', '*', ok), + ), + ("empty part spns (consecutive slashes)", + ('A', 'cifs//{dnsname}', '*', ok), + ('B', 'cifs//{dnsname}', '*', bad), # should clash with line 1 + ('B', 'cifs/zzzy.{dnsname}/', '*', ok), + ('B', '/host/zzzy.{dnsname}', '*', ok), + ), + ("too many spn parts", + ('A', 'cifs/{dnsname}/{dnsname}/{dnsname}', '*', bad), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}/{dnsname}/', '*', bad), + ('B', 'cifs/y.{dnsname}/{dnsname}/toop', '*', bad), + ('B', 'host/{dnsname}/a/b/c', '*', bad), + ), + ("add a conflict, host first, as admin", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', '*', ok), + ), + ("add a conflict, host first, with host write rights", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', 'A', denied), + ), + ("add a conflict, service first, with service write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'host/{dnsname}', 'A', denied), + ), + ("adding dNSHostName after cifs with no old dNSHostName", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}', '*', constraint), + ('B', 'cifs/y.{dnsname}', '*', ok), + ('B', 'host/y.{dnsname}', '*', ok), + ), + ("changing dNSHostName after cifs", + ('A', {'dNSHostName': '{dnsname}'}, '*', ok), + ('A', 'cifs/{dnsname}', '*', ok), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}', '*', ok), + ('B', 'cifs/y.{dnsname}', '*', bad), + ('B', 'host/y.{dnsname}', '*', bad), + ), + ] + + +@DynamicTestCase +class LdapSpnSambaOnlyTest(LdapSpnTestBase): + # We don't run these ones outside of selftest, where we are + # probably testing against Windows and these are known failures. + _disabled = 'SAMBA_SELFTEST' not in os.environ + cases = [ + ("add a conflict, host first, with service write rights", + ('A', 'host/z.{dnsname}', '*', ok), + ('B', 'cifs/z.{dnsname}', 'B', denied), + ), + ("add a conflict, service first, with host write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'host/{dnsname}', 'B', constraint), + ), + ("add a conflict, service first, as admin", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'host/{dnsname}', '*', constraint), + ), + ("add a conflict, service first, with both write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'host/{dnsname}', 'A,B', constraint), + ), + ("add a conflict, host first both on user, service rights", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}', 'D', denied), + ), + + ("changing dNSHostName after host", + ('A', {'dNSHostName': '{dnsname}'}, '*', ok), + ('A', 'host/{dnsname}', '*', ok), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}', 'B', ok), # no clash with A + ('B', 'cifs/y.{dnsname}', 'B', bad), # should clash with A + ('B', 'host/y.{dnsname}', '*', bad), + ), + + ("mystery dnsname clash, host first", + ('user:C', 'host/heeble.example.net', '*', ok), + ('user:D', 'www/heeble.example.net', '*', ok), + ), + ("mystery dnsname clash, www first", + ('user:D', 'www/heeble.example.net', '*', ok), + ('user:C', 'host/heeble.example.net', '*', constraint), + ), + ("replace as admin", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok), + ), + ("replace as non-admin with rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', 'A', ok), + ('A', 'cifs/{dnsname}', 'A', ok), + ), + ("replace vial delete as non-admin with rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', 'A', ok), + ('A', 'host/{dnsname}', 'A', ok, delete), + ('A', 'cifs/{dnsname}', 'A', ok, add), + ), + ("replace as non-admin without rights", + ('B', 'cifs/b', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', 'B', denied), + ('A', 'cifs/{dnsname}', 'B', denied), + ), + ("replace as nobody", + ('B', 'cifs/b', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '', denied), + ('A', 'cifs/{dnsname}', '', denied), + ), + ("accumulate and delete as admin", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', '*', ok, add), + ('A', 'www/{dnsname}', '*', ok, add), + ('A', 'www/...', '*', ok, add), + ('A', 'host/...', '*', ok, add), + ('A', 'www/{dnsname}', '*', ok, delete), + ('A', 'host/{dnsname}', '*', ok, delete), + ('A', 'host/{dnsname}', '*', ok, add), + ('A', 'www/{dnsname}', '*', ok, add), + ('A', 'host/...', '*', ok, delete), + ), + ("accumulate and delete with user rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'host/{dnsname}', 'A', ok, add), + ('A', 'www/{dnsname}', 'A', ok, add), + ('A', 'www/...', 'A', ok, add), + ('A', 'host/...', 'A', ok, add), + ('A', 'www/{dnsname}', 'A', ok, delete), + ('A', 'host/{dnsname}', 'A', ok, delete), + ('A', 'host/{dnsname}', 'A', ok, add), + ('A', 'www/{dnsname}', 'A', ok, add), + ('A', 'host/...', 'A', ok, delete), + ), + ("three way conflict, host first, with partial write rights", + ('A', 'host/z.{dnsname}', 'A', ok), + ('B', 'cifs/z.{dnsname}', 'B', denied), + ('C', 'www/z.{dnsname}', 'C', denied), + ), + ("three way conflict, host first, with partial write rights 2", + ('A', 'host/z.{dnsname}', 'A', ok), + ('B', 'cifs/z.{dnsname}', 'B', bad), + ('C', 'www/z.{dnsname}', 'C,A', ok), + ), + + ("three way conflict sandwich, sufficient rights", + ('B', 'host/{dnsname}', 'B', ok), + ('A', 'cifs/{dnsname}', 'A,B', ok), + # the replaces don't fail even though they appear to affect A + # and B, because they are effectively no-ops, leaving + # everything as it was before. + ('A', 'cifs/{dnsname}', 'A', ok), + ('B', 'host/{dnsname}', 'B', ok), + ('C', 'www/{dnsname}', 'A,B,C', ok), + ('C', 'www/{dnsname}', 'B,C', ok), + # because B already has host/, C doesn't matter + ('B', 'host/{dnsname}', 'A,B', ok), + # removing host (via replace) frees others, needs B only + ('B', 'ldap/{dnsname}', 'B', ok), + ('C', 'www/{dnsname}', 'C', ok), + ('A', 'cifs/{dnsname}', 'A', ok), + + # re-adding host is now impossible while A and C have {dnsname} spns + ('B', 'host/{dnsname}', '*', bad), + ('B', 'host/{dnsname}', 'A,B,C', bad), + # so let's remove those... (not needing B rights) + ('C', 'www/{dnsname}', 'C', ok, delete), + ('A', 'cifs/{dnsname}', 'A', ok, delete), + # and now we can add host/ again + ('B', 'host/{dnsname}', 'B', ok), + ('C', 'www/{dnsname}', 'B,C', ok, add), + ('A', 'cifs/{dnsname}', 'A,B', ok), + ), + ("three way conflict, service first, with all write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'www/{dnsname}', 'A,B,C', ok), + ('C', 'host/{dnsname}', 'A,B,C', bad), + ), + ("three way conflict, service first, just sufficient rights", + ('A', 'cifs/{dnsname}', 'A', ok), + ('B', 'www/{dnsname}', 'B', ok), + ('C', 'host/{dnsname}', 'A,B,C', bad), + ), + + ("three way conflict, service first, with host write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('B', 'www/{dnsname}', '*', ok), + ('C', 'host/{dnsname}', 'C', bad), + ), + ("three way conflict, service first, with both write rights", + ('A', 'cifs/{dnsname}', '*', ok), + ('A', 'cifs/{dnsname}', '*', ok, delete), + ('A', 'www/{dnsname}', 'A,B,C', ok), + ('B', 'host/{dnsname}', 'A,B', bad), + ('A', 'www/{dnsname}', 'A', ok, delete), + ('B', 'host/{dnsname}', 'A,B', ok), + ('C', 'cifs/{dnsname}', 'C', bad), + ('C', 'cifs/{dnsname}', 'B,C', ok), + ), + ("three way conflict, services first, with partial rights", + ('A', 'cifs/{dnsname}', 'A,C', ok), + ('B', 'www/{dnsname}', '*', ok), + ('C', 'host/{dnsname}', 'A,C', bad), + ), + ] + + +@DynamicTestCase +class LdapSpnAmbitiousTest(LdapSpnTestBase): + _disabled = True + cases = [ + ("add a conflict with port, host first both on user", + ('user:C', 'host/{dnsname}', '*', ok), + ('user:D', 'www/{dnsname}:80', '*', bad), + ), + # see https://bugzilla.samba.org/show_bug.cgi?id=8929 + ("add the same one twice, case-insensitive duplicate", + ('A', 'host/{dnsname}', '*', ok), + ('A', 'Host/{dnsname}', '*', bad, add), + ), + ("special SPN", + # should fail because we don't have all the DSA infrastructure + ('A', ("E3514235-4B06-11D1-AB04-00C04FC2DCD2/" + "75b84f00-a81b-4a19-8ef2-8e483cccff11/" + "{dnsname}"), '*', constraint) + ), + ("single part SPNs matching sAMAccountName", + # setting them both together is allegedly a MacOS behaviour, + # but all we get from Windows is a mysterious NO_SUCH_OBJECT. + ('user:A', {'sAMAccountName': 'A', + 'servicePrincipalName': 'A'}, '*', ldb.ERR_NO_SUCH_OBJECT), + ('user:B', {'sAMAccountName': 'B'}, '*', ok), + ('user:B', {'servicePrincipalName': 'B'}, '*', constraint), + ('user:C', {'servicePrincipalName': 'C'}, '*', constraint), + ('user:C', {'sAMAccountName': 'C'}, '*', ok), + ), + ("three part spns with dnsHostName", + ('A', {'dNSHostName': '{dnsname}'}, '*', ok), + ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok), + ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok), + ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok), + ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint), + ('C', 'host/{y.dnsname}/{y.dnsname}', '*', constraint), + ('A', 'host/y.{dnsname}/{dnsname}', '*', constraint), + ), + ] + + +def main(): + TestProgram(module=__name__, opts=subunitopts) + +main() diff --git a/selftest/knownfail.d/ldap_spn b/selftest/knownfail.d/ldap_spn new file mode 100644 index 000000000000..dc768728658d --- /dev/null +++ b/selftest/knownfail.d/ldap_spn @@ -0,0 +1,26 @@ +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_host_first_both_on_user_service_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_host_first_with_service_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_as_admin +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_with_both_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_with_host_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_changing_dNSHostName_after_host +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_mystery_dnsname_clash_www_first +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_host_first_with_partial_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_host_first_with_partial_write_rights_2 +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_sandwich_sufficient_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_just_sufficient_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_all_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_both_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_host_write_rights +samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_services_first_with_partial_rights +samba.tests.ldap_spn.+LdapSpnTest.test_spn_adding_dNSHostName_after_cifs_with_no_old_dNSHostName +samba.tests.ldap_spn.+LdapSpnTest.test_spn_changing_dNSHostName_after_cifs +samba.tests.ldap_spn.+LdapSpnTest.test_spn_dodgy_spns +samba.tests.ldap_spn.+LdapSpnTest.test_spn_empty_part_spns_consecutive_slashes_ +samba.tests.ldap_spn.+LdapSpnTest.test_spn_one_part_spns_no_slashes_ +samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_part_nonsense_spns +samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_part_spns +samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_host_first_adding_duplicate +samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_host_first_adding_duplicate_full_rights +samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_services_first_as_admin +samba.tests.ldap_spn.+LdapSpnTest.test_spn_too_many_spn_parts diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 88a28d2f8d84..2b3c2a9cad65 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1073,7 +1073,15 @@ planoldpythontestsuite("ad_dc", extra_args=['-U"$USERNAME%$PASSWORD"'], environ={'TEST_ENV': 'ad_dc'}) -plantestsuite_loadlist("samba.tests.ldap_upn_sam_account", "ad_dc", +plantestsuite_loadlist("samba.tests.ldap_spn", "ad_dc", + [python, + f"{srcdir()}/python/samba/tests/ldap_spn.py", + '$SERVER', + '-U"$USERNAME%$PASSWORD"', + '--workgroup=$DOMAIN', + '$LOADLIST', '$LISTOPT']) + +plantestsuite_loadlist("samba.tests.ldap_upn_sam_account", "ad_dc_ntvfs", [python, f"{srcdir()}/python/samba/tests/ldap_upn_sam_account.py", '$SERVER', -- 2.25.1 From a216dd4ed696facb2e393aa141a8ab313823a7d9 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 12 Aug 2021 21:53:16 +1200 Subject: [PATCH 130/224] CVE-2020-25722 s4/cracknames: add comment pointing to samldb spn handling These need to stay a little bit in sync. The reverse comment is there. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/cracknames.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source4/dsdb/samdb/cracknames.c b/source4/dsdb/samdb/cracknames.c index 3454cf7dc35a..8344b1f6cf86 100644 --- a/source4/dsdb/samdb/cracknames.c +++ b/source4/dsdb/samdb/cracknames.c @@ -81,6 +81,12 @@ static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(struct ldb_context *ldb_ct const char *alias_from, char **alias_to) { + /* + * Some of the logic of this function is mirrored in find_spn_alias() + * in source4/dsdb.samdb/ldb_modules/samldb.c. If you change this to + * not return the first matched alias, you will need to rethink that + * function too. + */ unsigned int i; int ret; struct ldb_result *res; -- 2.25.1 From 8bd6fd5df5e9fc7327cab65e4191895d320d5d33 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 14:12:25 +1300 Subject: [PATCH 131/224] CVE-2020-25722 s4/dsdb/samldb: add samldb_get_single_valued_attr() helper This takes a string of logic out of samldb_unique_attr_check() that we are going to need in other places, and that would be very tedious to repeat. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 325d97ff3971..a5f4b1dcef1c 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -164,6 +164,55 @@ static int samldb_next_step(struct samldb_ctx *ac) } } +static int samldb_get_single_valued_attr(struct ldb_context *ldb, + struct samldb_ctx *ac, + const char *attr, + const char **value) +{ + /* + * The steps we end up going through to get and check a single valued + * attribute. + */ + struct ldb_message_element *el = NULL; + + *value = NULL; + + el = dsdb_get_single_valued_attr(ac->msg, attr, + ac->req->operation); + if (el == NULL) { + /* we are not affected */ + return LDB_SUCCESS; + } + + if (el->num_values > 1) { + ldb_asprintf_errstring( + ldb, + "samldb: %s has %u values, should be single-valued!", + attr, el->num_values); + return LDB_ERR_CONSTRAINT_VIOLATION; + } else if (el->num_values == 0) { + ldb_asprintf_errstring( + ldb, + "samldb: new value for %s " + "not provided for mandatory, single-valued attribute!", + attr); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + + + if (el->values[0].length == 0) { + ldb_asprintf_errstring( + ldb, + "samldb: %s is of zero length, should have a value!", + attr); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + + *value = (char *)el->values[0].data; + + return LDB_SUCCESS; +} + static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr, const char *attr_conflict, struct ldb_dn *base_dn) -- 2.25.1 From 4c114901188815f5591031287ff2deb1d2f3d0a2 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:16:30 +1300 Subject: [PATCH 132/224] CVE-2020-25722 s4/dsdb/samldb: unique_attr_check uses samldb_get_single_valued_attr() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 36 +++++++------------------ 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index a5f4b1dcef1c..600b789db142 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -219,37 +219,21 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr, { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); const char * const no_attrs[] = { NULL }; - struct ldb_result *res; - const char *enc_str; - struct ldb_message_element *el; + struct ldb_result *res = NULL; + const char *str = NULL; + const char *enc_str = NULL; int ret; - el = dsdb_get_single_valued_attr(ac->msg, attr, - ac->req->operation); - if (el == NULL) { - /* we are not affected */ - return LDB_SUCCESS; - } - - if (el->num_values > 1) { - ldb_asprintf_errstring(ldb, - "samldb: %s has %u values, should be single-valued!", - attr, el->num_values); - return LDB_ERR_CONSTRAINT_VIOLATION; - } else if (el->num_values == 0) { - ldb_asprintf_errstring(ldb, - "samldb: new value for %s not provided for mandatory, single-valued attribute!", - attr); - return LDB_ERR_OBJECT_CLASS_VIOLATION; + ret = samldb_get_single_valued_attr(ldb, ac, attr, &str); + if (ret != LDB_SUCCESS) { + return ret; } - if (el->values[0].length == 0) { - ldb_asprintf_errstring(ldb, - "samldb: %s is of zero length, should have a value!", - attr); - return LDB_ERR_OBJECT_CLASS_VIOLATION; + if (str == NULL) { + /* the attribute wasn't found */ + return LDB_SUCCESS; } - enc_str = ldb_binary_encode(ac, el->values[0]); + enc_str = ldb_binary_encode_string(ac, str); if (enc_str == NULL) { return ldb_module_oom(ac->module); } -- 2.25.1 From 721cc44e0140e4bad417b40049f026527a60e2db Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:17:34 +1300 Subject: [PATCH 133/224] CVE-2020-25722 s4/dsdb/samldb: check for clashes in UPNs/samaccountnames We already know duplicate sAMAccountNames and UserPrincipalNames are bad, but we also have to check against the values these imply in each other. For example, imagine users with SAM account names "Alice" and "Bob" in the realm "example.com". If they do not have explicit UPNs, by the logic of MS-ADTS 5.1.1.1.1 they use the implict UPNs "alice@example.com" and "bob@example.com", respectively. If Bob's UPN gets set to "alice@example.com", it will clash with Alice's implicit one. Therefore we refuse to allow a UPN that implies an existing SAM account name and vice versa. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap_upn_sam_account | 15 -- source4/dsdb/samdb/ldb_modules/samldb.c | 206 +++++++++++++++++++++- 2 files changed, 203 insertions(+), 18 deletions(-) diff --git a/selftest/knownfail.d/ldap_upn_sam_account b/selftest/knownfail.d/ldap_upn_sam_account index c4d494968b2a..e53d566816e5 100644 --- a/selftest/knownfail.d/ldap_upn_sam_account +++ b/selftest/knownfail.d/ldap_upn_sam_account @@ -1,16 +1 @@ samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_SAM_contains_delete -samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_UPN_same_but_for_internal_spaces -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_ends_with_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_first -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_has_at_signs_clashing_upn_second -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_SAM_starts_with_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_clash_on_other_realm -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_ends_with_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_has_no_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_UPN_starts_with_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_add_the_same_upn_to_different_objects -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_after_UPN -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_SAM_before_UPN -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_different_objects_same_UPN_different_case -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_spaces_around_at -samba.tests.ldap_upn_sam_account.+LdapUpnSamTest.test_upn_sam_two_way_clash diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 600b789db142..927e6cbd2bfa 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -238,8 +238,9 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr, return ldb_module_oom(ac->module); } - /* Make sure that attr (eg) "sAMAccountName" is only used once */ - + /* + * No other object should have the attribute with this value. + */ if (attr_conflict != NULL) { ret = dsdb_module_search(ac->module, ac, &res, base_dn, @@ -273,6 +274,193 @@ static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr, return LDB_SUCCESS; } + + +static inline int samldb_sam_account_upn_clash_sub_search( + struct samldb_ctx *ac, + TALLOC_CTX *mem_ctx, + struct ldb_dn *base_dn, + const char *attr, + const char *value, + const char *err_msg + ) +{ + /* + * A very specific helper function for samldb_sam_account_upn_clash(), + * where we end up doing this same thing several times in a row. + */ + const char * const no_attrs[] = { NULL }; + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + struct ldb_result *res = NULL; + int ret; + char *enc_value = ldb_binary_encode_string(ac, value); + if (enc_value == NULL) { + return ldb_module_oom(ac->module); + } + ret = dsdb_module_search(ac->module, mem_ctx, &res, + base_dn, + LDB_SCOPE_SUBTREE, no_attrs, + DSDB_FLAG_NEXT_MODULE, ac->req, + "(%s=%s)", + attr, enc_value); + talloc_free(enc_value); + + if (ret != LDB_SUCCESS) { + return ret; + } else if (res->count > 1) { + return ldb_operr(ldb); + } else if (res->count == 1) { + if (ldb_dn_compare(res->msgs[0]->dn, ac->msg->dn) != 0){ + ldb_asprintf_errstring(ldb, + "samldb: %s '%s' " + "is already in use %s", + attr, value, err_msg); + /* different errors for different attrs */ + if (strcasecmp("userPrincipalName", attr) == 0) { + return LDB_ERR_CONSTRAINT_VIOLATION; + } + return LDB_ERR_ENTRY_ALREADY_EXISTS; + } + } + return LDB_SUCCESS; +} + +static int samldb_sam_account_upn_clash(struct samldb_ctx *ac) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + int ret; + struct ldb_dn *base_dn = ldb_get_default_basedn(ldb); + TALLOC_CTX *tmp_ctx = NULL; + const char *real_sam = NULL; + const char *real_upn = NULL; + char *implied_sam = NULL; + char *implied_upn = NULL; + const char *realm = NULL; + + ret = samldb_get_single_valued_attr(ldb, ac, + "sAMAccountName", + &real_sam); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samldb_get_single_valued_attr(ldb, ac, + "userPrincipalName", + &real_upn); + if (ret != LDB_SUCCESS) { + return ret; + } + if (real_upn == NULL && real_sam == NULL) { + /* Not changing these things, so we're done */ + return LDB_SUCCESS; + } + + tmp_ctx = talloc_new(ac); + realm = samdb_dn_to_dns_domain(tmp_ctx, base_dn); + if (realm == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + if (real_upn != NULL) { + /* + * note we take the last @ in the upn because the first (i.e. + * sAMAccountName equivalent) part can contain @. + * + * It is also OK (per Windows) for a UPN to have zero @s. + */ + char *at = NULL; + char *upn_realm = NULL; + implied_sam = talloc_strdup(tmp_ctx, real_upn); + if (implied_sam == NULL) { + talloc_free(tmp_ctx); + return ldb_module_oom(ac->module); + } + + at = strrchr(implied_sam, '@'); + if (at == NULL) { + /* + * there is no @ in this UPN, so we treat the whole + * thing as a sAMAccountName for the purposes of a + * clash. + */ + DBG_INFO("samldb: userPrincipalName '%s' contains " + "no '@' character\n", implied_sam); + } else { + /* + * Now, this upn only implies a sAMAccountName if the + * realm is our realm. So we need to compare the tail + * of the upn to the realm. + */ + *at = '\0'; + upn_realm = at + 1; + if (strcasecmp(upn_realm, realm) != 0) { + /* implied_sam is not the implied + * sAMAccountName after all, because it is + * from a different realm. */ + TALLOC_FREE(implied_sam); + } + } + } + + if (real_sam != NULL) { + implied_upn = talloc_asprintf(tmp_ctx, "%s@%s", + real_sam, realm); + if (implied_upn == NULL) { + talloc_free(tmp_ctx); + return ldb_module_oom(ac->module); + } + } + + /* + * Now we have all of the actual and implied names, in which to search + * for conflicts. + */ + if (real_sam != NULL) { + ret = samldb_sam_account_upn_clash_sub_search( + ac, tmp_ctx, base_dn, "sAMAccountName", + real_sam, ""); + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + if (implied_upn != NULL) { + ret = samldb_sam_account_upn_clash_sub_search( + ac, tmp_ctx, base_dn, "userPrincipalName", implied_upn, + "(implied by sAMAccountName)"); + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + if (real_upn != NULL) { + ret = samldb_sam_account_upn_clash_sub_search( + ac, tmp_ctx, base_dn, "userPrincipalName", + real_upn, ""); + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + if (implied_sam != NULL) { + ret = samldb_sam_account_upn_clash_sub_search( + ac, tmp_ctx, base_dn, "sAMAccountName", implied_sam, + "(implied by userPrincipalName)"); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +/* This is run during an add or modify */ static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac) { int ret = 0; @@ -306,7 +494,11 @@ static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac) } else if (ret == LDB_ERR_OBJECT_CLASS_VIOLATION) { ret = LDB_ERR_CONSTRAINT_VIOLATION; } + if (ret != LDB_SUCCESS) { + return ret; + } + ret = samldb_sam_account_upn_clash(ac); if (ret != LDB_SUCCESS) { return ret; } @@ -4180,7 +4372,6 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) if (ret != LDB_SUCCESS) { return ret; } - user_account_control = ldb_msg_find_attr_as_uint(res->msgs[0], "userAccountControl", @@ -4204,6 +4395,15 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) } } + el = ldb_msg_find_element(ac->msg, "userPrincipalName"); + if (el != NULL) { + ret = samldb_sam_account_upn_clash(ac); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + } + el = ldb_msg_find_element(ac->msg, "ldapDisplayName"); if (el != NULL) { ret = samldb_schema_ldapdisplayname_valid_check(ac); -- 2.25.1 From 418eff311a6692487d58814b2749676001d74e62 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 15:27:25 +1300 Subject: [PATCH 134/224] CVE-2020-25722 s4/dsdb/samldb: check sAMAccountName for illegal characters This only for the real account name, not the account name implicit in a UPN. It doesn't matter if a UPN implies an illegal sAMAccountName, since that is not going to conflict with a real one. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap_upn_sam_account | 1 - source4/dsdb/samdb/ldb_modules/samldb.c | 58 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) delete mode 100644 selftest/knownfail.d/ldap_upn_sam_account diff --git a/selftest/knownfail.d/ldap_upn_sam_account b/selftest/knownfail.d/ldap_upn_sam_account deleted file mode 100644 index e53d566816e5..000000000000 --- a/selftest/knownfail.d/ldap_upn_sam_account +++ /dev/null @@ -1 +0,0 @@ -samba.tests.ldap_upn_sam_account.+LdapUpnSamSambaOnlyTest.test_upn_sam_SAM_contains_delete diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 927e6cbd2bfa..46bf477186f9 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -325,6 +325,59 @@ static inline int samldb_sam_account_upn_clash_sub_search( return LDB_SUCCESS; } +static int samaccountname_bad_chars_check(struct samldb_ctx *ac, + const char *name) +{ + /* + * The rules here are based on + * + * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx + * + * Windows considers UTF-8 sequences that map to "similar" characters + * (e.g. 'a', 'ā') to be the same sAMAccountName, and we don't. Names + * that are not valid UTF-8 *are* allowed. + * + * Additionally, Samba collapses multiple spaces, and Windows doesn't. + */ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + size_t i; + + for (i = 0; name[i] != '\0'; i++) { + uint8_t c = name[i]; + char *p = NULL; + if (c < 32 || c == 127) { + ldb_asprintf_errstring( + ldb, + "samldb: sAMAccountName contains invalid " + "0x%.2x character\n", c); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + p = strchr("\"[]:;|=+*?<>/\\,", c); + if (p != NULL) { + ldb_asprintf_errstring( + ldb, + "samldb: sAMAccountName contains invalid " + "'%c' character\n", c); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + } + + if (i == 0) { + ldb_asprintf_errstring( + ldb, + "samldb: sAMAccountName is empty\n"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (name[i - 1] == '.') { + ldb_asprintf_errstring( + ldb, + "samldb: sAMAccountName ends with '.'"); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + return LDB_SUCCESS; +} + static int samldb_sam_account_upn_clash(struct samldb_ctx *ac) { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); @@ -424,6 +477,11 @@ static int samldb_sam_account_upn_clash(struct samldb_ctx *ac) talloc_free(tmp_ctx); return ret; } + ret = samaccountname_bad_chars_check(ac, real_sam); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } } if (implied_upn != NULL) { ret = samldb_sam_account_upn_clash_sub_search( -- 2.25.1 From b0a58d8d368bc08869549011880f375690d518ec Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:14:32 +1300 Subject: [PATCH 135/224] CVE-2020-25722 s4/dsdb/samldb: check for SPN uniqueness, including aliases Not only should it not be possible to add a servicePrincipalName that is already present in the domain, it should not be possible to add one that is implied by an entry in sPNMappings, unless the user is adding an alias to another SPN and has rights to alter that one. For example, with the default sPNMappings, cifs/ is an alias pointing to host/, meaning if there is no cifs/example.com SPN, the host/example.com one will be used instead. A user can add the cifs/example.com SPN only if they can also change the host/example.com one (because adding the cifs/ effectively changes the host/). The reverse is refused in all cases, unless they happen to be on the same object. That is, if there is a cifs/example.com SPN, there is no way to add host/example.com elsewhere. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap_spn | 23 - source4/dsdb/samdb/ldb_modules/samldb.c | 585 +++++++++++++++++++++++- 2 files changed, 582 insertions(+), 26 deletions(-) diff --git a/selftest/knownfail.d/ldap_spn b/selftest/knownfail.d/ldap_spn index dc768728658d..b7eb6f30e7af 100644 --- a/selftest/knownfail.d/ldap_spn +++ b/selftest/knownfail.d/ldap_spn @@ -1,26 +1,3 @@ -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_host_first_both_on_user_service_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_host_first_with_service_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_as_admin -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_with_both_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_add_a_conflict_service_first_with_host_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_changing_dNSHostName_after_host -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_mystery_dnsname_clash_www_first -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_host_first_with_partial_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_host_first_with_partial_write_rights_2 -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_sandwich_sufficient_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_just_sufficient_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_all_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_both_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_service_first_with_host_write_rights -samba.tests.ldap_spn.+LdapSpnSambaOnlyTest.test_spn_three_way_conflict_services_first_with_partial_rights -samba.tests.ldap_spn.+LdapSpnTest.test_spn_adding_dNSHostName_after_cifs_with_no_old_dNSHostName -samba.tests.ldap_spn.+LdapSpnTest.test_spn_changing_dNSHostName_after_cifs samba.tests.ldap_spn.+LdapSpnTest.test_spn_dodgy_spns -samba.tests.ldap_spn.+LdapSpnTest.test_spn_empty_part_spns_consecutive_slashes_ samba.tests.ldap_spn.+LdapSpnTest.test_spn_one_part_spns_no_slashes_ -samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_part_nonsense_spns -samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_part_spns -samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_host_first_adding_duplicate -samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_host_first_adding_duplicate_full_rights -samba.tests.ldap_spn.+LdapSpnTest.test_spn_three_way_conflict_services_first_as_admin samba.tests.ldap_spn.+LdapSpnTest.test_spn_too_many_spn_parts diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 46bf477186f9..9270c9137a13 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3388,6 +3388,543 @@ static int samldb_description_check(struct samldb_ctx *ac, bool *modified) return LDB_SUCCESS; } +#define SPN_ALIAS_NONE 0 +#define SPN_ALIAS_LINK 1 +#define SPN_ALIAS_TARGET 2 + +static int find_spn_aliases(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *service_class, + char ***aliases, + size_t *n_aliases, + int *direction) +{ + /* + * If you change the way this works, you should also look at changing + * LDB_lookup_spn_alias() in source4/dsdb/samdb/cracknames.c, which + * does some of the same work. + * + * In particular, note that sPNMappings are resolved on a first come, + * first served basis. For example, if we have + * + * host=ldap,cifs + * foo=ldap + * cifs=host,alerter + * + * then 'ldap', 'cifs', and 'host' will resolve to 'host', and + * 'alerter' will resolve to 'cifs'. + * + * If this resolution method is made more complicated, then the + * cracknames function should also be changed. + */ + size_t i, j; + int ret; + bool ok; + struct ldb_result *res = NULL; + struct ldb_message_element *spnmappings = NULL; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_dn *service_dn = NULL; + + const char *attrs[] = { + "sPNMappings", + NULL + }; + + *direction = SPN_ALIAS_NONE; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + service_dn = ldb_dn_new( + tmp_ctx, ldb, + "CN=Directory Service,CN=Windows NT,CN=Services"); + if (service_dn == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ok = ldb_dn_add_base(service_dn, ldb_get_config_basedn(ldb)); + if (! ok) { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_search(ldb, tmp_ctx, &res, service_dn, LDB_SCOPE_BASE, + attrs, "(objectClass=nTDSService)"); + + if (ret != LDB_SUCCESS || res->count != 1) { + DBG_WARNING("sPNMappings not found.\n"); + talloc_free(tmp_ctx); + return ret; + } + + spnmappings = ldb_msg_find_element(res->msgs[0], "sPNMappings"); + if (spnmappings == NULL || spnmappings->num_values == 0) { + DBG_WARNING("no sPNMappings attribute\n"); + talloc_free(tmp_ctx); + return LDB_ERR_NO_SUCH_OBJECT; + } + *n_aliases = 0; + + for (i = 0; i < spnmappings->num_values; i++) { + char *p = NULL; + char *mapping = talloc_strndup( + tmp_ctx, + (char *)spnmappings->values[i].data, + spnmappings->values[i].length); + if (mapping == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + p = strchr(mapping, '='); + if (p == NULL) { + talloc_free(tmp_ctx); + return LDB_ERR_ALIAS_PROBLEM; + } + p[0] = '\0'; + p++; + + if (strcasecmp(mapping, service_class) == 0) { + /* + * We need to return the reverse aliases for this one. + * + * typically, this means the service_class is "host" + * and the mapping is "host=alerter,appmgmt,cisvc,..", + * so we get "alerter", "appmgmt", etc in the list of + * aliases. + */ + + /* There is one more field than there are commas */ + size_t n = 1; + + for (j = 0; p[j] != '\0'; j++) { + if (p[j] == ',') { + n++; + p[j] = '\0'; + } + } + *aliases = talloc_array(mem_ctx, char*, n); + if (*aliases == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + *n_aliases = n; + talloc_steal(mem_ctx, mapping); + for (j = 0; j < n; j++) { + (*aliases)[j] = p; + p += strlen(p) + 1; + } + talloc_free(tmp_ctx); + *direction = SPN_ALIAS_LINK; + return LDB_SUCCESS; + } + /* + * We need to look along the list to see if service_class is + * there; if so, we return a list of one item (probably "host"). + */ + do { + char *str = p; + p = strchr(p, ','); + if (p != NULL) { + p[0] = '\0'; + p++; + } + if (strcasecmp(str, service_class) == 0) { + *aliases = talloc_array(mem_ctx, char*, 1); + if (*aliases == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + *n_aliases = 1; + (*aliases)[0] = mapping; + talloc_steal(mem_ctx, mapping); + talloc_free(tmp_ctx); + *direction = SPN_ALIAS_TARGET; + return LDB_SUCCESS; + } + } while (p != NULL); + } + DBG_INFO("no sPNMappings alias for '%s'\n", service_class); + talloc_free(tmp_ctx); + *aliases = NULL; + *n_aliases = 0; + return LDB_SUCCESS; +} + + +static int get_spn_dn(struct ldb_context *ldb, + TALLOC_CTX *tmp_ctx, + const char *candidate, + struct ldb_dn **dn) +{ + int ret; + const char *empty_attrs[] = { NULL }; + struct ldb_message *msg = NULL; + struct ldb_dn *base_dn = ldb_get_default_basedn(ldb); + + const char *enc_candidate = NULL; + + *dn = NULL; + + enc_candidate = ldb_binary_encode_string(tmp_ctx, candidate); + if (enc_candidate == NULL) { + return ldb_operr(ldb); + } + + ret = dsdb_search_one(ldb, + tmp_ctx, + &msg, + base_dn, + LDB_SCOPE_SUBTREE, + empty_attrs, + 0, + "(servicePrincipalName=%s)", + enc_candidate); + if (ret != LDB_SUCCESS) { + return ret; + } + *dn = msg->dn; + return LDB_SUCCESS; +} + + +static int check_spn_write_rights(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *spn, + struct ldb_dn *dn) +{ + int ret; + struct ldb_message *msg = NULL; + struct ldb_message_element *del_el = NULL; + struct ldb_message_element *add_el = NULL; + struct ldb_val val = { + .data = discard_const_p(uint8_t, spn), + .length = strlen(spn) + }; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return ldb_oom(ldb); + } + msg->dn = dn; + + ret = ldb_msg_add_empty(msg, + "servicePrincipalName", + LDB_FLAG_MOD_DELETE, + &del_el); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } + ret = ldb_msg_add_empty(msg, + "servicePrincipalName", + LDB_FLAG_MOD_ADD, + &add_el); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } + + del_el->values = talloc_array(msg->elements, struct ldb_val, 1); + if (del_el->values == NULL) { + talloc_free(msg); + return ret; + } + + add_el->values = talloc_array(msg->elements, struct ldb_val, 1); + if (add_el->values == NULL) { + talloc_free(msg); + return ret; + } + + del_el->values[0] = val; + del_el->num_values = 1; + add_el->values[0] = val; + add_el->num_values = 1; + ret = ldb_modify(ldb, msg); + if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + DBG_ERR("hmm I think we're OK, but not sure\n"); + } else if (ret != LDB_SUCCESS) { + DBG_ERR("SPN write rights check failed with %d\n", ret); + talloc_free(msg); + return ret; + } + talloc_free(msg); + return LDB_SUCCESS; +} + + +static int check_spn_alias_collision(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *spn, + struct ldb_dn *target_dn) +{ + int ret; + char *service_class = NULL; + char *spn_tail = NULL; + char *p = NULL; + char **aliases = NULL; + size_t n_aliases = 0; + size_t i, len; + TALLOC_CTX *tmp_ctx = NULL; + const char *target_dnstr = ldb_dn_get_linearized(target_dn); + int link_direction; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + /* + * "dns/example.com/xxx" gives + * service_class = "dns" + * spn_tail = "example.com/xxx" + */ + p = strchr(spn, '/'); + if (p == NULL) { + /* bad SPN */ + talloc_free(tmp_ctx); + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "malformed servicePrincipalName"); + } + len = p - spn; + + service_class = talloc_strndup(tmp_ctx, spn, len); + if (service_class == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + spn_tail = p + 1; + + ret = find_spn_aliases(ldb, + tmp_ctx, + service_class, + &aliases, + &n_aliases, + &link_direction); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + /* + * we have the list of aliases, and now we need to combined them with + * spn_tail and see if we can find the SPN. + */ + for (i = 0; i < n_aliases; i++) { + struct ldb_dn *colliding_dn = NULL; + const char *colliding_dnstr = NULL; + + char *candidate = talloc_asprintf(tmp_ctx, + "%s/%s", + aliases[i], + spn_tail); + if (candidate == NULL) { + talloc_free(tmp_ctx); + return ldb_oom(ldb); + } + + ret = get_spn_dn(ldb, tmp_ctx, candidate, &colliding_dn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DBG_DEBUG("SPN alias '%s' not found (good)\n", + candidate); + talloc_free(candidate); + continue; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("SPN '%s' search error %d\n", candidate, ret); + talloc_free(tmp_ctx); + return ret; + } + + target_dnstr = ldb_dn_get_linearized(target_dn); + /* + * We have found an existing SPN that matches the alias. That + * is OK only if it is on the object we are trying to add to, + * or if the SPN on the other side is a more generic alias for + * this one and we also have rights to modify it. + * + * That is, we can put "host/X" and "cifs/X" on the same + * object, but not on different objects, unless we put the + * host/X on first, and could also change that object when we + * add cifs/X. It is forbidden to add the objects in the other + * order. + * + * The rationale for this is that adding "cifs/X" effectively + * changes "host/X" by diverting traffic. If "host/X" can be + * added after "cifs/X", a sneaky person could get "cifs/X" in + * first, making "host/X" have less effect than intended. + * + * Note: we also can't have "host/X" and "Host/X" on the same + * object, but that is not relevant here. + */ + + ret = ldb_dn_compare(colliding_dn, target_dn); + if (ret != 0) { + colliding_dnstr = ldb_dn_get_linearized(colliding_dn); + DBG_ERR("trying to add SPN '%s' on '%s' when '%s' is " + "on '%s'\n", + spn, + target_dnstr, + candidate, + colliding_dnstr); + + if (link_direction == SPN_ALIAS_LINK) { + /* we don't allow host/X if there is a + * cifs/X */ + talloc_free(tmp_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + ret = check_spn_write_rights(ldb, + tmp_ctx, + candidate, + colliding_dn); + if (ret != LDB_SUCCESS) { + DBG_ERR("SPN '%s' is on '%s' so '%s' can't be " + "added to '%s'\n", + candidate, + colliding_dnstr, + spn, + target_dnstr); + talloc_free(tmp_ctx); + ldb_asprintf_errstring(ldb, + "samldb: spn[%s] would cause a conflict", + spn); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + } else { + DBG_INFO("SPNs '%s' and '%s' alias both on '%s'\n", + candidate, spn, target_dnstr); + } + talloc_free(candidate); + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + +static int check_spn_direct_collision(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *spn, + struct ldb_dn *target_dn) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_dn *colliding_dn = NULL; + const char *target_dnstr = NULL; + const char *colliding_dnstr = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + ret = get_spn_dn(ldb, tmp_ctx, spn, &colliding_dn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DBG_DEBUG("SPN '%s' not found (good)\n", spn); + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("SPN '%s' search error %d\n", spn, ret); + talloc_free(tmp_ctx); + if (ret == LDB_ERR_COMPARE_TRUE) { + /* + * COMPARE_TRUE has special meaning here and we don't + * want to return it by mistake. + */ + ret = LDB_ERR_OPERATIONS_ERROR; + } + return ret; + } + /* + * We have found this exact SPN. This is mostly harmless (depend on + * ADD vs REPLACE) when the spn is being put on the object that + * already has, so we let it through to succeed or fail as some other + * module sees fit. + */ + target_dnstr = ldb_dn_get_linearized(target_dn); + ret = ldb_dn_compare(colliding_dn, target_dn); + if (ret != 0) { + colliding_dnstr = ldb_dn_get_linearized(colliding_dn); + DBG_ERR("SPN '%s' is on '%s' so it can't be " + "added to '%s'\n", + spn, + colliding_dnstr, + target_dnstr); + ldb_asprintf_errstring(ldb, + "samldb: spn[%s] would cause a conflict", + spn); + talloc_free(tmp_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + DBG_INFO("SPN '%s' is already on '%s'\n", + spn, target_dnstr); + talloc_free(tmp_ctx); + return LDB_ERR_COMPARE_TRUE; +} + + +/* Check that "servicePrincipalName" changes do not introduce a collision + * globally. */ +static int samldb_spn_uniqueness_check(struct samldb_ctx *ac, + struct ldb_message_element *spn_el) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + int ret; + const char *spn = NULL; + size_t i; + TALLOC_CTX *tmp_ctx = talloc_new(ac->msg); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + for (i = 0; i < spn_el->num_values; i++) { + spn = (char *)spn_el->values[i].data; + + ret = check_spn_direct_collision(ldb, + tmp_ctx, + spn, + ac->msg->dn); + if (ret == LDB_ERR_COMPARE_TRUE) { + DBG_INFO("SPN %s re-added to the same object\n", spn); + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("SPN %s failed direct uniqueness check\n", spn); + talloc_free(tmp_ctx); + return ret; + } + + ret = check_spn_alias_collision(ldb, + tmp_ctx, + spn, + ac->msg->dn); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* we have no sPNMappings, hence no aliases */ + break; + } + if (ret != LDB_SUCCESS) { + DBG_ERR("SPN %s failed alias uniqueness check\n", spn); + talloc_free(tmp_ctx); + return ret; + } + DBG_INFO("SPN %s seems to be unique\n", spn); + } + + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + + /* This trigger adapts the "servicePrincipalName" attributes if the * "dNSHostName" and/or "sAMAccountName" attribute change(s) */ static int samldb_service_principal_names_change(struct samldb_ctx *ac) @@ -3503,8 +4040,14 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac) return LDB_SUCCESS; } - /* Potential "servicePrincipalName" changes in the same request have to - * be handled before the update (Windows behaviour). */ + /* + * Potential "servicePrincipalName" changes in the same request have + * to be handled before the update (Windows behaviour). + * + * We extract the SPN changes into a new message and run it through + * the stack from this module, so that it subjects them to the SPN + * checks we have here. + */ el = ldb_msg_find_element(ac->msg, "servicePrincipalName"); if (el != NULL) { msg = ldb_msg_new(ac->msg); @@ -3526,7 +4069,7 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac) } while (el != NULL); ret = dsdb_module_modify(ac->module, msg, - DSDB_FLAG_NEXT_MODULE, ac->req); + DSDB_FLAG_OWN_MODULE, ac->req); if (ret != LDB_SUCCESS) { return ret; } @@ -4260,6 +4803,19 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) return samldb_fill_object(ac); } + + el = ldb_msg_find_element(ac->msg, "servicePrincipalName"); + if ((el != NULL)) { + /* + * We need to check whether the SPN collides with an existing + * one (anywhere) including via aliases. + */ + ret = samldb_spn_uniqueness_check(ac, el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (samdb_find_attribute(ldb, ac->msg, "objectclass", "subnet") != NULL) { ret = samldb_verify_subnet(ac, ac->msg->dn); @@ -4510,12 +5066,35 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) el2 = ldb_msg_find_element(ac->msg, "sAMAccountName"); if ((el != NULL) || (el2 != NULL)) { modified = true; + /* + * samldb_service_principal_names_change() might add SPN + * changes to the request, so this must come before the SPN + * uniqueness check below. + * + * Note we ALSO have to do the SPN uniqueness check inside + * samldb_service_principal_names_change(), because it does a + * subrequest to do requested SPN modifications *before* its + * automatic ones are added. + */ ret = samldb_service_principal_names_change(ac); if (ret != LDB_SUCCESS) { return ret; } } + el = ldb_msg_find_element(ac->msg, "servicePrincipalName"); + if ((el != NULL)) { + /* + * We need to check whether the SPN collides with an existing + * one (anywhere) including via aliases. + */ + modified = true; + ret = samldb_spn_uniqueness_check(ac, el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + el = ldb_msg_find_element(ac->msg, "fSMORoleOwner"); if (el != NULL) { ret = samldb_fsmo_role_owner_check(ac); -- 2.25.1 From bacfbeb1b3ee030805d4d283ab05638dbaab6bed Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 16:03:18 +1300 Subject: [PATCH 136/224] CVE-2020-25722 s4/dsdb/samldb: reject SPN with too few/many components BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap_spn | 2 -- source4/dsdb/samdb/ldb_modules/samldb.c | 41 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/selftest/knownfail.d/ldap_spn b/selftest/knownfail.d/ldap_spn index b7eb6f30e7af..63f9fe02ef7d 100644 --- a/selftest/knownfail.d/ldap_spn +++ b/selftest/knownfail.d/ldap_spn @@ -1,3 +1 @@ samba.tests.ldap_spn.+LdapSpnTest.test_spn_dodgy_spns -samba.tests.ldap_spn.+LdapSpnTest.test_spn_one_part_spns_no_slashes_ -samba.tests.ldap_spn.+LdapSpnTest.test_spn_too_many_spn_parts diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 9270c9137a13..27f1ae285211 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3870,6 +3870,37 @@ static int check_spn_direct_collision(struct ldb_context *ldb, } +static int count_spn_components(struct ldb_val val) +{ + /* + * a 3 part servicePrincipalName has two slashes, like + * ldap/example.com/DomainDNSZones.example.com. + * + * In krb5_parse_name_flags() we don't count "\/" as a slash (i.e. + * escaped by a backslash), but this is not the behaviour of Windows + * on setting a servicePrincipalName -- slashes are counted regardless + * of backslashes. + * + * Accordingly, here we ignore backslashes. This will reject + * multi-slash SPNs that krb5_parse_name_flags() would accept, and + * allow ones in the form "a\/b" that it won't parse. + */ + size_t i; + int slashes = 0; + for (i = 0; i < val.length; i++) { + char c = val.data[i]; + if (c == '/') { + slashes++; + if (slashes == 3) { + /* at this point we don't care */ + return 4; + } + } + } + return slashes + 1; +} + + /* Check that "servicePrincipalName" changes do not introduce a collision * globally. */ static int samldb_spn_uniqueness_check(struct samldb_ctx *ac, @@ -3885,8 +3916,18 @@ static int samldb_spn_uniqueness_check(struct samldb_ctx *ac, } for (i = 0; i < spn_el->num_values; i++) { + int n_components; spn = (char *)spn_el->values[i].data; + n_components = count_spn_components(spn_el->values[i]); + if (n_components > 3 || n_components < 2) { + ldb_asprintf_errstring(ldb, + "samldb: spn[%s] invalid with %u components", + spn, n_components); + talloc_free(tmp_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + ret = check_spn_direct_collision(ldb, tmp_ctx, spn, -- 2.25.1 From 75a8f4b995206f83554d9b329879d951e5ec6cca Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:09:21 +1300 Subject: [PATCH 137/224] CVE-2020-25722 s4/dsdb modules: add dsdb_get_expected_new_values() This function collects a superset of all the new values for the specified attribute that could result from an ldb add or modify message. In most cases -- where there is a single add or modify -- the exact set of added values is returned, and this is done reasonably efficiently using the existing element. Where it gets complicated is when there are multiple elements for the same attribute in a message. Anything added before a replace or delete will be included in these results but may not end up in the database if the message runs its course. Examples: sequence result 1. ADD the element is returned (exact) 2. REPLACE the element is returned (exact) 3. ADD, ADD both elements are concatenated together (exact) 4. ADD, REPLACE both elements are concatenated together (superset) 5. REPLACE, ADD both elements are concatenated together (exact) 6. ADD, DEL, ADD adds are concatenated together (superset) 7. REPLACE, REPLACE both concatenated (superset) 8. DEL, ADD last element is returned (exact) Why this? In the past we have treated dsdb_get_single_valued_attr() as if it returned the complete set of possible database changes, when in fact it only returned the last non-delete. That is, it could have missed values in examples 3-7 above. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/util.c | 121 ++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c index 0e8b72233f44..b10177775a80 100644 --- a/source4/dsdb/samdb/ldb_modules/util.c +++ b/source4/dsdb/samdb/ldb_modules/util.c @@ -1443,6 +1443,127 @@ void dsdb_req_chain_debug(struct ldb_request *req, int level) talloc_free(s); } +/* + * Get all the values that *might* be added by an ldb message, as a composite + * ldb element. + * + * This is useful when we need to check all the possible values against some + * criteria. + * + * In cases where a modify message mixes multiple ADDs, DELETEs, and REPLACES, + * the returned element might contain more values than would actually end up + * in the database if the message was run to its conclusion. + * + * If the operation is not LDB_ADD or LDB_MODIFY, an operations error is + * returned. + * + * The returned element might not be new, and should not be modified or freed + * before the message is finished. + */ + +int dsdb_get_expected_new_values(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + const char *attr_name, + struct ldb_message_element **el, + enum ldb_request_type operation) +{ + unsigned int i; + unsigned int el_count = 0; + unsigned int val_count = 0; + struct ldb_val *v = NULL; + struct ldb_message_element *_el = NULL; + *el = NULL; + + if (operation != LDB_ADD && operation != LDB_MODIFY) { + DBG_ERR("inapplicable operation type: %d\n", operation); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* count the adding or replacing elements */ + for (i = 0; i < msg->num_elements; i++) { + if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) { + unsigned int tmp; + if ((operation == LDB_MODIFY) && + (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) + == LDB_FLAG_MOD_DELETE)) { + continue; + } + el_count++; + tmp = val_count + msg->elements[i].num_values; + if (unlikely(tmp < val_count)) { + DBG_ERR("too many values for one element!"); + return LDB_ERR_OPERATIONS_ERROR; + } + val_count = tmp; + } + } + if (el_count == 0) { + /* nothing to see here */ + return LDB_SUCCESS; + } + + if (el_count == 1 || val_count == 0) { + /* + * There is one effective element, which we can return as-is, + * OR there are only elements with zero values -- any of which + * will do. + */ + for (i = 0; i < msg->num_elements; i++) { + if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) { + if ((operation == LDB_MODIFY) && + (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) + == LDB_FLAG_MOD_DELETE)) { + continue; + } + *el = &msg->elements[i]; + return LDB_SUCCESS; + } + } + } + + _el = talloc_zero(mem_ctx, struct ldb_message_element); + if (_el == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + _el->name = attr_name; + + if (val_count == 0) { + /* + * Seems unlikely, but sometimes we might be adding zero + * values in multiple separate elements. The talloc zero has + * already set the expected values = NULL, num_values = 0. + */ + *el = _el; + return LDB_SUCCESS; + } + + _el->values = talloc_array(_el, struct ldb_val, val_count); + if (_el->values == NULL) { + talloc_free(_el); + return LDB_ERR_OPERATIONS_ERROR; + } + _el->num_values = val_count; + + v = _el->values; + + for (i = 0; i < val_count; i++) { + if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) { + if ((operation == LDB_MODIFY) && + (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) + == LDB_FLAG_MOD_DELETE)) { + continue; + } + memcpy(v, + msg->elements[i].values, + msg->elements[i].num_values); + v += msg->elements[i].num_values; + } + } + + *el = _el; + return LDB_SUCCESS; +} + /* * Gets back a single-valued attribute by the rules of the DSDB triggers when * performing a modify operation. -- 2.25.1 From 24b2463397cd3ac3cede721ded2b425fa3a71b5b Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:10:44 +1300 Subject: [PATCH 138/224] CVE-2020-25722 s4/dsdb/samldb: samldb_get_single_valued_attr() check all values using dsdb_get_expected_new_values(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 27f1ae285211..8de6c4974ab2 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -174,11 +174,19 @@ static int samldb_get_single_valued_attr(struct ldb_context *ldb, * attribute. */ struct ldb_message_element *el = NULL; + int ret; *value = NULL; - el = dsdb_get_single_valued_attr(ac->msg, attr, - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + attr, + &el, + ac->req->operation); + + if (ret != LDB_SUCCESS) { + return ret; + } if (el == NULL) { /* we are not affected */ return LDB_SUCCESS; -- 2.25.1 From 54f02cb2e802ecb1a4e569dc61db0fd9a555e2a0 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 14:52:49 +1300 Subject: [PATCH 139/224] CVE-2020-25722 s4/dsdb/samldb: samldb_sam_accountname_valid_check() check all values Using dsdb_get_expected_new_values(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 8de6c4974ab2..8222c0c95c62 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -533,8 +533,17 @@ static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac) bool is_admin; struct security_token *user_token = NULL; struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - struct ldb_message_element *el = dsdb_get_single_valued_attr(ac->msg, "samAccountName", - ac->req->operation); + struct ldb_message_element *el = NULL; + + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "samAccountName", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL || el->num_values == 0) { ldb_asprintf_errstring(ldb, "%08X: samldb: 'samAccountName' can't be deleted/empty!", -- 2.25.1 From 10475ee538ff308a3725c775c0d486337419b97b Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:12:49 +1300 Subject: [PATCH 140/224] CVE-2020-25722 s4/dsdb/samldb: samldb_schema_add_handle_linkid() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 8222c0c95c62..490a4d3598d4 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -742,8 +742,15 @@ static int samldb_schema_add_handle_linkid(struct samldb_ctx *ac) schema = dsdb_get_schema(ldb, ac); schema_dn = ldb_get_schema_basedn(ldb); - el = dsdb_get_single_valued_attr(ac->msg, "linkID", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "linkID", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL) { return LDB_SUCCESS; } -- 2.25.1 From 6a8b03462785178c52fe286c6df855a4e9eb04af Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:13:35 +1300 Subject: [PATCH 141/224] CVE-2020-25722 s4/dsdb/samldb: samldb_schema_add_handle_mapiid() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 490a4d3598d4..c67b0dba881a 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -910,8 +910,15 @@ static int samldb_schema_add_handle_mapiid(struct samldb_ctx *ac) schema = dsdb_get_schema(ldb, ac); schema_dn = ldb_get_schema_basedn(ldb); - el = dsdb_get_single_valued_attr(ac->msg, "mAPIID", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "mAPIID", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL) { return LDB_SUCCESS; } -- 2.25.1 From 1f904b6eb000aaeaf5a1f18dba721e943cadcea3 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:14:05 +1300 Subject: [PATCH 142/224] CVE-2020-25722 s4/dsdb/samldb: samldb_prim_group_change() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index c67b0dba881a..bf65a374d3fa 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2124,8 +2124,15 @@ static int samldb_prim_group_change(struct samldb_ctx *ac) int ret; const char * const noattrs[] = { NULL }; - el = dsdb_get_single_valued_attr(ac->msg, "primaryGroupID", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "primaryGroupID", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL) { /* we are not affected */ return LDB_SUCCESS; -- 2.25.1 From b704f4b984ca16a4570b81e69cd6731d90939649 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:15:00 +1300 Subject: [PATCH 143/224] CVE-2020-25722 s4/dsdb/samldb: samldb_user_account_control_change() checks all values There is another call to dsdb_get_expected_new_values() in this function that we change in the next commit. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index bf65a374d3fa..85d5a891c061 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2812,8 +2812,15 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) bool old_is_critical = false; bool new_is_critical = false; - el = dsdb_get_single_valued_attr(ac->msg, "userAccountControl", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "userAccountControl", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL || el->num_values == 0) { ldb_asprintf_errstring(ldb, "%08X: samldb: 'userAccountControl' can't be deleted!", -- 2.25.1 From 9ca860161d90053c3fce6a9a744896c9224bdf57 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:15:43 +1300 Subject: [PATCH 144/224] CVE-2020-25722 s4/dsdb/samldb _user_account_control_change() always add final value dsdb_get_single_valued_attr() was finding the last non-delete element for userAccountControl and changing its value to the computed value. Unfortunately, the last non-delete element might not be the last element, and a subsequent delete might remove it. Instead we just add a replace on the end. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 85d5a891c061..e29141e0c1dc 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3013,9 +3013,12 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) return ldb_module_oom(ac->module); } - /* Overwrite "userAccountControl" correctly */ - el = dsdb_get_single_valued_attr(ac->msg, "userAccountControl", - ac->req->operation); + ret = ldb_msg_add_empty(ac->msg, + "userAccountControl", + LDB_FLAG_MOD_REPLACE, + &el); + el->values = talloc(ac->msg, struct ldb_val); + el->num_values = 1; el->values[0].data = (uint8_t *) tempstr; el->values[0].length = strlen(tempstr); } else { -- 2.25.1 From 882e5fbe4f9f0c4a876a42daf79ea71e2dedddd2 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:16:34 +1300 Subject: [PATCH 145/224] CVE-2020-25722 s4/dsdb/samldb: samldb_pwd_last_set_change() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index e29141e0c1dc..c5e720ffcac0 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3107,8 +3107,15 @@ static int samldb_pwd_last_set_change(struct samldb_ctx *ac) NULL }; - el = dsdb_get_single_valued_attr(ac->msg, "pwdLastSet", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "pwdLastSet", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL || el->num_values == 0) { ldb_asprintf_errstring(ldb, "%08X: samldb: 'pwdLastSet' can't be deleted!", -- 2.25.1 From 11a4a48fddb17e3e51d5e6ee186f45d6d45566df Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:17:31 +1300 Subject: [PATCH 146/224] CVE-2020-25722 s4/dsdb/samldb: samldb_lockout_time() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index c5e720ffcac0..13518471fe94 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3169,8 +3169,15 @@ static int samldb_lockout_time(struct samldb_ctx *ac) struct ldb_message *tmp_msg; int ret; - el = dsdb_get_single_valued_attr(ac->msg, "lockoutTime", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "lockoutTime", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL || el->num_values == 0) { ldb_asprintf_errstring(ldb, "%08X: samldb: 'lockoutTime' can't be deleted!", -- 2.25.1 From 57cdbf0bfc037069c8603d2528c1ea42aea261c4 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:17:50 +1300 Subject: [PATCH 147/224] CVE-2020-25722 s4/dsdb/samldb: samldb_group_type_change() checks all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 13518471fe94..3e9b713114aa 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3226,8 +3226,15 @@ static int samldb_group_type_change(struct samldb_ctx *ac) struct ldb_result *res; const char * const attrs[] = { "groupType", NULL }; - el = dsdb_get_single_valued_attr(ac->msg, "groupType", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "groupType", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + if (el == NULL) { /* we are not affected */ return LDB_SUCCESS; -- 2.25.1 From 49f8b1906d2763e4190fd87a70373de160ef6432 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:18:10 +1300 Subject: [PATCH 148/224] CVE-2020-25722 s4/dsdb/samldb: samldb_service_principal_names_change checks values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 3e9b713114aa..8564d89865c5 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4049,10 +4049,22 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac) unsigned int i, j; int ret; - el = dsdb_get_single_valued_attr(ac->msg, "dNSHostName", - ac->req->operation); - el2 = dsdb_get_single_valued_attr(ac->msg, "sAMAccountName", - ac->req->operation); + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "dNSHostName", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "sAMAccountName", + &el2, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } if ((el == NULL) && (el2 == NULL)) { /* we are not affected */ return LDB_SUCCESS; -- 2.25.1 From 791fa3154046f29c0f51289997806513a2ac99f0 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:18:21 +1300 Subject: [PATCH 149/224] CVE-2020-25722 s4/dsdb/samldb: samldb_fsmo_role_owner_check checks values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 8564d89865c5..3e0949fc0656 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4304,9 +4304,15 @@ static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac) struct ldb_dn *res_dn; struct ldb_result *res; int ret; + ret = dsdb_get_expected_new_values(ac, + ac->msg, + "fSMORoleOwner", + &el, + ac->req->operation); + if (ret != LDB_SUCCESS) { + return ret; + } - el = dsdb_get_single_valued_attr(ac->msg, "fSMORoleOwner", - ac->req->operation); if (el == NULL) { /* we are not affected */ return LDB_SUCCESS; -- 2.25.1 From c144d957aef3ffef75dfb5578cd11444eca5dded Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 21 Oct 2021 12:52:07 +1300 Subject: [PATCH 150/224] CVE-2020-25722 s4/dsdb/samldb: samldb_fsmo_role_owner_check() wants one value BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/samldb.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 3e0949fc0656..1ecd44b4b11a 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4317,6 +4317,9 @@ static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac) /* we are not affected */ return LDB_SUCCESS; } + if (el->num_values != 1) { + goto choose_error_code; + } /* Create a temporary message for fetching the "fSMORoleOwner" */ tmp_msg = ldb_msg_new(ac->msg); @@ -4333,11 +4336,7 @@ static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac) if (res_dn == NULL) { ldb_set_errstring(ldb, "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!"); - if (ac->req->operation == LDB_ADD) { - return LDB_ERR_CONSTRAINT_VIOLATION; - } else { - return LDB_ERR_UNWILLING_TO_PERFORM; - } + goto choose_error_code; } /* Fetched DN has to reference a "nTDSDSA" entry */ @@ -4357,6 +4356,14 @@ static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac) talloc_free(res); return LDB_SUCCESS; + +choose_error_code: + /* this is just how it is */ + if (ac->req->operation == LDB_ADD) { + return LDB_ERR_CONSTRAINT_VIOLATION; + } else { + return LDB_ERR_UNWILLING_TO_PERFORM; + } } /* -- 2.25.1 From 8f5d5c7f25dcbd2f10fba20a89f9b41a200c06b7 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:19:42 +1300 Subject: [PATCH 151/224] CVE-2020-25722 s4/dsdb/pwd_hash: password_hash_bypass gets all values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- .../dsdb/samdb/ldb_modules/password_hash.c | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index fce3b27db9fc..fca4f267ead3 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -204,6 +204,7 @@ static int password_hash_bypass(struct ldb_module *module, struct ldb_request *r struct ldb_message_element *nthe; struct ldb_message_element *lmhe; struct ldb_message_element *sce; + int ret; switch (request->operation) { case LDB_ADD: @@ -217,17 +218,26 @@ static int password_hash_bypass(struct ldb_module *module, struct ldb_request *r } /* nobody must touch password histories and 'supplementalCredentials' */ - nte = dsdb_get_single_valued_attr(msg, "unicodePwd", - request->operation); - lme = dsdb_get_single_valued_attr(msg, "dBCSPwd", - request->operation); - nthe = dsdb_get_single_valued_attr(msg, "ntPwdHistory", - request->operation); - lmhe = dsdb_get_single_valued_attr(msg, "lmPwdHistory", - request->operation); - sce = dsdb_get_single_valued_attr(msg, "supplementalCredentials", - request->operation); +#define GET_VALUES(el, attr) do { \ + ret = dsdb_get_expected_new_values(request, \ + msg, \ + attr, \ + &el, \ + request->operation); \ + \ + if (ret != LDB_SUCCESS) { \ + return ret; \ + } \ +} while(0) + + GET_VALUES(nte, "unicodePwd"); + GET_VALUES(lme, "dBCSPwd"); + GET_VALUES(nthe, "ntPwdHistory"); + GET_VALUES(lmhe, "lmPwdHistory"); + GET_VALUES(sce, "supplementalCredentials"); + +#undef GET_VALUES #define CHECK_HASH_ELEMENT(e, min, max) do {\ if (e && e->num_values) { \ unsigned int _count; \ -- 2.25.1 From dc666c7a77c1dd2ac989c93062f720ea9455ea9d Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:20:54 +1300 Subject: [PATCH 152/224] CVE-2020-25722 s4/dsdb/pwd_hash: rework pwdLastSet bypass This tightens the logic a bit, in that a message with trailing DELETE elements is no longer accepted when the bypass flag is set. In any case this is an unlikely scenario as this is an internal flag set by a private control in pdb_samba_dsdb_replace_by_sam(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- .../dsdb/samdb/ldb_modules/password_hash.c | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index fca4f267ead3..0e08f4889ee0 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -2230,23 +2230,31 @@ static int setup_last_set_field(struct setup_password_fields_io *io) } if (io->ac->pwd_last_set_bypass) { - struct ldb_message_element *el1 = NULL; - struct ldb_message_element *el2 = NULL; - + struct ldb_message_element *el = NULL; + size_t i; + size_t count = 0; + /* + * This is a message from pdb_samba_dsdb_replace_by_sam() + * + * We want to ensure there is only one pwdLastSet element, and + * it isn't deleting. + */ if (msg == NULL) { return LDB_ERR_CONSTRAINT_VIOLATION; } - el1 = dsdb_get_single_valued_attr(msg, "pwdLastSet", - io->ac->req->operation); - if (el1 == NULL) { - return LDB_ERR_CONSTRAINT_VIOLATION; + for (i = 0; i < msg->num_elements; i++) { + if (ldb_attr_cmp(msg->elements[i].name, + "pwdLastSet") == 0) { + count++; + el = &msg->elements[i]; + } } - el2 = ldb_msg_find_element(msg, "pwdLastSet"); - if (el2 == NULL) { + if (count != 1) { return LDB_ERR_CONSTRAINT_VIOLATION; } - if (el1 != el2) { + + if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) { return LDB_ERR_CONSTRAINT_VIOLATION; } -- 2.25.1 From 030e66609f7df2da95db4b7c9d7db231a24bc5fc Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 21 Oct 2021 13:49:28 +1300 Subject: [PATCH 153/224] CVE-2020-25722 s4/dsdb/util: remove unused dsdb_get_single_valued_attr() Nobody uses it now. It never really did what it said it did. Almost every use was wrong. It was a trap. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/util.c | 34 --------------------------- 1 file changed, 34 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c index b10177775a80..405febf0b3d0 100644 --- a/source4/dsdb/samdb/ldb_modules/util.c +++ b/source4/dsdb/samdb/ldb_modules/util.c @@ -1564,40 +1564,6 @@ int dsdb_get_expected_new_values(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } -/* - * Gets back a single-valued attribute by the rules of the DSDB triggers when - * performing a modify operation. - * - * In order that the constraint checking by the "objectclass_attrs" LDB module - * does work properly, the change request should remain similar or only be - * enhanced (no other modifications as deletions, variations). - */ -struct ldb_message_element *dsdb_get_single_valued_attr(const struct ldb_message *msg, - const char *attr_name, - enum ldb_request_type operation) -{ - struct ldb_message_element *el = NULL; - unsigned int i; - - /* We've to walk over all modification entries and consider the last - * non-delete one which belongs to "attr_name". - * - * If "el" is NULL afterwards then that means there was no interesting - * change entry. */ - for (i = 0; i < msg->num_elements; i++) { - if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) { - if ((operation == LDB_MODIFY) && - (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) - == LDB_FLAG_MOD_DELETE)) { - continue; - } - el = &msg->elements[i]; - } - } - - return el; -} - /* * This function determines the (last) structural or 88 object class of a passed * "objectClass" attribute - per MS-ADTS 3.1.1.1.4 this is the last value. -- 2.25.1 From bace40e0873d36a1374b987e413534de687fbc90 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 12:20:49 +1300 Subject: [PATCH 154/224] CVE-2020-25722 selftest: Adapt ldap.py tests to new objectClass restrictions BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap | 1 + source4/dsdb/tests/python/ldap.py | 36 +++++++++++++++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/selftest/knownfail.d/ldap b/selftest/knownfail.d/ldap index 0331d3687d41..545dc93db8e7 100644 --- a/selftest/knownfail.d/ldap +++ b/selftest/knownfail.d/ldap @@ -1,3 +1,4 @@ # the attributes too long test returns the wrong error ^samba4.ldap.python.+test_attribute_ranges_too_long samba4.ldap.python\(ad_dc_default\).*__main__.BasicTests.test_ldapSearchNoAttributes +^samba4.ldap.python.+test_objectclasses diff --git a/source4/dsdb/tests/python/ldap.py b/source4/dsdb/tests/python/ldap.py index 161c2f8b3c22..cd4e82cedf62 100755 --- a/source4/dsdb/tests/python/ldap.py +++ b/source4/dsdb/tests/python/ldap.py @@ -436,33 +436,41 @@ class BasicTests(samba.tests.TestCase): (num, _) = e.args self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) - # Add a new top-most structural class "inetOrgPerson" and remove it - # afterwards + # Try to add a new top-most structural class "inetOrgPerson" m = Message() m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) m["objectClass"] = MessageElement("inetOrgPerson", FLAG_MOD_ADD, "objectClass") - ldb.modify(m) + try: + ldb.modify(m) + self.fail() + except LdbError as e: + (num, _) = e.args + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) + # Try to remove the structural class "user" m = Message() m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - m["objectClass"] = MessageElement("inetOrgPerson", FLAG_MOD_DELETE, + m["objectClass"] = MessageElement("user", FLAG_MOD_DELETE, "objectClass") - ldb.modify(m) + try: + ldb.modify(m) + self.fail() + except LdbError as e: + (num, _) = e.args + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) - # Replace top-most structural class to "inetOrgPerson" and reset it - # back to "user" + # Try to replace top-most structural class to "inetOrgPerson" m = Message() m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) m["objectClass"] = MessageElement("inetOrgPerson", FLAG_MOD_REPLACE, "objectClass") - ldb.modify(m) - - m = Message() - m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - m["objectClass"] = MessageElement("user", FLAG_MOD_REPLACE, - "objectClass") - ldb.modify(m) + try: + ldb.modify(m) + self.fail() + except LdbError as e: + (num, _) = e.args + self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION) # Add a new auxiliary object class "posixAccount" to "ldaptestuser" m = Message() -- 2.25.1 From 11725c9c9ed173c107f0817baeee670fedc6a827 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:56:10 +1300 Subject: [PATCH 155/224] CVE-2020-25718 tests/krb5: Fix indentation BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 5313dbc6045f..480e3d8264d0 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -667,7 +667,7 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds(replication_allowed=True, revealed_to_rodc=True) existing_rid = self._get_existing_rid(replication_allowed=True, - revealed_to_rodc=True) + revealed_to_rodc=True) tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) @@ -675,7 +675,7 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds(replication_allowed=True, revealed_to_rodc=True) existing_rid = self._get_existing_rid(replication_allowed=True, - revealed_to_rodc=True) + revealed_to_rodc=True) tgt = self._get_tgt(creds, renewable=True, from_rodc=True, new_rid=existing_rid) self._renew_tgt(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) @@ -693,7 +693,7 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds(replication_allowed=True, revealed_to_rodc=True) existing_rid = self._get_existing_rid(replication_allowed=True, - revealed_to_rodc=True) + revealed_to_rodc=True) tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) self._s4u2self(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) @@ -701,7 +701,7 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds(replication_allowed=True, revealed_to_rodc=True) existing_rid = self._get_existing_rid(replication_allowed=True, - revealed_to_rodc=True) + revealed_to_rodc=True) tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid) self._user2user(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) -- 2.25.1 From 495d5e983ce448eac304db129e4771593833c449 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:33:38 +1300 Subject: [PATCH 156/224] CVE-2020-25719 krb5pac.idl: Add PAC_ATTRIBUTES_INFO PAC buffer type BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- librpc/idl/krb5pac.idl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/librpc/idl/krb5pac.idl b/librpc/idl/krb5pac.idl index ed488dee4251..11e227026f62 100644 --- a/librpc/idl/krb5pac.idl +++ b/librpc/idl/krb5pac.idl @@ -111,6 +111,16 @@ interface krb5pac [switch_is(flags & PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID)] PAC_UPN_DNS_INFO_EX ex; } PAC_UPN_DNS_INFO; + typedef [bitmap32bit] bitmap { + PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED = 0x00000001, + PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY = 0x00000002 + } PAC_ATTRIBUTE_INFO_FLAGS; + + typedef struct { + uint32 flags_length; /* length in bits */ + PAC_ATTRIBUTE_INFO_FLAGS flags; + } PAC_ATTRIBUTES_INFO; + typedef [public] struct { PAC_LOGON_INFO *info; } PAC_LOGON_INFO_CTR; @@ -130,7 +140,8 @@ interface krb5pac PAC_TYPE_CLIENT_CLAIMS_INFO = 13, PAC_TYPE_DEVICE_INFO = 14, PAC_TYPE_DEVICE_CLAIMS_INFO = 15, - PAC_TYPE_TICKET_CHECKSUM = 16 + PAC_TYPE_TICKET_CHECKSUM = 16, + PAC_TYPE_ATTRIBUTES_INFO = 17 } PAC_TYPE; typedef struct { @@ -147,6 +158,7 @@ interface krb5pac PAC_CONSTRAINED_DELEGATION_CTR constrained_delegation; [case(PAC_TYPE_UPN_DNS_INFO)] PAC_UPN_DNS_INFO upn_dns_info; [case(PAC_TYPE_TICKET_CHECKSUM)] PAC_SIGNATURE_DATA ticket_checksum; + [case(PAC_TYPE_ATTRIBUTES_INFO)] PAC_ATTRIBUTES_INFO attributes_info; /* when new PAC info types are added they are supposed to be done in such a way that they are backwards compatible with existing servers. This makes it safe to just use a [default] for -- 2.25.1 From 724273fc535915c2c94dd444d779da1e2a0f4ee2 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:33:49 +1300 Subject: [PATCH 157/224] CVE-2020-25719 krb5pac.idl: Add PAC_REQUESTER_SID PAC buffer type BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- librpc/idl/krb5pac.idl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/librpc/idl/krb5pac.idl b/librpc/idl/krb5pac.idl index 11e227026f62..bbe4a253e3a2 100644 --- a/librpc/idl/krb5pac.idl +++ b/librpc/idl/krb5pac.idl @@ -121,6 +121,10 @@ interface krb5pac PAC_ATTRIBUTE_INFO_FLAGS flags; } PAC_ATTRIBUTES_INFO; + typedef struct { + dom_sid sid; + } PAC_REQUESTER_SID; + typedef [public] struct { PAC_LOGON_INFO *info; } PAC_LOGON_INFO_CTR; @@ -141,7 +145,8 @@ interface krb5pac PAC_TYPE_DEVICE_INFO = 14, PAC_TYPE_DEVICE_CLAIMS_INFO = 15, PAC_TYPE_TICKET_CHECKSUM = 16, - PAC_TYPE_ATTRIBUTES_INFO = 17 + PAC_TYPE_ATTRIBUTES_INFO = 17, + PAC_TYPE_REQUESTER_SID = 18 } PAC_TYPE; typedef struct { @@ -159,6 +164,7 @@ interface krb5pac [case(PAC_TYPE_UPN_DNS_INFO)] PAC_UPN_DNS_INFO upn_dns_info; [case(PAC_TYPE_TICKET_CHECKSUM)] PAC_SIGNATURE_DATA ticket_checksum; [case(PAC_TYPE_ATTRIBUTES_INFO)] PAC_ATTRIBUTES_INFO attributes_info; + [case(PAC_TYPE_REQUESTER_SID)] PAC_REQUESTER_SID requester_sid; /* when new PAC info types are added they are supposed to be done in such a way that they are backwards compatible with existing servers. This makes it safe to just use a [default] for -- 2.25.1 From eb4d8a15de3c8a0c1b84c51e2e39b9ff15d328ec Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:44:45 +1300 Subject: [PATCH 158/224] CVE-2020-25719 tests/krb5: Provide expected parameters for both AS-REQs in get_tgt() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 9be6cbab30bc..6d6dcc216075 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1389,6 +1389,8 @@ class KDCBaseTest(RawKerberosTest): ticket_decryption_key = ( self.TicketDecryptionKey_from_creds(krbtgt_creds)) + expected_etypes = krbtgt_creds.tgs_supported_enctypes + if kdc_options is None: kdc_options = ('forwardable,' 'renewable,' @@ -1415,6 +1417,7 @@ class KDCBaseTest(RawKerberosTest): expected_salt=salt, expected_flags=expected_flags, unexpected_flags=unexpected_flags, + expected_supported_etypes=expected_etypes, etypes=etype, padata=None, kdc_options=kdc_options, @@ -1422,6 +1425,7 @@ class KDCBaseTest(RawKerberosTest): ticket_decryption_key=ticket_decryption_key, pac_request=pac_request, pac_options=pac_options, + expect_pac=expect_pac, to_rodc=to_rodc) self.check_pre_authentication(rep) @@ -1440,8 +1444,6 @@ class KDCBaseTest(RawKerberosTest): expected_sname = self.PrincipalName_create( name_type=NT_SRV_INST, names=['krbtgt', realm.upper()]) - expected_etypes = krbtgt_creds.tgs_supported_enctypes - rep, kdc_exchange_dict = self._test_as_exchange( cname=cname, realm=realm, @@ -1453,6 +1455,9 @@ class KDCBaseTest(RawKerberosTest): expected_cname=cname, expected_srealm=expected_realm, expected_sname=expected_sname, + expected_account_name=expected_account_name, + expected_upn_name=expected_upn_name, + expected_sid=expected_sid, expected_salt=salt, expected_flags=expected_flags, unexpected_flags=unexpected_flags, -- 2.25.1 From 5d74992b3ad27c2e45d1b3d9fb2b69aaa0903708 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:47:53 +1300 Subject: [PATCH 159/224] CVE-2020-25719 tests/krb5: Allow update_pac_checksums=True if the PAC is not present BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 18ee8738eaa5..39ca4a69e1ca 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -3293,7 +3293,7 @@ class RawKerberosTest(TestCaseInTempDir): self.assertFalse(checksum_keys) self.assertFalse(include_checksums) - expect_pac = update_pac_checksums or modify_pac_fn is not None + expect_pac = modify_pac_fn is not None key = ticket.decryption_key -- 2.25.1 From e22111ae47f54a9529c592aab83a6d4f30137b59 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:34 +1300 Subject: [PATCH 160/224] CVE-2020-25719 tests/krb5: Don't expect a kvno for user-to-user BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 39ca4a69e1ca..f39e57c81891 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -2225,9 +2225,19 @@ class RawKerberosTest(TestCaseInTempDir): self.assertIsNotNone(ticket_encpart) if ticket_encpart is not None: # Never None, but gives indentation self.assertElementPresent(ticket_encpart, 'etype') - # 'unspecified' means present, with any value != 0 - self.assertElementKVNO(ticket_encpart, 'kvno', - self.unspecified_kvno) + + kdc_options = kdc_exchange_dict['kdc_options'] + pos = len(tuple(krb5_asn1.KDCOptions('enc-tkt-in-skey'))) - 1 + expect_kvno = (pos >= len(kdc_options) + or kdc_options[pos] != '1') + if expect_kvno: + # 'unspecified' means present, with any value != 0 + self.assertElementKVNO(ticket_encpart, 'kvno', + self.unspecified_kvno) + else: + # For user-to-user, don't expect a kvno. + self.assertElementMissing(ticket_encpart, 'kvno') + self.assertElementPresent(ticket_encpart, 'cipher') ticket_cipher = self.getElementValue(ticket_encpart, 'cipher') self.assertElementPresent(rep, 'enc-part') -- 2.25.1 From 8e6ca8cf076971909a38e50238b3f9cecca765a4 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:46 +1300 Subject: [PATCH 161/224] CVE-2020-25719 tests/krb5: Expect 'renew-till' element when renewing a TGT BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index f39e57c81891..79fe9ec4620b 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -2369,6 +2369,10 @@ class RawKerberosTest(TestCaseInTempDir): renewable_pos = len(tuple(krb5_asn1.KDCOptions('renewable'))) - 1 renewable = (renewable_pos < len(kdc_options) and kdc_options[renewable_pos] == '1') + renew_pos = len(tuple(krb5_asn1.KDCOptions('renew'))) - 1 + renew = (renew_pos < len(kdc_options) + and kdc_options[renew_pos] == '1') + expect_renew_till = renewable or renew expected_crealm = kdc_exchange_dict['expected_crealm'] expected_cname = kdc_exchange_dict['expected_cname'] @@ -2425,7 +2429,7 @@ class RawKerberosTest(TestCaseInTempDir): if self.strict_checking: self.assertElementPresent(ticket_private, 'starttime') self.assertElementPresent(ticket_private, 'endtime') - if renewable: + if expect_renew_till: if self.strict_checking: self.assertElementPresent(ticket_private, 'renew-till') else: @@ -2461,7 +2465,7 @@ class RawKerberosTest(TestCaseInTempDir): if self.strict_checking: self.assertElementPresent(encpart_private, 'starttime') self.assertElementPresent(encpart_private, 'endtime') - if renewable: + if expect_renew_till: if self.strict_checking: self.assertElementPresent(encpart_private, 'renew-till') else: -- 2.25.1 From 9e4cac95d6a36084185f14a2fc0f0a0b5707dde2 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:05:08 +1300 Subject: [PATCH 162/224] CVE-2020-25719 tests/krb5: Return ticket from _tgs_req() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 480e3d8264d0..11bf38766ae8 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1302,12 +1302,18 @@ class KdcTgsTests(KDCBaseTest): expect_edata=False, expect_claims=expect_claims) - self._generic_kdc_exchange(kdc_exchange_dict, - cname=None, - realm=srealm, - sname=sname, - etypes=etypes, - additional_tickets=additional_tickets) + rep = self._generic_kdc_exchange(kdc_exchange_dict, + cname=None, + realm=srealm, + sname=sname, + etypes=etypes, + additional_tickets=additional_tickets) + if expected_error: + self.check_error_rep(rep, expected_error) + return None + else: + self.check_reply(rep, KRB_TGS_REP) + return kdc_exchange_dict['rep_ticket_creds'] if __name__ == "__main__": -- 2.25.1 From 2bbdc9fecb91cfcaf13a3984661c9715c8801e53 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:14:45 +1300 Subject: [PATCH 163/224] CVE-2020-25719 tests/krb5: Use correct credentials for user-to-user tests BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 9 ++++----- selftest/knownfail_heimdal_kdc | 1 - selftest/knownfail_mit_kdc | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 11bf38766ae8..2787185f04af 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -949,7 +949,7 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds() tgt = self._get_tgt(creds) - user_name = self._get_mach_creds().get_username() + user_name = creds.get_username() sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=['host', user_name]) @@ -960,18 +960,17 @@ class KdcTgsTests(KDCBaseTest): creds = self._get_creds() tgt = self._get_tgt(creds) - user_name = self._get_mach_creds().get_username() + user_name = creds.get_username() sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[user_name]) - self._user2user(tgt, creds, sname=sname, - expected_error=KDC_ERR_BADMATCH) + self._user2user(tgt, creds, sname=sname, expected_error=0) def test_user2user_wrong_sname(self): creds = self._get_creds() tgt = self._get_tgt(creds) - other_creds = self.get_service_creds() + other_creds = self._get_mach_creds() user_name = other_creds.get_username() sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, names=[user_name]) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 468668235907..42f024732725 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -323,7 +323,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_non_existent_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index d2acc5559ed5..daf8012be43f 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -441,7 +441,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_no_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied -- 2.25.1 From de6f4b86cf572df1d673aad9a0666476c3f5b8bc Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:15:53 +1300 Subject: [PATCH 164/224] CVE-2020-25719 tests/krb5: Adjust PAC tests to prepare for new PAC_ATTRIBUTES_INFO buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 10 +++++----- selftest/knownfail_heimdal_kdc | 1 - selftest/knownfail_mit_kdc | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 2787185f04af..10a146a5e59e 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -324,10 +324,10 @@ class KdcTgsTests(KDCBaseTest): self.assertIsNotNone(pac) ticket = self._make_tgs_request(client_creds, service_creds, tgt, - pac_request=False) + pac_request=False, expect_pac=False) - pac = self.get_ticket_pac(ticket) - self.assertIsNotNone(pac) + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) def test_client_no_auth_data_required(self): client_creds = self.get_cached_creds( @@ -351,13 +351,13 @@ class KdcTgsTests(KDCBaseTest): opts={'no_auth_data_required': True}) service_creds = self.get_service_creds() - tgt = self.get_tgt(client_creds, pac_request=False) + tgt = self.get_tgt(client_creds) pac = self.get_ticket_pac(tgt) self.assertIsNotNone(pac) ticket = self._make_tgs_request(client_creds, service_creds, tgt, - pac_request=False) + pac_request=False, expect_pac=True) pac = self.get_ticket_pac(ticket) self.assertIsNotNone(pac) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 42f024732725..1ddf812da254 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -265,7 +265,6 @@ # # KDC TGS PAC tests # -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_client_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index daf8012be43f..720d243e05c5 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -278,7 +278,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ # # KDC TGS PAC tests # -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_client_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required\(ad_dc\) -- 2.25.1 From 01c9478f681170116000885134b578f16fdae056 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:20:51 +1300 Subject: [PATCH 165/224] CVE-2020-25719 tests/krb5: Adjust expected error codes for user-to-user tests BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 10a146a5e59e..3a60b9cafb96 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -40,6 +40,7 @@ from samba.tests.krb5.rfc4120_constants import ( KDC_ERR_BADMATCH, KDC_ERR_BADOPTION, KDC_ERR_CLIENT_NAME_MISMATCH, + KDC_ERR_MODIFIED, KDC_ERR_POLICY, KDC_ERR_S_PRINCIPAL_UNKNOWN, KDC_ERR_TGT_REVOKED, @@ -996,7 +997,8 @@ class KdcTgsTests(KDCBaseTest): service_creds = self.get_service_creds() service_ticket = self.get_service_ticket(tgt, service_creds) - self._user2user(service_ticket, creds, expected_error=KDC_ERR_POLICY) + self._user2user(service_ticket, creds, + expected_error=(KDC_ERR_MODIFIED, KDC_ERR_POLICY)) def _get_tgt(self, client_creds, -- 2.25.1 From 769ca889bcd52e72ddfdcd99ee77cdac18ce930c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:08:34 +1300 Subject: [PATCH 166/224] CVE-2020-25719 tests/krb5: tests/krb5: Adjust expected error code for S4U2Self no-PAC tests BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 32 ++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 3a60b9cafb96..3fd6fc1a7b1e 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -23,7 +23,7 @@ import os import ldb -from samba import dsdb +from samba import dsdb, ntstatus from samba.dcerpc import krb5pac @@ -40,6 +40,7 @@ from samba.tests.krb5.rfc4120_constants import ( KDC_ERR_BADMATCH, KDC_ERR_BADOPTION, KDC_ERR_CLIENT_NAME_MISMATCH, + KDC_ERR_GENERIC, KDC_ERR_MODIFIED, KDC_ERR_POLICY, KDC_ERR_S_PRINCIPAL_UNKNOWN, @@ -528,7 +529,10 @@ class KdcTgsTests(KDCBaseTest): def test_s4u2self_no_pac(self): creds = self._get_creds() tgt = self._get_tgt(creds, remove_pac=True) - self._s4u2self(tgt, creds, expected_error=KDC_ERR_BADOPTION) + self._s4u2self(tgt, creds, + expected_error=(KDC_ERR_GENERIC, KDC_ERR_BADOPTION), + expected_status=ntstatus.NT_STATUS_INVALID_PARAMETER, + expect_edata=True) def test_user2user_no_pac(self): creds = self._get_creds() @@ -556,7 +560,10 @@ class KdcTgsTests(KDCBaseTest): def test_s4u2self_authdata_no_pac(self): creds = self._get_creds() tgt = self._get_tgt(creds, remove_pac=True, allow_empty_authdata=True) - self._s4u2self(tgt, creds, expected_error=KDC_ERR_BADOPTION) + self._s4u2self(tgt, creds, + expected_error=(KDC_ERR_GENERIC, KDC_ERR_BADOPTION), + expected_status=ntstatus.NT_STATUS_INVALID_PARAMETER, + expect_edata=True) def test_user2user_authdata_no_pac(self): creds = self._get_creds() @@ -1210,7 +1217,8 @@ class KdcTgsTests(KDCBaseTest): self._tgs_req(tgt, expected_error, krbtgt_creds, kdc_options=kdc_options) - def _s4u2self(self, tgt, tgt_creds, expected_error): + def _s4u2self(self, tgt, tgt_creds, expected_error, + expect_edata=False, expected_status=None): user_creds = self._get_mach_creds() user_name = user_creds.get_username() @@ -1229,10 +1237,11 @@ class KdcTgsTests(KDCBaseTest): return [padata], req_body - self._tgs_req(tgt, expected_error, tgt_creds, - expected_cname=user_cname, - generate_padata_fn=generate_s4u2self_padata, - expect_claims=False) + return self._tgs_req(tgt, expected_error, tgt_creds, + expected_cname=user_cname, + generate_padata_fn=generate_s4u2self_padata, + expect_claims=False, expect_edata=expect_edata, + expected_status=expected_status) def _user2user(self, tgt, tgt_creds, expected_error, sname=None): user_creds = self._get_mach_creds() @@ -1250,7 +1259,9 @@ class KdcTgsTests(KDCBaseTest): additional_ticket=None, generate_padata_fn=None, sname=None, - expect_claims=True): + expect_claims=True, + expect_edata=False, + expected_status=None): srealm = target_creds.get_realm() if sname is None: @@ -1297,10 +1308,11 @@ class KdcTgsTests(KDCBaseTest): check_rep_fn=check_rep_fn, check_kdc_private_fn=self.generic_check_kdc_private, expected_error_mode=expected_error, + expected_status=expected_status, tgt=tgt, authenticator_subkey=subkey, kdc_options=kdc_options, - expect_edata=False, + expect_edata=expect_edata, expect_claims=expect_claims) rep = self._generic_kdc_exchange(kdc_exchange_dict, -- 2.25.1 From 9e52e3339b3e00320a1e5e6d60325093a61b7ea7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:12:12 +1300 Subject: [PATCH 167/224] CVE-2020-25719 tests/krb5: Extend _get_tgt() method to allow more modifications to tickets BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 55 ++++++++++++++++-------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 3fd6fc1a7b1e..4cb32c962500 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -25,7 +25,7 @@ import ldb from samba import dsdb, ntstatus -from samba.dcerpc import krb5pac +from samba.dcerpc import krb5pac, security sys.path.insert(0, "bin/python") os.environ["PYTHONUNBUFFERED"] = "1" @@ -1014,7 +1014,11 @@ class KdcTgsTests(KDCBaseTest): from_rodc=False, new_rid=None, remove_pac=False, - allow_empty_authdata=False): + allow_empty_authdata=False, + can_modify_logon_info=True, + can_modify_requester_sid=True, + remove_pac_attrs=False, + remove_requester_sid=False): self.assertFalse(renewable and invalid) if remove_pac: @@ -1027,19 +1031,38 @@ class KdcTgsTests(KDCBaseTest): else: krbtgt_creds = self.get_krbtgt_creds() - if new_rid is not None: + if new_rid is not None or remove_requester_sid or remove_pac_attrs: def change_sid_fn(pac): - for pac_buffer in pac.buffers: + pac_buffers = pac.buffers + for pac_buffer in pac_buffers: if pac_buffer.type == krb5pac.PAC_TYPE_LOGON_INFO: - logon_info = pac_buffer.info.info + if new_rid is not None and can_modify_logon_info: + logon_info = pac_buffer.info.info - logon_info.info3.base.rid = new_rid + logon_info.info3.base.rid = new_rid + elif pac_buffer.type == krb5pac.PAC_TYPE_REQUESTER_SID: + if remove_requester_sid: + pac.num_buffers -= 1 + pac_buffers.remove(pac_buffer) + elif new_rid is not None and can_modify_requester_sid: + requester_sid = pac_buffer.info - return pac + samdb = self.get_samdb() + domain_sid = samdb.get_domain_sid() + + new_sid = f'{domain_sid}-{new_rid}' + + requester_sid.sid = security.dom_sid(new_sid) + elif pac_buffer.type == krb5pac.PAC_TYPE_ATTRIBUTES_INFO: + if remove_pac_attrs: + pac.num_buffers -= 1 + pac_buffers.remove(pac_buffer) + + pac.buffers = pac_buffers - modify_pac_fn = change_sid_fn + return pac else: - modify_pac_fn = None + change_sid_fn = None krbtgt_key = self.TicketDecryptionKey_from_creds(krbtgt_creds) @@ -1051,7 +1074,7 @@ class KdcTgsTests(KDCBaseTest): } if renewable: - def set_renewable(enc_part): + def flags_modify_fn(enc_part): # Set the renewable flag. renewable_flag = krb5_asn1.TicketFlags('renewable') pos = len(tuple(renewable_flag)) - 1 @@ -1067,10 +1090,8 @@ class KdcTgsTests(KDCBaseTest): enc_part['renew-till'] = renew_till return enc_part - - modify_fn = set_renewable elif invalid: - def set_invalid(enc_part): + def flags_modify_fn(enc_part): # Set the invalid flag. invalid_flag = krb5_asn1.TicketFlags('invalid') pos = len(tuple(invalid_flag)) - 1 @@ -1086,16 +1107,14 @@ class KdcTgsTests(KDCBaseTest): enc_part['starttime'] = past_time return enc_part - - modify_fn = set_invalid else: - modify_fn = None + flags_modify_fn = None return self.modified_ticket( tgt, new_ticket_key=krbtgt_key, - modify_fn=modify_fn, - modify_pac_fn=modify_pac_fn, + modify_fn=flags_modify_fn, + modify_pac_fn=change_sid_fn, exclude_pac=remove_pac, allow_empty_authdata=allow_empty_authdata, update_pac_checksums=not remove_pac, -- 2.25.1 From 87800ef766ec4ddf5abc6ab110393cca52fa0a9d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 10:25:08 +1300 Subject: [PATCH 168/224] CVE-2020-25719 tests/krb5: Add _modify_tgt() method for modifying already obtained tickets https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 62 +++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 4cb32c962500..52a347b9ed44 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1026,6 +1026,33 @@ class KdcTgsTests(KDCBaseTest): tgt = self.get_tgt(client_creds) + return self._modify_tgt( + tgt=tgt, + renewable=renewable, + invalid=invalid, + from_rodc=from_rodc, + new_rid=new_rid, + remove_pac=remove_pac, + allow_empty_authdata=allow_empty_authdata, + can_modify_logon_info=can_modify_logon_info, + can_modify_requester_sid=can_modify_requester_sid, + remove_pac_attrs=remove_pac_attrs, + remove_requester_sid=remove_requester_sid) + + def _modify_tgt(self, + tgt, + renewable=False, + invalid=False, + from_rodc=False, + new_rid=None, + remove_pac=False, + allow_empty_authdata=False, + cname=None, + crealm=None, + can_modify_logon_info=True, + can_modify_requester_sid=True, + remove_pac_attrs=False, + remove_requester_sid=False): if from_rodc: krbtgt_creds = self.get_mock_rodc_krbtgt_creds() else: @@ -1110,11 +1137,42 @@ class KdcTgsTests(KDCBaseTest): else: flags_modify_fn = None + if cname is not None or crealm is not None: + def modify_fn(enc_part): + if flags_modify_fn is not None: + enc_part = flags_modify_fn(enc_part) + + if cname is not None: + enc_part['cname'] = cname + + if crealm is not None: + enc_part['crealm'] = crealm + + return enc_part + else: + modify_fn = flags_modify_fn + + if cname is not None: + def modify_pac_fn(pac): + if change_sid_fn is not None: + pac = change_sid_fn(pac) + + for pac_buffer in pac.buffers: + if pac_buffer.type == krb5pac.PAC_TYPE_LOGON_NAME: + logon_info = pac_buffer.info + + logon_info.account_name = ( + cname['name-string'][0].decode('utf-8')) + + return pac + else: + modify_pac_fn = change_sid_fn + return self.modified_ticket( tgt, new_ticket_key=krbtgt_key, - modify_fn=flags_modify_fn, - modify_pac_fn=change_sid_fn, + modify_fn=modify_fn, + modify_pac_fn=modify_pac_fn, exclude_pac=remove_pac, allow_empty_authdata=allow_empty_authdata, update_pac_checksums=not remove_pac, -- 2.25.1 From 4a9d89fcfc508059ba4da3680f9c9e197065f174 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:50:09 +1300 Subject: [PATCH 169/224] CVE-2020-25719 tests/krb5: Add testing for PAC_TYPE_ATTRIBUTES_INFO PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 8 ++++- python/samba/tests/krb5/raw_testcase.py | 37 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 6d6dcc216075..dc1ba629b41a 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1360,7 +1360,9 @@ class KDCBaseTest(RawKerberosTest): expected_flags=None, unexpected_flags=None, expected_account_name=None, expected_upn_name=None, expected_sid=None, - pac_request=True, expect_pac=True, fresh=False): + pac_request=True, expect_pac=True, + expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + fresh=False): user_name = creds.get_username() cache_key = (user_name, to_rodc, kdc_options, pac_request) @@ -1426,6 +1428,8 @@ class KDCBaseTest(RawKerberosTest): pac_request=pac_request, pac_options=pac_options, expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, to_rodc=to_rodc) self.check_pre_authentication(rep) @@ -1470,6 +1474,8 @@ class KDCBaseTest(RawKerberosTest): pac_request=pac_request, pac_options=pac_options, expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, to_rodc=to_rodc) self.check_as_reply(rep) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 79fe9ec4620b..d63366318be1 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -2021,6 +2021,8 @@ class RawKerberosTest(TestCaseInTempDir): expect_pac=True, expect_claims=True, expect_upn_dns_info_ex=None, + expect_pac_attrs=None, + expect_pac_attrs_pac_request=None, to_rodc=False): if expected_error_mode == 0: expected_error_mode = () @@ -2074,6 +2076,8 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_pac': expect_pac, 'expect_claims': expect_claims, 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, + 'expect_pac_attrs': expect_pac_attrs, + 'expect_pac_attrs_pac_request': expect_pac_attrs_pac_request, 'to_rodc': to_rodc } if callback_dict is None: @@ -2122,6 +2126,8 @@ class RawKerberosTest(TestCaseInTempDir): expect_pac=True, expect_claims=True, expect_upn_dns_info_ex=None, + expect_pac_attrs=None, + expect_pac_attrs_pac_request=None, expected_proxy_target=None, expected_transited_services=None, to_rodc=False): @@ -2176,6 +2182,8 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_pac': expect_pac, 'expect_claims': expect_claims, 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, + 'expect_pac_attrs': expect_pac_attrs, + 'expect_pac_attrs_pac_request': expect_pac_attrs_pac_request, 'expected_proxy_target': expected_proxy_target, 'expected_transited_services': expected_transited_services, 'to_rodc': to_rodc @@ -2596,6 +2604,12 @@ class RawKerberosTest(TestCaseInTempDir): if not self.tkt_sig_support: require_strict.add(krb5pac.PAC_TYPE_TICKET_CHECKSUM) + expect_pac_attrs = kdc_exchange_dict['expect_pac_attrs'] + if expect_pac_attrs: + expected_types.append(krb5pac.PAC_TYPE_ATTRIBUTES_INFO) + elif expect_pac_attrs is None: + require_strict.add(krb5pac.PAC_TYPE_ATTRIBUTES_INFO) + buffer_types = [pac_buffer.type for pac_buffer in pac.buffers] self.assertSequenceElementsEqual( @@ -2671,6 +2685,25 @@ class RawKerberosTest(TestCaseInTempDir): self.assertEqual(expected_sid, str(upn_dns_info_ex.objectsid)) + elif (pac_buffer.type == krb5pac.PAC_TYPE_ATTRIBUTES_INFO + and expect_pac_attrs): + attr_info = pac_buffer.info + + self.assertEqual(2, attr_info.flags_length) + + flags = attr_info.flags + + requested_pac = bool(flags & 1) + given_pac = bool(flags & 2) + + expect_pac_attrs_pac_request = kdc_exchange_dict[ + 'expect_pac_attrs_pac_request'] + + self.assertEqual(expect_pac_attrs_pac_request is True, + requested_pac) + self.assertEqual(expect_pac_attrs_pac_request is None, + given_pac) + def generic_check_kdc_error(self, kdc_exchange_dict, callback_dict, @@ -3663,6 +3696,8 @@ class RawKerberosTest(TestCaseInTempDir): pac_request=None, pac_options=None, expect_pac=True, + expect_pac_attrs=None, + expect_pac_attrs_pac_request=None, to_rodc=False): def _generate_padata_copy(_kdc_exchange_dict, @@ -3706,6 +3741,8 @@ class RawKerberosTest(TestCaseInTempDir): pac_request=pac_request, pac_options=pac_options, expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, to_rodc=to_rodc) rep = self._generic_kdc_exchange(kdc_exchange_dict, -- 2.25.1 From fad5b94ac353193fe5eda43c77fa687b1336af35 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:13 +1300 Subject: [PATCH 170/224] CVE-2020-25719 tests/krb5: Add testing for PAC_TYPE_REQUESTER_SID PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 3 +++ python/samba/tests/krb5/raw_testcase.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index dc1ba629b41a..61eeb2333f9b 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1362,6 +1362,7 @@ class KDCBaseTest(RawKerberosTest): expected_sid=None, pac_request=True, expect_pac=True, expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, fresh=False): user_name = creds.get_username() cache_key = (user_name, to_rodc, kdc_options, pac_request) @@ -1430,6 +1431,7 @@ class KDCBaseTest(RawKerberosTest): expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, to_rodc=to_rodc) self.check_pre_authentication(rep) @@ -1476,6 +1478,7 @@ class KDCBaseTest(RawKerberosTest): expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, to_rodc=to_rodc) self.check_as_reply(rep) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index d63366318be1..8779d0f7869f 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -2023,6 +2023,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_upn_dns_info_ex=None, expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, to_rodc=False): if expected_error_mode == 0: expected_error_mode = () @@ -2078,6 +2079,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, 'expect_pac_attrs': expect_pac_attrs, 'expect_pac_attrs_pac_request': expect_pac_attrs_pac_request, + 'expect_requester_sid': expect_requester_sid, 'to_rodc': to_rodc } if callback_dict is None: @@ -2128,6 +2130,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_upn_dns_info_ex=None, expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, expected_proxy_target=None, expected_transited_services=None, to_rodc=False): @@ -2184,6 +2187,7 @@ class RawKerberosTest(TestCaseInTempDir): 'expect_upn_dns_info_ex': expect_upn_dns_info_ex, 'expect_pac_attrs': expect_pac_attrs, 'expect_pac_attrs_pac_request': expect_pac_attrs_pac_request, + 'expect_requester_sid': expect_requester_sid, 'expected_proxy_target': expected_proxy_target, 'expected_transited_services': expected_transited_services, 'to_rodc': to_rodc @@ -2610,6 +2614,12 @@ class RawKerberosTest(TestCaseInTempDir): elif expect_pac_attrs is None: require_strict.add(krb5pac.PAC_TYPE_ATTRIBUTES_INFO) + expect_requester_sid = kdc_exchange_dict['expect_requester_sid'] + if expect_requester_sid: + expected_types.append(krb5pac.PAC_TYPE_REQUESTER_SID) + elif expect_requester_sid is None: + require_strict.add(krb5pac.PAC_TYPE_REQUESTER_SID) + buffer_types = [pac_buffer.type for pac_buffer in pac.buffers] self.assertSequenceElementsEqual( @@ -2704,6 +2714,13 @@ class RawKerberosTest(TestCaseInTempDir): self.assertEqual(expect_pac_attrs_pac_request is None, given_pac) + elif (pac_buffer.type == krb5pac.PAC_TYPE_REQUESTER_SID + and expect_requester_sid): + requester_sid = pac_buffer.info.sid + + self.assertIsNotNone(expected_sid) + self.assertEqual(expected_sid, str(requester_sid)) + def generic_check_kdc_error(self, kdc_exchange_dict, callback_dict, @@ -3698,6 +3715,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_pac=True, expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, to_rodc=False): def _generate_padata_copy(_kdc_exchange_dict, @@ -3743,6 +3761,7 @@ class RawKerberosTest(TestCaseInTempDir): expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, to_rodc=to_rodc) rep = self._generic_kdc_exchange(kdc_exchange_dict, -- 2.25.1 From c3d2b0ca0d797eeb8b5630d0f4d52a7dc1aec49d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:47:24 +1300 Subject: [PATCH 171/224] CVE-2020-25719 tests/krb5: Add EXPECT_PAC environment variable to expect pac from all TGS tickets BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/raw_testcase.py | 25 ++++++++--- source4/selftest/tests.py | 55 +++++++++++++++++-------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 8779d0f7869f..42f2e94f5aa9 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -596,6 +596,12 @@ class RawKerberosTest(TestCaseInTempDir): tkt_sig_support = '0' cls.tkt_sig_support = bool(int(tkt_sig_support)) + expect_pac = samba.tests.env_get_var_value('EXPECT_PAC', + allow_missing=True) + if expect_pac is None: + expect_pac = '1' + cls.expect_pac = bool(int(expect_pac)) + def setUp(self): super().setUp() self.do_asn1_print = False @@ -2417,7 +2423,10 @@ class RawKerberosTest(TestCaseInTempDir): etype=kcrypto.Enctype.RC4) krbtgt_keys.append(krbtgt_key_rc4) - expect_pac = kdc_exchange_dict['expect_pac'] + if self.expect_pac and self.is_tgs(expected_sname): + expect_pac = True + else: + expect_pac = kdc_exchange_dict['expect_pac'] ticket_session_key = None if ticket_private is not None: @@ -2448,8 +2457,9 @@ class RawKerberosTest(TestCaseInTempDir): self.assertElementMissing(ticket_private, 'renew-till') if self.strict_checking: self.assertElementEqual(ticket_private, 'caddr', []) - self.assertElementPresent(ticket_private, 'authorization-data', - expect_empty=not expect_pac) + if expect_pac is not None: + self.assertElementPresent(ticket_private, 'authorization-data', + expect_empty=not expect_pac) encpart_session_key = None if encpart_private is not None: @@ -2554,11 +2564,14 @@ class RawKerberosTest(TestCaseInTempDir): if ticket_private is not None: pac_data = self.get_ticket_pac(ticket_creds, expect_pac=expect_pac) - if expect_pac: - self.check_pac_buffers(pac_data, kdc_exchange_dict) - else: + if expect_pac is True: + self.assertIsNotNone(pac_data) + elif expect_pac is False: self.assertIsNone(pac_data) + if pac_data is not None: + self.check_pac_buffers(pac_data, kdc_exchange_dict) + expect_ticket_checksum = kdc_exchange_dict['expect_ticket_checksum'] if expect_ticket_checksum: self.assertIsNotNone(ticket_decryption_key) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 2b3c2a9cad65..35186ad59c5a 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -811,28 +811,33 @@ planoldpythontestsuite("ad_dc:local", "samba.tests.dckeytab", extra_args=['-U"$U have_fast_support = int('SAMBA_USES_MITKDC' in config_hash) tkt_sig_support = int('SAMBA4_USES_HEIMDAL' in config_hash) +expect_pac = 0 planoldpythontestsuite("none", "samba.tests.krb5.kcrypto") planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.simple_tests", environ={'SERVICE_USERNAME':'$SERVER', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support}) + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac}) planoldpythontestsuite("ad_dc_default:local", "samba.tests.krb5.s4u_tests", environ={'ADMIN_USERNAME':'$USERNAME', 'ADMIN_PASSWORD':'$PASSWORD', 'FOR_USER':'$USERNAME', 'STRICT_CHECKING':'0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support}) + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac}) planoldpythontestsuite("rodc:local", "samba.tests.krb5.rodc_tests", environ={'ADMIN_USERNAME':'$USERNAME', 'ADMIN_PASSWORD':'$PASSWORD', 'STRICT_CHECKING':'0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support}) + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac}) planoldpythontestsuite("fl2008r2dc:local", "samba.tests.krb5.xrealm_tests", environ={'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support}) + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac}) planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_ccache", environ={ @@ -840,7 +845,8 @@ planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_ccache", 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_ldap", environ={ @@ -848,7 +854,8 @@ planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.test_ldap", 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) for env in ['ad_dc_default', 'ad_member']: planoldpythontestsuite(env, "samba.tests.krb5.test_rpc", @@ -857,7 +864,8 @@ for env in ['ad_dc_default', 'ad_member']: 'ADMIN_PASSWORD': '$DC_PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planoldpythontestsuite("ad_dc_smb1", "samba.tests.krb5.test_smb", environ={ @@ -865,7 +873,8 @@ planoldpythontestsuite("ad_dc_smb1", "samba.tests.krb5.test_smb", 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planoldpythontestsuite("ad_member_no_nss_wb:local", "samba.tests.krb5.test_min_domain_uid", @@ -1453,7 +1462,8 @@ for env in ["fl2008r2dc", "fl2003dc"]: 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planoldpythontestsuite('fl2008r2dc', 'samba.tests.krb5.salt_tests', @@ -1462,7 +1472,8 @@ planoldpythontestsuite('fl2008r2dc', 'samba.tests.krb5.salt_tests', 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) for env in ["rodc", "promoted_dc", "fl2000dc", "fl2008r2dc"]: @@ -1484,7 +1495,8 @@ planpythontestsuite("ad_dc", "samba.tests.krb5.as_canonicalization_tests", 'ADMIN_USERNAME': '$USERNAME', 'ADMIN_PASSWORD': '$PASSWORD', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite("ad_dc", "samba.tests.krb5.compatability_tests", environ={ @@ -1492,11 +1504,13 @@ planpythontestsuite("ad_dc", "samba.tests.krb5.compatability_tests", 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite("ad_dc", "samba.tests.krb5.kdc_tests", environ={'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support}) + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac}) planpythontestsuite( "ad_dc", "samba.tests.krb5.kdc_tgs_tests", @@ -1505,7 +1519,8 @@ planpythontestsuite( 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite( "ad_dc", @@ -1515,7 +1530,8 @@ planpythontestsuite( 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite( "ad_dc", @@ -1525,7 +1541,8 @@ planpythontestsuite( 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite( "ad_dc", @@ -1535,7 +1552,8 @@ planpythontestsuite( 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) planpythontestsuite( "ad_dc", @@ -1545,7 +1563,8 @@ planpythontestsuite( 'ADMIN_PASSWORD': '$PASSWORD', 'STRICT_CHECKING': '0', 'FAST_SUPPORT': have_fast_support, - 'TKT_SIG_SUPPORT': tkt_sig_support + 'TKT_SIG_SUPPORT': tkt_sig_support, + 'EXPECT_PAC': expect_pac }) for env in [ -- 2.25.1 From 84a301fbf10768ceea81c1b286b474bd9c0f86af Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 11:18:36 +1300 Subject: [PATCH 172/224] CVE-2020-25719 tests/krb5: Add expected parameters to cache key for obtaining tickets If multiple calls to get_tgt() or get_service_ticket() specify different expected parameters, we want to perform the request again so that the checking can be performed, rather than reusing a previously obtained ticket and potentially skipping checks. It should be fine to cache tickets with the same expected parameters, as tickets that fail to be obtained will not be stored in the cache, so the checking will happen for every call. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 61eeb2333f9b..4b4f1486f60d 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -1294,7 +1294,8 @@ class KDCBaseTest(RawKerberosTest): if target_name is None: target_name = target_creds.get_username()[:-1] cache_key = (user_name, target_name, service, to_rodc, kdc_options, - pac_request) + pac_request, str(expected_flags), str(unexpected_flags), + expect_pac) if not fresh: ticket = self.tkt_cache.get(cache_key) @@ -1365,7 +1366,11 @@ class KDCBaseTest(RawKerberosTest): expect_requester_sid=None, fresh=False): user_name = creds.get_username() - cache_key = (user_name, to_rodc, kdc_options, pac_request) + cache_key = (user_name, to_rodc, kdc_options, pac_request, + str(expected_flags), str(unexpected_flags), + expected_account_name, expected_upn_name, expected_sid, + expect_pac, expect_pac_attrs, + expect_pac_attrs_pac_request, expect_requester_sid) if not fresh: tgt = self.tkt_cache.get(cache_key) -- 2.25.1 From 248abc86afb19371fe77d60fca03f16826d649c9 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:02:08 +1300 Subject: [PATCH 173/224] CVE-2020-25719 tests/krb5: Add tests for PAC attributes buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 281 +++++++++++++++++++++-- selftest/knownfail_heimdal_kdc | 21 ++ selftest/knownfail_mit_kdc | 22 ++ 3 files changed, 308 insertions(+), 16 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 52a347b9ed44..402916778192 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -510,6 +510,20 @@ class KdcTgsTests(KDCBaseTest): tgt = self._get_tgt(creds) self._user2user(tgt, creds, expected_error=0) + def test_tgs_req_no_pac_attrs(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_pac_attrs=True) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_pac_attrs=False) + + def test_tgs_req_from_rodc_no_pac_attrs(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, remove_pac_attrs=True) + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_pac_attrs=False) + # Test making a request without a PAC. def test_tgs_no_pac(self): creds = self._get_creds() @@ -1007,6 +1021,221 @@ class KdcTgsTests(KDCBaseTest): self._user2user(service_ticket, creds, expected_error=(KDC_ERR_MODIFIED, KDC_ERR_POLICY)) + def test_pac_attrs_none(self): + creds = self._get_creds() + self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + + def test_pac_attrs_false(self): + creds = self._get_creds() + self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + + def test_pac_attrs_true(self): + creds = self._get_creds() + self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + + def test_pac_attrs_renew_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + tgt = self._modify_tgt(tgt, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + + def test_pac_attrs_renew_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + tgt = self._modify_tgt(tgt, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + + def test_pac_attrs_renew_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + tgt = self._modify_tgt(tgt, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + + def test_pac_attrs_rodc_renew_none(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + + def test_pac_attrs_rodc_renew_false(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + + def test_pac_attrs_rodc_renew_true(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + + def test_pac_attrs_missing_renew_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + tgt = self._modify_tgt(tgt, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_pac_attrs_missing_renew_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + tgt = self._modify_tgt(tgt, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_pac_attrs_missing_renew_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + tgt = self._modify_tgt(tgt, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_pac_attrs_missing_rodc_renew_none(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_pac_attrs_missing_rodc_renew_false(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_pac_attrs_missing_rodc_renew_true(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True, + remove_pac_attrs=True) + + self._renew_tgt(tgt, expected_error=0, + expect_pac=True, + expect_pac_attrs=False) + + def test_tgs_pac_attrs_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=None) + + def test_tgs_pac_attrs_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=False) + + self._run_tgs(tgt, expected_error=0, expect_pac=False) + + def test_tgs_pac_attrs_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True, + expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_pac_attrs=True, + expect_pac_attrs_pac_request=True) + + def _get_tgt(self, client_creds, renewable=False, @@ -1278,23 +1507,34 @@ class KdcTgsTests(KDCBaseTest): def _get_non_existent_rid(self): return (1 << 30) - 1 - def _run_tgs(self, tgt, expected_error): + def _run_tgs(self, tgt, expected_error, expect_pac=True, + expect_pac_attrs=None, expect_pac_attrs_pac_request=None): target_creds = self.get_service_creds() - self._tgs_req(tgt, expected_error, target_creds) - - def _renew_tgt(self, tgt, expected_error): + return self._tgs_req( + tgt, expected_error, target_creds, + expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request) + + def _renew_tgt(self, tgt, expected_error, expect_pac=True, + expect_pac_attrs=None, expect_pac_attrs_pac_request=None): krbtgt_creds = self.get_krbtgt_creds() kdc_options = str(krb5_asn1.KDCOptions('renew')) - self._tgs_req(tgt, expected_error, krbtgt_creds, - kdc_options=kdc_options) + return self._tgs_req( + tgt, expected_error, krbtgt_creds, + kdc_options=kdc_options, + expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request) - def _validate_tgt(self, tgt, expected_error): + def _validate_tgt(self, tgt, expected_error, expect_pac=True): krbtgt_creds = self.get_krbtgt_creds() kdc_options = str(krb5_asn1.KDCOptions('validate')) - self._tgs_req(tgt, expected_error, krbtgt_creds, - kdc_options=kdc_options) + return self._tgs_req(tgt, expected_error, krbtgt_creds, + kdc_options=kdc_options, + expect_pac=expect_pac) - def _s4u2self(self, tgt, tgt_creds, expected_error, + def _s4u2self(self, tgt, tgt_creds, expected_error, expect_pac=True, expect_edata=False, expected_status=None): user_creds = self._get_mach_creds() @@ -1318,17 +1558,20 @@ class KdcTgsTests(KDCBaseTest): expected_cname=user_cname, generate_padata_fn=generate_s4u2self_padata, expect_claims=False, expect_edata=expect_edata, - expected_status=expected_status) + expected_status=expected_status, + expect_pac=expect_pac) - def _user2user(self, tgt, tgt_creds, expected_error, sname=None): + def _user2user(self, tgt, tgt_creds, expected_error, sname=None, + expect_pac=True): user_creds = self._get_mach_creds() user_tgt = self.get_tgt(user_creds) kdc_options = str(krb5_asn1.KDCOptions('enc-tkt-in-skey')) - self._tgs_req(user_tgt, expected_error, tgt_creds, - kdc_options=kdc_options, - additional_ticket=tgt, - sname=sname) + return self._tgs_req(user_tgt, expected_error, tgt_creds, + kdc_options=kdc_options, + additional_ticket=tgt, + sname=sname, + expect_pac=expect_pac) def _tgs_req(self, tgt, expected_error, target_creds, kdc_options='0', @@ -1337,6 +1580,9 @@ class KdcTgsTests(KDCBaseTest): generate_padata_fn=None, sname=None, expect_claims=True, + expect_pac=True, + expect_pac_attrs=None, + expect_pac_attrs_pac_request=None, expect_edata=False, expected_status=None): srealm = target_creds.get_realm() @@ -1390,6 +1636,9 @@ class KdcTgsTests(KDCBaseTest): authenticator_subkey=subkey, kdc_options=kdc_options, expect_edata=expect_edata, + expect_pac=expect_pac, + expect_pac_attrs=expect_pac_attrs, + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, expect_claims=expect_claims) rep = self._generic_kdc_exchange(kdc_exchange_dict, diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 1ddf812da254..e9b510e0f09b 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -349,3 +349,24 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting +# +# PAC attributes tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_true diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 720d243e05c5..cf0eb2495b28 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -467,3 +467,25 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting +# +# PAC attributes tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_pac_attrs -- 2.25.1 From ded54860cea556dd0a34434a9d97874c434dd5f1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:19:44 +1300 Subject: [PATCH 174/224] CVE-2020-25719 tests/krb5: Add tests for PAC-REQUEST padata BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 232 ++++++++++++++++++++++- selftest/knownfail_heimdal_kdc | 9 + selftest/knownfail_mit_kdc | 18 ++ 3 files changed, 256 insertions(+), 3 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 402916778192..53d7dd4effba 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1235,6 +1235,231 @@ class KdcTgsTests(KDCBaseTest): expect_pac_attrs=True, expect_pac_attrs_pac_request=True) + def test_tgs_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_tgs_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_tgs_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_renew_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None) + tgt = self._modify_tgt(tgt, renewable=True) + + tgt = self._renew_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_renew_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + tgt = self._modify_tgt(tgt, renewable=True) + + tgt = self._renew_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_renew_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True) + tgt = self._modify_tgt(tgt, renewable=True) + + tgt = self._renew_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_validate_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None) + tgt = self._modify_tgt(tgt, invalid=True) + + tgt = self._validate_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_validate_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + tgt = self._modify_tgt(tgt, invalid=True) + + tgt = self._validate_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_validate_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True) + tgt = self._modify_tgt(tgt, invalid=True) + + tgt = self._validate_tgt(tgt, expected_error=0, expect_pac=None) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_s4u2self_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None) + + ticket = self._s4u2self(tgt, creds, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_s4u2self_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + + ticket = self._s4u2self(tgt, creds, expected_error=0, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_s4u2self_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True) + + ticket = self._s4u2self(tgt, creds, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_user2user_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=None) + + ticket = self._user2user(tgt, creds, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_user2user_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + + ticket = self._user2user(tgt, creds, expected_error=0, + expect_pac=True) + + pac = self.get_ticket_pac(ticket, expect_pac=True) + self.assertIsNotNone(pac) + + def test_user2user_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds, pac_request=True) + + ticket = self._user2user(tgt, creds, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_user2user_user_pac_request_none(self): + creds = self._get_creds() + tgt = self.get_tgt(creds) + + user_creds = self._get_mach_creds() + user_tgt = self.get_tgt(user_creds, pac_request=None) + + ticket = self._user2user(tgt, creds, expected_error=0, + user_tgt=user_tgt, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_user2user_user_pac_request_false(self): + creds = self._get_creds() + tgt = self.get_tgt(creds) + + user_creds = self._get_mach_creds() + user_tgt = self.get_tgt(user_creds, pac_request=False, expect_pac=None) + + ticket = self._user2user(tgt, creds, expected_error=0, + user_tgt=user_tgt, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_user2user_user_pac_request_true(self): + creds = self._get_creds() + tgt = self.get_tgt(creds) + + user_creds = self._get_mach_creds() + user_tgt = self.get_tgt(user_creds, pac_request=True) + + ticket = self._user2user(tgt, creds, expected_error=0, + user_tgt=user_tgt, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_tgs_rodc_pac_request_none(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=None) + tgt = self._modify_tgt(tgt, from_rodc=True) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) + + def test_tgs_rodc_pac_request_false(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=False, expect_pac=None) + tgt = self._modify_tgt(tgt, from_rodc=True) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=False) + + pac = self.get_ticket_pac(ticket, expect_pac=False) + self.assertIsNone(pac) + + def test_tgs_rodc_pac_request_true(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self.get_tgt(creds, pac_request=True) + tgt = self._modify_tgt(tgt, from_rodc=True) + + ticket = self._run_tgs(tgt, expected_error=0, expect_pac=True) + + pac = self.get_ticket_pac(ticket) + self.assertIsNotNone(pac) def _get_tgt(self, client_creds, @@ -1562,9 +1787,10 @@ class KdcTgsTests(KDCBaseTest): expect_pac=expect_pac) def _user2user(self, tgt, tgt_creds, expected_error, sname=None, - expect_pac=True): - user_creds = self._get_mach_creds() - user_tgt = self.get_tgt(user_creds) + user_tgt=None, expect_pac=True): + if user_tgt is None: + user_creds = self._get_mach_creds() + user_tgt = self.get_tgt(user_creds) kdc_options = str(krb5_asn1.KDCOptions('enc-tkt-in-skey')) return self._tgs_req(user_tgt, expected_error, tgt_creds, diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index e9b510e0f09b..e6fad91b4020 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -370,3 +370,12 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_true +# +# PAC request tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index cf0eb2495b28..17a9792c6192 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -489,3 +489,21 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_true ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_pac_attrs +# +# PAC request tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_pac_request_false -- 2.25.1 From 2f61ca20baebaca0a7d55a8f48995a71b2c2c32d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:04:25 +1300 Subject: [PATCH 175/224] CVE-2020-25719 tests/krb5: Add tests for requester SID PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 222 ++++++++++++++++++++++- selftest/knownfail_heimdal_kdc | 18 ++ selftest/knownfail_mit_kdc | 20 ++ 3 files changed, 256 insertions(+), 4 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 53d7dd4effba..2005d71fa81a 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -510,6 +510,13 @@ class KdcTgsTests(KDCBaseTest): tgt = self._get_tgt(creds) self._user2user(tgt, creds, expected_error=0) + def test_tgs_req_no_requester_sid(self): + creds = self._get_creds() + tgt = self._get_tgt(creds, remove_requester_sid=True) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_requester_sid=False) # Note: not expected + def test_tgs_req_no_pac_attrs(self): creds = self._get_creds() tgt = self._get_tgt(creds, remove_pac_attrs=True) @@ -517,6 +524,17 @@ class KdcTgsTests(KDCBaseTest): self._run_tgs(tgt, expected_error=0, expect_pac=True, expect_pac_attrs=False) + def test_tgs_req_from_rodc_no_requester_sid(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, remove_requester_sid=True) + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expect_requester_sid=True, expected_sid=sid) + def test_tgs_req_from_rodc_no_pac_attrs(self): creds = self._get_creds(replication_allowed=True, revealed_to_rodc=True) @@ -617,6 +635,27 @@ class KdcTgsTests(KDCBaseTest): self._user2user(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + def test_requester_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid, + can_modify_logon_info=False) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_logon_info_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid, + can_modify_requester_sid=False) + self._run_tgs(tgt, expected_error=0) + + def test_logon_info_only_sid_mismatch_existing(self): + creds = self._get_creds() + existing_rid = self._get_existing_rid() + tgt = self._get_tgt(creds, new_rid=existing_rid, + remove_requester_sid=True) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + # Test changing the SID in the PAC to a non-existent one. def test_tgs_sid_mismatch_nonexisting(self): creds = self._get_creds() @@ -652,6 +691,27 @@ class KdcTgsTests(KDCBaseTest): self._user2user(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + def test_requester_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid, + can_modify_logon_info=False) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_logon_info_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid, + can_modify_requester_sid=False) + self._run_tgs(tgt, expected_error=0) + + def test_logon_info_only_sid_mismatch_nonexisting(self): + creds = self._get_creds() + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, new_rid=nonexistent_rid, + remove_requester_sid=True) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + # Test with an RODC-issued ticket where the client is revealed to the RODC. def test_tgs_rodc_revealed(self): creds = self._get_creds(replication_allowed=True, @@ -728,6 +788,33 @@ class KdcTgsTests(KDCBaseTest): self._user2user(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + def test_tgs_rodc_requester_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid, + can_modify_logon_info=False) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_tgs_rodc_logon_info_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid, + can_modify_requester_sid=False) + self._run_tgs(tgt, expected_error=0) + + def test_tgs_rodc_logon_info_only_sid_mismatch_existing(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + existing_rid = self._get_existing_rid(replication_allowed=True, + revealed_to_rodc=True) + tgt = self._get_tgt(creds, from_rodc=True, new_rid=existing_rid, + remove_requester_sid=True) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + # Test with an RODC-issued ticket where the SID in the PAC is changed to a # non-existent one. def test_tgs_rodc_sid_mismatch_nonexisting(self): @@ -768,6 +855,30 @@ class KdcTgsTests(KDCBaseTest): self._user2user(tgt, creds, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + def test_tgs_rodc_requester_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid, + can_modify_logon_info=False) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + + def test_tgs_rodc_logon_info_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid, + can_modify_requester_sid=False) + self._run_tgs(tgt, expected_error=0) + + def test_tgs_rodc_logon_info_only_sid_mismatch_nonexisting(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + nonexistent_rid = self._get_non_existent_rid() + tgt = self._get_tgt(creds, from_rodc=True, new_rid=nonexistent_rid, + remove_requester_sid=True) + self._run_tgs(tgt, expected_error=KDC_ERR_CLIENT_NAME_MISMATCH) + # Test with an RODC-issued ticket where the client is not revealed to the # RODC. def test_tgs_rodc_not_revealed(self): @@ -1235,6 +1346,99 @@ class KdcTgsTests(KDCBaseTest): expect_pac_attrs=True, expect_pac_attrs_pac_request=True) + def test_as_requester_sid(self): + creds = self._get_creds() + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + + def test_tgs_requester_sid(self): + creds = self._get_creds() + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + + self._run_tgs(tgt, expected_error=0, expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + + def test_tgs_requester_sid_renew(self): + creds = self._get_creds() + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + tgt = self._modify_tgt(tgt, renewable=True) + + self._renew_tgt(tgt, expected_error=0, expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + + def test_tgs_requester_sid_rodc_renew(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True) + + self._renew_tgt(tgt, expected_error=0, expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + + def test_tgs_requester_sid_missing_renew(self): + creds = self._get_creds() + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + tgt = self._modify_tgt(tgt, renewable=True, + remove_requester_sid=True) + + self._renew_tgt(tgt, expected_error=0, expect_pac=True, + expect_requester_sid=False) # Note: not expected + + def test_tgs_requester_sid_missing_rodc_renew(self): + creds = self._get_creds(replication_allowed=True, + revealed_to_rodc=True) + + samdb = self.get_samdb() + sid = self.get_objectSid(samdb, creds.get_dn()) + + tgt = self.get_tgt(creds, pac_request=None, + expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + tgt = self._modify_tgt(tgt, from_rodc=True, renewable=True, + remove_requester_sid=True) + + self._renew_tgt(tgt, expected_error=0, expect_pac=True, + expected_sid=sid, + expect_requester_sid=True) + def test_tgs_pac_request_none(self): creds = self._get_creds() tgt = self.get_tgt(creds, pac_request=None) @@ -1733,16 +1937,20 @@ class KdcTgsTests(KDCBaseTest): return (1 << 30) - 1 def _run_tgs(self, tgt, expected_error, expect_pac=True, - expect_pac_attrs=None, expect_pac_attrs_pac_request=None): + expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, expected_sid=None): target_creds = self.get_service_creds() return self._tgs_req( tgt, expected_error, target_creds, expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, - expect_pac_attrs_pac_request=expect_pac_attrs_pac_request) + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, + expected_sid=expected_sid) def _renew_tgt(self, tgt, expected_error, expect_pac=True, - expect_pac_attrs=None, expect_pac_attrs_pac_request=None): + expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, expected_sid=None): krbtgt_creds = self.get_krbtgt_creds() kdc_options = str(krb5_asn1.KDCOptions('renew')) return self._tgs_req( @@ -1750,7 +1958,9 @@ class KdcTgsTests(KDCBaseTest): kdc_options=kdc_options, expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, - expect_pac_attrs_pac_request=expect_pac_attrs_pac_request) + expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, + expected_sid=expected_sid) def _validate_tgt(self, tgt, expected_error, expect_pac=True): krbtgt_creds = self.get_krbtgt_creds() @@ -1809,7 +2019,9 @@ class KdcTgsTests(KDCBaseTest): expect_pac=True, expect_pac_attrs=None, expect_pac_attrs_pac_request=None, + expect_requester_sid=None, expect_edata=False, + expected_sid=None, expected_status=None): srealm = target_creds.get_realm() @@ -1865,6 +2077,8 @@ class KdcTgsTests(KDCBaseTest): expect_pac=expect_pac, expect_pac_attrs=expect_pac_attrs, expect_pac_attrs_pac_request=expect_pac_attrs_pac_request, + expect_requester_sid=expect_requester_sid, + expected_sid=expected_sid, expect_claims=expect_claims) rep = self._generic_kdc_exchange(kdc_exchange_dict, diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index e6fad91b4020..41ad710d2f22 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -379,3 +379,21 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true +# +# PAC requester SID tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_as_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_rodc_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_rodc_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_nonexisting diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 17a9792c6192..cf3fc5abbaf3 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -507,3 +507,23 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_pac_request_false +# +# PAC requester SID tests +# +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_as_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_rodc_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_rodc_renew +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_nonexisting -- 2.25.1 From e2cc1b4b8a668fddb42f917fe053d1ef276c7dfe Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:06:58 +1300 Subject: [PATCH 176/224] CVE-2020-25719 tests/krb5: Add test for user-to-user with no sname BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 38 +++++++++++++++++------- selftest/knownfail_heimdal_kdc | 1 + selftest/knownfail_mit_kdc | 1 + 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index 2005d71fa81a..b0f60c0a8ce9 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1122,6 +1122,14 @@ class KdcTgsTests(KDCBaseTest): self._user2user(tgt, creds, sname=sname, expected_error=KDC_ERR_S_PRINCIPAL_UNKNOWN) + def test_user2user_no_sname(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + self._user2user(tgt, creds, sname=False, + expected_error=(KDC_ERR_GENERIC, + KDC_ERR_S_PRINCIPAL_UNKNOWN)) + def test_user2user_service_ticket(self): creds = self._get_creds() tgt = self._get_tgt(creds) @@ -2025,16 +2033,24 @@ class KdcTgsTests(KDCBaseTest): expected_status=None): srealm = target_creds.get_realm() - if sname is None: - target_name = target_creds.get_username() - if target_name == 'krbtgt': - sname = self.PrincipalName_create(name_type=NT_SRV_INST, - names=[target_name, srealm]) - else: - if target_name[-1] == '$': - target_name = target_name[:-1] - sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, - names=['host', target_name]) + if sname is False: + sname = None + expected_sname = self.get_krbtgt_sname() + else: + if sname is None: + target_name = target_creds.get_username() + if target_name == 'krbtgt': + sname = self.PrincipalName_create( + name_type=NT_SRV_INST, + names=[target_name, srealm]) + else: + if target_name[-1] == '$': + target_name = target_name[:-1] + sname = self.PrincipalName_create( + name_type=NT_PRINCIPAL, + names=['host', target_name]) + + expected_sname = sname if additional_ticket is not None: additional_tickets = [additional_ticket.ticket] @@ -2062,7 +2078,7 @@ class KdcTgsTests(KDCBaseTest): expected_crealm=tgt.crealm, expected_cname=expected_cname, expected_srealm=srealm, - expected_sname=sname, + expected_sname=expected_sname, ticket_decryption_key=decryption_key, generate_padata_fn=generate_padata_fn, check_error_fn=check_error_fn, diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 41ad710d2f22..fc2917761a16 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -323,6 +323,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_non_existent_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index cf3fc5abbaf3..aa66f4cb0fc6 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -441,6 +441,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied -- 2.25.1 From 2a2eae3777a0e3bdb117aaf5bf7881ec018619ae Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:09:32 +1300 Subject: [PATCH 177/224] CVE-2020-25719 tests/krb5: Add tests for mismatched names with user-to-user BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 105 ++++++++++++++++++- python/samba/tests/krb5/rfc4120_constants.py | 1 + selftest/knownfail_heimdal_kdc | 8 ++ selftest/knownfail_mit_kdc | 8 ++ 4 files changed, 120 insertions(+), 2 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index b0f60c0a8ce9..cfe1ad42d615 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -43,8 +43,10 @@ from samba.tests.krb5.rfc4120_constants import ( KDC_ERR_GENERIC, KDC_ERR_MODIFIED, KDC_ERR_POLICY, + KDC_ERR_C_PRINCIPAL_UNKNOWN, KDC_ERR_S_PRINCIPAL_UNKNOWN, KDC_ERR_TGT_REVOKED, + KDC_ERR_WRONG_REALM, NT_PRINCIPAL, NT_SRV_INST, ) @@ -1112,6 +1114,100 @@ class KdcTgsTests(KDCBaseTest): expected_error=(KDC_ERR_BADMATCH, KDC_ERR_BADOPTION)) + def test_user2user_other_sname(self): + other_name = self.get_new_username() + spn = f'host/{other_name}' + creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={'spn': spn}) + tgt = self._get_tgt(creds) + + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['host', other_name]) + + self._user2user(tgt, creds, sname=sname, expected_error=0) + + def test_user2user_wrong_sname_krbtgt(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + sname = self.get_krbtgt_sname() + + self._user2user(tgt, creds, sname=sname, + expected_error=(KDC_ERR_BADMATCH, + KDC_ERR_BADOPTION)) + + def test_user2user_wrong_srealm(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + self._user2user(tgt, creds, srealm='OTHER.REALM', + expected_error=(KDC_ERR_WRONG_REALM, + KDC_ERR_S_PRINCIPAL_UNKNOWN)) + + def test_user2user_tgt_correct_realm(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + realm = creds.get_realm().encode('utf-8') + tgt = self._modify_tgt(tgt, realm) + + self._user2user(tgt, creds, + expected_error=0) + + def test_user2user_tgt_wrong_realm(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + tgt = self._modify_tgt(tgt, b'OTHER.REALM') + + self._user2user(tgt, creds, + expected_error=0) + + def test_user2user_tgt_correct_cname(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + user_name = creds.get_username() + user_name = user_name.encode('utf-8') + cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[user_name]) + + tgt = self._modify_tgt(tgt, cname=cname) + + self._user2user(tgt, creds, expected_error=0) + + def test_user2user_tgt_other_cname(self): + samdb = self.get_samdb() + + other_name = self.get_new_username() + upn = f'{other_name}@{samdb.domain_dns_name()}' + + creds = self.get_cached_creds( + account_type=self.AccountType.COMPUTER, + opts={'upn': upn}) + tgt = self._get_tgt(creds) + + cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[other_name.encode('utf-8')]) + + tgt = self._modify_tgt(tgt, cname=cname) + + self._user2user(tgt, creds, expected_error=0) + + def test_user2user_tgt_cname_host(self): + creds = self._get_creds() + tgt = self._get_tgt(creds) + + user_name = creds.get_username() + user_name = user_name.encode('utf-8') + cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[b'host', user_name]) + + tgt = self._modify_tgt(tgt, cname=cname) + + self._user2user(tgt, creds, expected_error=KDC_ERR_C_PRINCIPAL_UNKNOWN) + def test_user2user_non_existent_sname(self): creds = self._get_creds() tgt = self._get_tgt(creds) @@ -2005,7 +2101,7 @@ class KdcTgsTests(KDCBaseTest): expect_pac=expect_pac) def _user2user(self, tgt, tgt_creds, expected_error, sname=None, - user_tgt=None, expect_pac=True): + srealm=None, user_tgt=None, expect_pac=True): if user_tgt is None: user_creds = self._get_mach_creds() user_tgt = self.get_tgt(user_creds) @@ -2015,6 +2111,7 @@ class KdcTgsTests(KDCBaseTest): kdc_options=kdc_options, additional_ticket=tgt, sname=sname, + srealm=srealm, expect_pac=expect_pac) def _tgs_req(self, tgt, expected_error, target_creds, @@ -2023,6 +2120,7 @@ class KdcTgsTests(KDCBaseTest): additional_ticket=None, generate_padata_fn=None, sname=None, + srealm=None, expect_claims=True, expect_pac=True, expect_pac_attrs=None, @@ -2031,7 +2129,10 @@ class KdcTgsTests(KDCBaseTest): expect_edata=False, expected_sid=None, expected_status=None): - srealm = target_creds.get_realm() + if srealm is False: + srealm = None + elif srealm is None: + srealm = target_creds.get_realm() if sname is False: sname = None diff --git a/python/samba/tests/krb5/rfc4120_constants.py b/python/samba/tests/krb5/rfc4120_constants.py index 490cd255ec30..5251e291fde1 100644 --- a/python/samba/tests/krb5/rfc4120_constants.py +++ b/python/samba/tests/krb5/rfc4120_constants.py @@ -82,6 +82,7 @@ KDC_ERR_SKEW = 37 KDC_ERR_MODIFIED = 41 KDC_ERR_INAPP_CKSUM = 50 KDC_ERR_GENERIC = 60 +KDC_ERR_WRONG_REALM = 68 KDC_ERR_CLIENT_NAME_MISMATCH = 75 KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS = 93 diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index fc2917761a16..d1fb5f210afb 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -325,6 +325,7 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_non_existent_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_other_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied @@ -337,7 +338,14 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_cname_host +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_cname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_realm +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_other_cname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_wrong_realm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index aa66f4cb0fc6..04efccf4a591 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -442,6 +442,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_other_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied @@ -454,7 +455,14 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_cname_host +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_cname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_realm +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_other_cname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_wrong_realm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied -- 2.25.1 From 09033988f2c61cbc671809c5c31a3aa0feaa4df0 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 11:00:38 +1300 Subject: [PATCH 178/224] CVE-2020-25719 s4/torture: Expect additional PAC buffers BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 39 ++++++++++++++++++++++++++++++++ source4/torture/rpc/remote_pac.c | 24 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index d1fb5f210afb..850346940ad4 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -406,3 +406,42 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_nonexisting +# +# PAC tests +# +^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc:local +^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc_ntvfs:local +^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc:local +^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc_ntvfs:local +^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc:local +^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc_ntvfs:local +^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc:local +^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc_ntvfs:local +^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc:local +^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc_ntvfs:local +^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc:local +^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc_ntvfs:local +^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008r2dc +^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008r2dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008r2dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008r2dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008r2dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2000dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2003dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008dc +^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008r2dc diff --git a/source4/torture/rpc/remote_pac.c b/source4/torture/rpc/remote_pac.c index 14c23f674f1e..c94decef5ce5 100644 --- a/source4/torture/rpc/remote_pac.c +++ b/source4/torture/rpc/remote_pac.c @@ -266,7 +266,7 @@ static bool test_PACVerify(struct torture_context *tctx, (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed"); - num_pac_buffers = 5; + num_pac_buffers = 7; if (expect_pac_upn_dns_info) { num_pac_buffers += 1; } @@ -323,6 +323,18 @@ static bool test_PACVerify(struct torture_context *tctx, pac_buf->info != NULL, "PAC_TYPE_TICKET_CHECKSUM info"); + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_ATTRIBUTES_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_ATTRIBUTES_INFO"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_ATTRIBUTES_INFO info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_REQUESTER_SID); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_REQUESTER_SID"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_REQUESTER_SID info"); + ok = netlogon_validate_pac(tctx, p, server_creds, secure_channel_type, test_machine_name, negotiate_flags, pac_data, session_info); @@ -1082,7 +1094,7 @@ static bool test_S4U2Proxy(struct torture_context *tctx, (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed"); - num_pac_buffers = 7; + num_pac_buffers = 9; torture_assert_int_equal(tctx, pac_data_struct.version, 0, "version"); torture_assert_int_equal(tctx, pac_data_struct.num_buffers, num_pac_buffers, "num_buffers"); @@ -1122,6 +1134,14 @@ static bool test_S4U2Proxy(struct torture_context *tctx, talloc_asprintf(tctx, "%s@%s", self_princ, cli_credentials_get_realm(credentials)), "wrong transited_services[0]"); + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_ATTRIBUTES_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_ATTRIBUTES_INFO"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_ATTRIBUTES_INFO info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_REQUESTER_SID); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_REQUESTER_SID"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_REQUESTER_SID info"); + return netlogon_validate_pac(tctx, p, server_creds, secure_channel_type, test_machine_name, negotiate_flags, pac_data, session_info); } -- 2.25.1 From f8f23f8a042864b1c8872d7bdd42bdd1ed0ccb3c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 19:18:20 +1300 Subject: [PATCH 179/224] CVE-2020-25722 pytest: Raise an error when adding a dynamic test that would overwrite an existing test BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index 2c4c8fad83e4..3812f5bad4a4 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -100,7 +100,10 @@ class TestCase(unittest.TestCase): def fn(self): getattr(self, "_%s_with_args" % fnname)(*args) fn.__doc__ = doc - setattr(cls, "%s_%s" % (fnname, suffix), fn) + attr = "%s_%s" % (fnname, suffix) + if hasattr(cls, attr): + raise RuntimeError(f"Dynamic test {attr} already exists!") + setattr(cls, attr, fn) @classmethod def setUpDynamicTestCases(cls): -- 2.25.1 From 092648d5f9b95212ff9993bdfbcfb9fc46b60d9c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 12:32:12 +0200 Subject: [PATCH 180/224] CVE-2020-25719 mit-samba: Make ks_get_principal() internally public BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/mit-kdb/kdb_samba.h | 5 +++++ source4/kdc/mit-kdb/kdb_samba_principals.c | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h index ad4f6e275739..132dcfed363e 100644 --- a/source4/kdc/mit-kdb/kdb_samba.h +++ b/source4/kdc/mit-kdb/kdb_samba.h @@ -41,6 +41,11 @@ struct mit_samba_context *ks_get_context(krb5_context kcontext); +krb5_error_code ks_get_principal(krb5_context context, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **kentry); + bool ks_data_eq_string(krb5_data d, const char *s); krb5_data ks_make_data(void *data, unsigned int len); diff --git a/source4/kdc/mit-kdb/kdb_samba_principals.c b/source4/kdc/mit-kdb/kdb_samba_principals.c index 8b67436dc472..79219e5a2743 100644 --- a/source4/kdc/mit-kdb/kdb_samba_principals.c +++ b/source4/kdc/mit-kdb/kdb_samba_principals.c @@ -33,10 +33,10 @@ #define ADMIN_LIFETIME 60*60*3 /* 3 hours */ #define CHANGEPW_LIFETIME 60*5 /* 5 minutes */ -static krb5_error_code ks_get_principal(krb5_context context, - krb5_const_principal principal, - unsigned int kflags, - krb5_db_entry **kentry) +krb5_error_code ks_get_principal(krb5_context context, + krb5_const_principal principal, + unsigned int kflags, + krb5_db_entry **kentry) { struct mit_samba_context *mit_ctx; krb5_error_code code; -- 2.25.1 From 2461dadacd40732c8e0585c6e5986a102c801977 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Wed, 14 Jul 2021 14:51:34 +0200 Subject: [PATCH 181/224] CVE-2020-25719 mit-samba: Add ks_free_principal() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 [abartlet@samba.org As submitted in patch to Samba bugzilla to address this issue as https://attachments.samba.org/attachment.cgi?id=16724 on overall bug https://bugzilla.samba.org/show_bug.cgi?id=14725] Signed-off-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- source4/kdc/mit-kdb/kdb_samba.h | 2 + source4/kdc/mit-kdb/kdb_samba_principals.c | 52 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h index 132dcfed363e..2ff8642cc501 100644 --- a/source4/kdc/mit-kdb/kdb_samba.h +++ b/source4/kdc/mit-kdb/kdb_samba.h @@ -46,6 +46,8 @@ krb5_error_code ks_get_principal(krb5_context context, unsigned int kflags, krb5_db_entry **kentry); +void ks_free_principal(krb5_context context, krb5_db_entry *entry); + bool ks_data_eq_string(krb5_data d, const char *s); krb5_data ks_make_data(void *data, unsigned int len); diff --git a/source4/kdc/mit-kdb/kdb_samba_principals.c b/source4/kdc/mit-kdb/kdb_samba_principals.c index 79219e5a2743..cc67c2392be1 100644 --- a/source4/kdc/mit-kdb/kdb_samba_principals.c +++ b/source4/kdc/mit-kdb/kdb_samba_principals.c @@ -59,6 +59,58 @@ cleanup: return code; } +static void ks_free_principal_e_data(krb5_context context, krb5_octet *e_data) +{ + struct samba_kdc_entry *skdc_entry; + + skdc_entry = talloc_get_type_abort(e_data, + struct samba_kdc_entry); + talloc_set_destructor(skdc_entry, NULL); + TALLOC_FREE(skdc_entry); +} + +void ks_free_principal(krb5_context context, krb5_db_entry *entry) +{ + krb5_tl_data *tl_data_next = NULL; + krb5_tl_data *tl_data = NULL; + size_t i, j; + + if (entry != NULL) { + krb5_free_principal(context, entry->princ); + + for (tl_data = entry->tl_data; tl_data; tl_data = tl_data_next) { + tl_data_next = tl_data->tl_data_next; + if (tl_data->tl_data_contents != NULL) { + free(tl_data->tl_data_contents); + } + free(tl_data); + } + + if (entry->key_data != NULL) { + for (i = 0; i < entry->n_key_data; i++) { + for (j = 0; j < entry->key_data[i].key_data_ver; j++) { + if (entry->key_data[i].key_data_length[j] != 0) { + if (entry->key_data[i].key_data_contents[j] != NULL) { + memset(entry->key_data[i].key_data_contents[j], 0, entry->key_data[i].key_data_length[j]); + free(entry->key_data[i].key_data_contents[j]); + } + } + entry->key_data[i].key_data_contents[j] = NULL; + entry->key_data[i].key_data_length[j] = 0; + entry->key_data[i].key_data_type[j] = 0; + } + } + free(entry->key_data); + } + + if (entry->e_data) { + ks_free_principal_e_data(context, entry->e_data); + } + + free(entry); + } +} + static krb5_boolean ks_is_master_key_principal(krb5_context context, krb5_const_principal princ) { -- 2.25.1 From 851a5a26708b42fd5c62f11c84103683182ce893 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 11:20:29 +0200 Subject: [PATCH 182/224] CVE-2020-25719 mit-samba: If we use client_princ, always lookup the db entry BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett [abartlet@samba.org backported due to support for MIT KDB < 10 in Samba 4.14] --- source4/kdc/mit-kdb/kdb_samba_policies.c | 81 ++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/source4/kdc/mit-kdb/kdb_samba_policies.c b/source4/kdc/mit-kdb/kdb_samba_policies.c index 9197551ed619..dce87c500497 100644 --- a/source4/kdc/mit-kdb/kdb_samba_policies.c +++ b/source4/kdc/mit-kdb/kdb_samba_policies.c @@ -323,6 +323,8 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, krb5_authdata ***signed_auth_data) { #endif + krb5_const_principal ks_client_princ = NULL; + krb5_db_entry *client_entry = NULL; krb5_authdata **authdata = NULL; krb5_boolean is_as_req; krb5_error_code code; @@ -341,8 +343,72 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0); + /* + * When using s4u2proxy client_princ actually refers to the proxied user + * while client->princ to the proxy service asking for the TGS on behalf + * of the proxied user. So always use client_princ in preference. + * + * Note that when client principal is not NULL, client entry might be + * NULL for cross-realm case, so we need to make sure to not + * dereference NULL pointer here. + */ + if (client_princ != NULL) { + ks_client_princ = client_princ; + if (!is_as_req) { + krb5_boolean is_equal = false; + + if (client != NULL && client->princ != NULL) { + is_equal = + krb5_principal_compare(context, + client_princ, + client->princ); + } + + /* + * When client principal is the same as supplied client + * entry, don't fetch it. + */ + if (!is_equal) { + code = ks_get_principal(context, + ks_client_princ, + 0, + &client_entry); + if (code != 0) { + char *client_name = NULL; + + (void)krb5_unparse_name(context, + ks_client_princ, + &client_name); + + DBG_DEBUG("We didn't find the client " + "principal [%s] in our " + "database.\n", + client_name); + SAFE_FREE(client_name); + + /* + * If we didn't find client_princ in our + * database it might be from another + * realm. + */ + client_entry = NULL; + } + } + } + } else { + if (client == NULL) { + *signed_auth_data = NULL; + return 0; + } + ks_client_princ = client->princ; + } + + if (client_entry == NULL) { + client_entry = client; + } + if (is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC)) { - code = ks_get_pac(context, client, client_key, &pac); + code = ks_get_pac(context, client_entry, client_key, &pac); if (code != 0) { goto done; } @@ -351,8 +417,8 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, if (!is_as_req) { code = ks_verify_pac(context, flags, - client_princ, - client, + ks_client_princ, + client_entry, server, krbtgt, server_key, @@ -365,9 +431,9 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, } } - if (pac == NULL && client != NULL) { + if (pac == NULL) { - code = ks_get_pac(context, client, client_key, &pac); + code = ks_get_pac(context, client_entry, client_key, &pac); if (code != 0) { goto done; } @@ -378,7 +444,7 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, goto done; } - code = krb5_pac_sign(context, pac, authtime, client_princ, + code = krb5_pac_sign(context, pac, authtime, ks_client_princ, server_key, krbtgt_key, &pac_data); if (code != 0) { DBG_ERR("krb5_pac_sign failed: %d\n", code); @@ -412,6 +478,9 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, code = 0; done: + if (client_entry != NULL && client_entry != client) { + ks_free_principal(context, client_entry); + } krb5_pac_free(context, pac); krb5_free_authdata(context, authdata); -- 2.25.1 From cc54cb7d71dafad9e319799a9dd9974f59c7cca1 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 13:12:00 +0200 Subject: [PATCH 183/224] CVE-2020-25719 mit-samba: Add mit_samba_princ_needs_pac() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/mit_samba.c | 8 ++++++++ source4/kdc/mit_samba.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index 689e14e1c38d..6aed31345441 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -1153,3 +1153,11 @@ void mit_samba_update_bad_password_count(krb5_db_entry *db_entry) p->msg, ldb_get_default_basedn(p->kdc_db_ctx->samdb)); } + +bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry) +{ + struct samba_kdc_entry *skdc_entry = + talloc_get_type_abort(db_entry->e_data, struct samba_kdc_entry); + + return samba_princ_needs_pac(skdc_entry); +} diff --git a/source4/kdc/mit_samba.h b/source4/kdc/mit_samba.h index ba824557bd51..636c77ec97cb 100644 --- a/source4/kdc/mit_samba.h +++ b/source4/kdc/mit_samba.h @@ -85,4 +85,6 @@ void mit_samba_zero_bad_password_count(krb5_db_entry *db_entry); void mit_samba_update_bad_password_count(krb5_db_entry *db_entry); +bool mit_samba_princ_needs_pac(krb5_db_entry *db_entry); + #endif /* _MIT_SAMBA_H */ -- 2.25.1 From f8581ac48dd6c0d8c660776b5a34280bce8caf8a Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 13:58:57 +0200 Subject: [PATCH 184/224] CVE-2020-25719 mit-samba: Handle no DB entry in mit_samba_get_pac() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/mit_samba.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index 6aed31345441..be6ea83c0420 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -437,6 +437,10 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, &upn_dns_info_blob); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(tmp_ctx); + if (NT_STATUS_EQUAL(nt_status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return ENOENT; + } return EINVAL; } -- 2.25.1 From b651e9a26b0389047bec8b5cc95f8778c9626fc7 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 14:00:19 +0200 Subject: [PATCH 185/224] CVE-2020-25719 mit-samba: Rework PAC handling in kdb_samba_db_sign_auth_data() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- selftest/knownfail_mit_kdc | 6 +- source4/kdc/mit-kdb/kdb_samba_policies.c | 116 ++++++++++++++++++----- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 04efccf4a591..53aa05814c27 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -278,12 +278,13 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ # # KDC TGS PAC tests # +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_client_no_auth_data_required\(ad_dc\) +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_client_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required\(ad_dc\) ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac\(ad_dc\) -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_service_no_auth_data_required\(ad_dc\) # # MIT currently fails the following MS-KILE tests. # @@ -501,11 +502,9 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ # # PAC request tests # -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_pac_request_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_pac_request_true @@ -515,7 +514,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_pac_request_false # # PAC requester SID tests # diff --git a/source4/kdc/mit-kdb/kdb_samba_policies.c b/source4/kdc/mit-kdb/kdb_samba_policies.c index dce87c500497..7bc9a7b3347c 100644 --- a/source4/kdc/mit-kdb/kdb_samba_policies.c +++ b/source4/kdc/mit-kdb/kdb_samba_policies.c @@ -4,7 +4,7 @@ Samba KDB plugin for MIT Kerberos Copyright (c) 2010 Simo Sorce . - Copyright (c) 2014 Andreas Schneider + Copyright (c) 2014-2021 Andreas Schneider This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -325,11 +325,16 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, #endif krb5_const_principal ks_client_princ = NULL; krb5_db_entry *client_entry = NULL; + krb5_authdata **pac_auth_data = NULL; krb5_authdata **authdata = NULL; krb5_boolean is_as_req; krb5_error_code code; krb5_pac pac = NULL; krb5_data pac_data; + bool with_pac = false; + bool generate_pac = false; + char *client_name = NULL; + #if KRB5_KDB_API_VERSION >= 10 krbtgt = krbtgt == NULL ? local_krbtgt : krbtgt; @@ -374,8 +379,6 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, 0, &client_entry); if (code != 0) { - char *client_name = NULL; - (void)krb5_unparse_name(context, ks_client_princ, &client_name); @@ -407,43 +410,105 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, client_entry = client; } - if (is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC)) { + if (is_as_req) { + with_pac = mit_samba_princ_needs_pac(client_entry); + } else { + with_pac = mit_samba_princ_needs_pac(server); + } + + code = krb5_unparse_name(context, + client_princ, + &client_name); + if (code != 0) { + goto done; + } + + if (is_as_req && (flags & KRB5_KDB_FLAG_INCLUDE_PAC) != 0) { + generate_pac = true; + } + + DBG_DEBUG("*** Sign data for client principal: %s [%s %s%s]\n", + client_name, + is_as_req ? "AS-REQ" : "TGS_REQ", + with_pac ? is_as_req ? "WITH_PAC" : "FIND_PAC" : "NO_PAC", + generate_pac ? " GENERATE_PAC" : ""); + + /* + * Generate PAC for the AS-REQ or check or generate one for the TGS if + * needed. + */ + if (with_pac && generate_pac) { + DBG_DEBUG("Generate PAC for AS-REQ [%s]\n", client_name); code = ks_get_pac(context, client_entry, client_key, &pac); if (code != 0) { goto done; } - } - - if (!is_as_req) { - code = ks_verify_pac(context, - flags, - ks_client_princ, - client_entry, - server, - krbtgt, - server_key, - krbtgt_key, - authtime, - tgt_auth_data, - &pac); + } else if (with_pac && !is_as_req) { + /* + * Find the PAC in the TGS, if one exists. + */ + code = krb5_find_authdata(context, + tgt_auth_data, + NULL, + KRB5_AUTHDATA_WIN2K_PAC, + &pac_auth_data); if (code != 0) { + DBG_ERR("krb5_find_authdata failed: %d\n", code); goto done; } - } + DBG_DEBUG("Found PAC data for TGS-REQ [%s]\n", client_name); - if (pac == NULL) { + if (pac_auth_data != NULL && pac_auth_data[0] != NULL) { + if (pac_auth_data[1] != NULL) { + DBG_ERR("Invalid PAC data!\n"); + code = KRB5KDC_ERR_BADOPTION; + goto done; + } - code = ks_get_pac(context, client_entry, client_key, &pac); - if (code != 0) { - goto done; + DBG_DEBUG("Verify PAC for TGS [%s]\n", + client_name); + + code = ks_verify_pac(context, + flags, + ks_client_princ, + client_entry, + server, + krbtgt, + server_key, + krbtgt_key, + authtime, + tgt_auth_data, + &pac); + if (code != 0) { + goto done; + } + } else { + if (flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) { + DBG_DEBUG("Generate PAC for constrained" + "delegation TGS [%s]\n", + client_name); + + code = ks_get_pac(context, + client_entry, + client_key, + &pac); + if (code != 0 && code != ENOENT) { + goto done; + } + } } } if (pac == NULL) { - code = KRB5_KDB_DBTYPE_NOSUP; + DBG_DEBUG("No PAC data - we're done [%s]\n", client_name); + *signed_auth_data = NULL; + code = 0; goto done; } + DBG_DEBUG("Signing PAC for %s [%s]\n", + is_as_req ? "AS-REQ" : "TGS-REQ", + client_name); code = krb5_pac_sign(context, pac, authtime, ks_client_princ, server_key, krbtgt_key, &pac_data); if (code != 0) { @@ -481,8 +546,9 @@ done: if (client_entry != NULL && client_entry != client) { ks_free_principal(context, client_entry); } - krb5_pac_free(context, pac); + SAFE_FREE(client_name); krb5_free_authdata(context, authdata); + krb5_pac_free(context, pac); return code; } -- 2.25.1 From 631be3dbeeb4bcd2926fa824e63745611efc4df2 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:22:52 +0200 Subject: [PATCH 186/224] CVE-2020-25719 mit_samba: The samba_princ_needs_pac check should be on the server entry This does the same check as the hdb plugin now. The client check is already done earlier. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/mit_samba.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index be6ea83c0420..d11e1640ee98 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -486,6 +486,7 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, DATA_BLOB *deleg_blob = NULL; struct samba_kdc_entry *client_skdc_entry = NULL; struct samba_kdc_entry *krbtgt_skdc_entry = NULL; + struct samba_kdc_entry *server_skdc_entry = NULL; bool is_in_db = false; bool is_untrusted = false; size_t num_types = 0; @@ -499,6 +500,7 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, ssize_t srv_checksum_idx = -1; ssize_t kdc_checksum_idx = -1; krb5_pac new_pac = NULL; + bool ok; if (client != NULL) { client_skdc_entry = @@ -510,6 +512,16 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, return EINVAL; } + server_skdc_entry = + talloc_get_type_abort(server->e_data, + struct samba_kdc_entry); + + /* The account may be set not to want the PAC */ + ok = samba_princ_needs_pac(server_skdc_entry); + if (!ok) { + return EINVAL; + } + if (krbtgt == NULL) { return EINVAL; } -- 2.25.1 From 0b37e1e74af7129a66737acc3dc2c16d6151e58e Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:25:53 +0200 Subject: [PATCH 187/224] CVE-2020-25719 mit_samba: Create the talloc context earlier BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/mit_samba.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index d11e1640ee98..d0e68ec8ea49 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -502,6 +502,12 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, krb5_pac new_pac = NULL; bool ok; + /* Create a memory context early so code can use talloc_stackframe() */ + tmp_ctx = talloc_named(ctx, 0, "mit_samba_reget_pac context"); + if (tmp_ctx == NULL) { + return ENOMEM; + } + if (client != NULL) { client_skdc_entry = talloc_get_type_abort(client->e_data, @@ -509,7 +515,8 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, } if (server == NULL) { - return EINVAL; + code = EINVAL; + goto done; } server_skdc_entry = @@ -519,21 +526,18 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, /* The account may be set not to want the PAC */ ok = samba_princ_needs_pac(server_skdc_entry); if (!ok) { - return EINVAL; + code = EINVAL; + goto done; } if (krbtgt == NULL) { - return EINVAL; + code = EINVAL; + goto done; } krbtgt_skdc_entry = talloc_get_type_abort(krbtgt->e_data, struct samba_kdc_entry); - tmp_ctx = talloc_named(ctx, 0, "mit_samba_reget_pac context"); - if (tmp_ctx == NULL) { - return ENOMEM; - } - code = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_untrusted); -- 2.25.1 From 65b32460061c357b70e480fb21a81b085e9d67b4 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 6 Aug 2021 12:03:49 +0200 Subject: [PATCH 188/224] CVE-2020-25719 s4:kdc: Remove trailing spaces in pac-glue.c BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/pac-glue.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 688103d8477b..4066389e7172 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -575,12 +575,12 @@ int samba_krbtgt_is_in_db(struct samba_kdc_entry *p, if (!mem_ctx) { return ENOMEM; } - + trust_direction = ldb_msg_find_attr_as_int(p->msg, "trustDirection", 0); if (trust_direction != 0) { /* Domain trust - we cannot check the sig, but we trust it for a correct PAC - + This is exactly where we should flag for SID validation when we do inter-foreest trusts */ @@ -768,7 +768,7 @@ NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, return nt_status; } - nt_status = samba_get_logon_info_pac_blob(mem_ctx, + nt_status = samba_get_logon_info_pac_blob(mem_ctx, user_info_dc, pac_blob); return nt_status; -- 2.25.1 From d01e4854acabfc955b69340ae55262ef38faf4c3 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:19:45 +0200 Subject: [PATCH 189/224] CVE-2020-25719 s4:kdc: Add samba_kdc_validate_pac_blob() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/pac-glue.c | 56 ++++++++++++++++++++++++++++++++++++++++++ source4/kdc/pac-glue.h | 5 ++++ 2 files changed, 61 insertions(+) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 4066389e7172..8a3ec22190c8 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -918,3 +918,59 @@ NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, talloc_free(tmp_ctx); return nt_status; } + +/* Does a parse and SID check, but no crypto. */ +krb5_error_code samba_kdc_validate_pac_blob( + krb5_context context, + struct samba_kdc_entry *client_skdc_entry, + const krb5_pac pac) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct auth_user_info_dc *pac_user_info = NULL; + struct dom_sid *client_sid = NULL; + struct dom_sid pac_sid; + krb5_error_code code; + bool ok; + + code = kerberos_pac_to_user_info_dc(frame, + pac, + context, + &pac_user_info, + NULL, + NULL); + if (code != 0) { + goto out; + } + + if (pac_user_info->num_sids == 0) { + code = EINVAL; + goto out; + } + + pac_sid = pac_user_info->sids[0]; + client_sid = samdb_result_dom_sid(frame, + client_skdc_entry->msg, + "objectSid"); + + ok = dom_sid_equal(&pac_sid, client_sid); + if (!ok) { + struct dom_sid_buf buf1; + struct dom_sid_buf buf2; + + DBG_ERR("SID mismatch between PAC and looked up client: " + "PAC[%s] != CLI[%s]\n", + dom_sid_str_buf(&pac_sid, &buf1), + dom_sid_str_buf(client_sid, &buf2)); +#if defined(KRB5KDC_ERR_CLIENT_NAME_MISMATCH) /* MIT */ + code = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; +#else /* Heimdal (where this is an enum) */ + code = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH; +#endif + goto out; + } + + code = 0; +out: + TALLOC_FREE(frame); + return code; +} diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h index 7b51b0389f5e..e83446647b33 100644 --- a/source4/kdc/pac-glue.h +++ b/source4/kdc/pac-glue.h @@ -69,3 +69,8 @@ NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, const char *client_name, const char *workstation, bool password_change); + +krb5_error_code samba_kdc_validate_pac_blob( + krb5_context context, + struct samba_kdc_entry *client_skdc_entry, + const krb5_pac pac); -- 2.25.1 From 227a1002ce36014c1d835840ea7f3b7244bab842 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:20:31 +0200 Subject: [PATCH 190/224] CVE-2020-25719 s4:kdc: Check if the pac is valid before updating it BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 31 ++++--------------------------- selftest/knownfail_mit_kdc | 10 ++-------- source4/kdc/mit_samba.c | 9 +++++++++ source4/kdc/wdc-samba4.c | 17 +++++++++++++++++ 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 850346940ad4..75ca4bba8c4d 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -271,13 +271,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac # -# Alias tests -# -^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_delete -^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_rename -^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_delete -^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_dc_alias_rename -# # KDC TGT tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac @@ -288,10 +281,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied @@ -300,10 +289,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied @@ -312,10 +297,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user @@ -354,10 +335,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting # # PAC attributes tests # @@ -392,8 +369,8 @@ # PAC requester SID tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_as_requester_sid -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_requester_sid @@ -402,8 +379,8 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_rodc_renew ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_renew ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_rodc_renew -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_nonexisting # diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index 53aa05814c27..dfdd17c655a3 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -407,8 +407,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_req @@ -434,8 +432,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user @@ -475,8 +471,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_sid_mismatch_nonexisting # # PAC attributes tests # @@ -518,8 +512,8 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ # PAC requester SID tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_as_requester_sid -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_only_sid_mismatch_nonexisting +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_existing +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_requester_sid diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index d0e68ec8ea49..592f6a3bac47 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -512,6 +512,15 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, client_skdc_entry = talloc_get_type_abort(client->e_data, struct samba_kdc_entry); + + /* + * Check the objectSID of the client and pac data are the same. + * Does a parse and SID check, but no crypto. + */ + code = samba_kdc_validate_pac_blob(context, client_skdc_entry, *pac); + if (code != 0) { + goto done; + } } if (server == NULL) { diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index ac9d7d517338..ed6e9fb9b633 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -137,6 +137,23 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, return ENOMEM; } + if (client != NULL) { + struct samba_kdc_entry *client_skdc_entry = NULL; + + client_skdc_entry = talloc_get_type_abort(client->ctx, + struct samba_kdc_entry); + + /* + * Check the objectSID of the client and pac data are the same. + * Does a parse and SID check, but no crypto. + */ + ret = samba_kdc_validate_pac_blob(context, client_skdc_entry, *pac); + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + } + /* If the krbtgt was generated by an RODC, and we are not that * RODC, then we need to regenerate the PAC - we can't trust * it */ -- 2.25.1 From 907cef8f3c83d10d4fc9e191cb31a39d7967b56f Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:41:31 +1300 Subject: [PATCH 191/224] CVE-2020-25719 s4:kdc: Add KDC support for PAC_ATTRIBUTES_INFO PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 23 ----- source4/heimdal/kdc/kerberos5.c | 23 +++-- source4/heimdal/kdc/krb5tgs.c | 2 +- source4/heimdal/kdc/windc.c | 7 +- source4/heimdal/kdc/windc_plugin.h | 2 + source4/kdc/mit_samba.c | 7 +- source4/kdc/pac-glue.c | 147 ++++++++++++++++++++++++++++- source4/kdc/pac-glue.h | 10 +- source4/kdc/wdc-samba4.c | 45 ++++++++- 9 files changed, 223 insertions(+), 43 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 75ca4bba8c4d..0f03e17c2428 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -265,11 +265,9 @@ # # KDC TGS PAC tests # -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_no_pac_service_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_request_no_pac # # KDC TGT tests # @@ -336,27 +334,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed # -# PAC attributes tests -# -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_renew_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_missing_rodc_renew_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_renew_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_rodc_renew_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_pac_attrs_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_pac_attrs_true -# # PAC request tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_false diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c index a131f1af08e7..c1d4cb1d4aa1 100644 --- a/source4/heimdal/kdc/kerberos5.c +++ b/source4/heimdal/kdc/kerberos5.c @@ -913,27 +913,30 @@ _kdc_check_addresses(krb5_context context, */ static krb5_boolean -send_pac_p(krb5_context context, KDC_REQ *req) +send_pac_p(krb5_context context, KDC_REQ *req, krb5_boolean *pac_request) { krb5_error_code ret; PA_PAC_REQUEST pacreq; const PA_DATA *pa; int i = 0; + *pac_request = TRUE; + pa = _kdc_find_padata(req, &i, KRB5_PADATA_PA_PAC_REQUEST); if (pa == NULL) - return TRUE; + return FALSE; ret = decode_PA_PAC_REQUEST(pa->padata_value.data, pa->padata_value.length, &pacreq, NULL); if (ret) - return TRUE; + return FALSE; i = pacreq.include_pac; free_PA_PAC_REQUEST(&pacreq); - if (i == 0) - return FALSE; + if (i == 0) { + *pac_request = FALSE; + } return TRUE; } @@ -1757,13 +1760,19 @@ _kdc_as_rep(krb5_context context, } /* Add the PAC */ - if (send_pac_p(context, req)) { + { krb5_pac p = NULL; krb5_data data; uint16_t rodc_id; krb5_principal client_pac; + krb5_boolean sent_pac_request; + krb5_boolean pac_request; + + sent_pac_request = send_pac_p(context, req, &pac_request); - ret = _kdc_pac_generate(context, client, pk_reply_key, &p); + ret = _kdc_pac_generate(context, client, pk_reply_key, + sent_pac_request ? &pac_request : NULL, + &p); if (ret) { kdc_log(context, config, 0, "PAC generation failed for -- %s", client_name); diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 7e9379db64a3..301ca92091a9 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1762,7 +1762,7 @@ server_lookup: if (mspac) { krb5_pac_free(context, mspac); mspac = NULL; - ret = _kdc_pac_generate(context, s4u2self_impersonated_client, NULL, &mspac); + ret = _kdc_pac_generate(context, s4u2self_impersonated_client, NULL, NULL, &mspac); if (ret) { kdc_log(context, config, 0, "PAC generation failed for -- %s", tpn); diff --git a/source4/heimdal/kdc/windc.c b/source4/heimdal/kdc/windc.c index 43dc89e2bc01..93b973f576b3 100644 --- a/source4/heimdal/kdc/windc.c +++ b/source4/heimdal/kdc/windc.c @@ -74,6 +74,7 @@ krb5_error_code _kdc_pac_generate(krb5_context context, hdb_entry_ex *client, const krb5_keyblock *pk_reply_key, + const krb5_boolean *pac_request, krb5_pac *pac) { *pac = NULL; @@ -87,8 +88,10 @@ _kdc_pac_generate(krb5_context context, if (windcft->pac_pk_generate != NULL && pk_reply_key != NULL) return (windcft->pac_pk_generate)(windcctx, context, - client, pk_reply_key, pac); - return (windcft->pac_generate)(windcctx, context, client, pac); + client, pk_reply_key, + pac_request, pac); + return (windcft->pac_generate)(windcctx, context, client, + pac_request, pac); } krb5_error_code diff --git a/source4/heimdal/kdc/windc_plugin.h b/source4/heimdal/kdc/windc_plugin.h index dda258da3d11..c7f2bcb5ed9e 100644 --- a/source4/heimdal/kdc/windc_plugin.h +++ b/source4/heimdal/kdc/windc_plugin.h @@ -55,12 +55,14 @@ struct hdb_entry_ex; typedef krb5_error_code (*krb5plugin_windc_pac_generate)(void *, krb5_context, struct hdb_entry_ex *, /* client */ + const krb5_boolean *, /* pac_request */ krb5_pac *); typedef krb5_error_code (*krb5plugin_windc_pac_pk_generate)(void *, krb5_context, struct hdb_entry_ex *, /* client */ const krb5_keyblock *, /* pk_replykey */ + const krb5_boolean *, /* pac_request */ krb5_pac *); typedef krb5_error_code diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index 592f6a3bac47..69cdbfba929f 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -434,7 +434,8 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, skdc_entry, &logon_info_blob, cred_ndr_ptr, - &upn_dns_info_blob); + &upn_dns_info_blob, + NULL, NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(tmp_ctx); if (NT_STATUS_EQUAL(nt_status, @@ -462,6 +463,7 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, pcred_blob, upn_dns_info_blob, NULL, + NULL, pac); talloc_free(tmp_ctx); @@ -564,7 +566,8 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, client_skdc_entry, &pac_blob, NULL, - &upn_blob); + &upn_blob, + NULL, NULL); if (!NT_STATUS_IS_OK(nt_status)) { code = EINVAL; goto done; diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 8a3ec22190c8..06019e579ebb 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -113,6 +113,43 @@ NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } +static +NTSTATUS samba_get_pac_attrs_blob(TALLOC_CTX *mem_ctx, + const krb5_boolean *pac_request, + DATA_BLOB *pac_attrs_data) +{ + union PAC_INFO pac_attrs; + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + + ZERO_STRUCT(pac_attrs); + + *pac_attrs_data = data_blob_null; + + /* Set the length of the flags in bits. */ + pac_attrs.attributes_info.flags_length = 2; + + if (pac_request == NULL) { + pac_attrs.attributes_info.flags + |= PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY; + } else if (*pac_request) { + pac_attrs.attributes_info.flags + |= PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED; + } + + ndr_err = ndr_push_union_blob(pac_attrs_data, mem_ctx, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC ATTRIBUTES_INFO (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + + return NT_STATUS_OK; +} + static NTSTATUS samba_get_cred_info_ndr_blob(TALLOC_CTX *mem_ctx, const struct ldb_message *msg, @@ -413,12 +450,14 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, const DATA_BLOB *logon_blob, const DATA_BLOB *cred_blob, const DATA_BLOB *upn_blob, + const DATA_BLOB *pac_attrs_blob, const DATA_BLOB *deleg_blob, krb5_pac *pac) { krb5_data logon_data; krb5_data cred_data; krb5_data upn_data; + krb5_data pac_attrs_data; krb5_data deleg_data; krb5_error_code ret; #ifdef SAMBA4_USES_HEIMDAL @@ -463,6 +502,19 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, } } + ZERO_STRUCT(pac_attrs_data); + if (pac_attrs_blob != NULL) { + ret = smb_krb5_copy_data_contents(&pac_attrs_data, + pac_attrs_blob->data, + pac_attrs_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + return ret; + } + } + ZERO_STRUCT(deleg_data); if (deleg_blob != NULL) { ret = smb_krb5_copy_data_contents(&deleg_data, @@ -472,6 +524,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &logon_data); smb_krb5_free_data_contents(context, &cred_data); smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); return ret; } } @@ -481,6 +534,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &logon_data); smb_krb5_free_data_contents(context, &cred_data); smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -488,8 +542,9 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, ret = krb5_pac_add_buffer(context, *pac, PAC_TYPE_LOGON_INFO, &logon_data); smb_krb5_free_data_contents(context, &logon_data); if (ret != 0) { - smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -501,6 +556,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &cred_data); if (ret != 0) { smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -519,6 +575,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, &null_data); if (ret != 0) { smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -529,6 +586,18 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, PAC_TYPE_UPN_DNS_INFO, &upn_data); smb_krb5_free_data_contents(context, &upn_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + + if (pac_attrs_blob != NULL) { + ret = krb5_pac_add_buffer(context, *pac, + PAC_TYPE_ATTRIBUTES_INFO, + &pac_attrs_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); if (ret != 0) { smb_krb5_free_data_contents(context, &deleg_data); return ret; @@ -562,6 +631,48 @@ bool samba_princ_needs_pac(struct samba_kdc_entry *skdc_entry) return true; } +int samba_client_requested_pac(krb5_context context, + krb5_pac *pac, + TALLOC_CTX *mem_ctx, + bool *requested_pac) +{ + enum ndr_err_code ndr_err; + krb5_data k5pac_attrs_in; + DATA_BLOB pac_attrs_in; + union PAC_INFO pac_attrs; + int ret; + + *requested_pac = true; + + ret = krb5_pac_get_buffer(context, *pac, PAC_TYPE_ATTRIBUTES_INFO, + &k5pac_attrs_in); + if (ret != 0) { + return ret == ENOENT ? 0 : ret; + } + + pac_attrs_in = data_blob_const(k5pac_attrs_in.data, + k5pac_attrs_in.length); + + ndr_err = ndr_pull_union_blob(&pac_attrs_in, mem_ctx, &pac_attrs, + PAC_TYPE_ATTRIBUTES_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_attrs_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC ATTRIBUTES_INFO: %s\n", nt_errstr(nt_status))); + return EINVAL; + } + + if (pac_attrs.attributes_info.flags & (PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY + | PAC_ATTRIBUTE_FLAG_PAC_WAS_REQUESTED)) { + *requested_pac = true; + } else { + *requested_pac = false; + } + + return 0; +} + /* Was the krbtgt in this DB (ie, should we check the incoming signature) and was it an RODC */ int samba_krbtgt_is_in_db(struct samba_kdc_entry *p, bool *is_in_db, @@ -637,12 +748,15 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, struct samba_kdc_entry *p, DATA_BLOB **_logon_info_blob, DATA_BLOB **_cred_ndr_blob, - DATA_BLOB **_upn_info_blob) + DATA_BLOB **_upn_info_blob, + DATA_BLOB **_pac_attrs_blob, + const krb5_boolean *pac_request) { struct auth_user_info_dc *user_info_dc; DATA_BLOB *logon_blob = NULL; DATA_BLOB *cred_blob = NULL; DATA_BLOB *upn_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; NTSTATUS nt_status; *_logon_info_blob = NULL; @@ -650,6 +764,9 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, *_cred_ndr_blob = NULL; } *_upn_info_blob = NULL; + if (_pac_attrs_blob != NULL) { + *_pac_attrs_blob = NULL; + } logon_blob = talloc_zero(mem_ctx, DATA_BLOB); if (logon_blob == NULL) { @@ -668,6 +785,13 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, return NT_STATUS_NO_MEMORY; } + if (_pac_attrs_blob != NULL) { + pac_attrs_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (pac_attrs_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + nt_status = authsam_make_user_info_dc(mem_ctx, p->kdc_db_ctx->samdb, lpcfg_netbios_name(p->kdc_db_ctx->lp_ctx), lpcfg_sam_name(p->kdc_db_ctx->lp_ctx), @@ -712,12 +836,27 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, return nt_status; } + if (pac_attrs_blob != NULL) { + nt_status = samba_get_pac_attrs_blob(pac_attrs_blob, + pac_request, + pac_attrs_blob); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("Building PAC ATTRIBUTES failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } + TALLOC_FREE(user_info_dc); *_logon_info_blob = logon_blob; if (_cred_ndr_blob != NULL) { *_cred_ndr_blob = cred_blob; } *_upn_info_blob = upn_blob; + if (_pac_attrs_blob != NULL) { + *_pac_attrs_blob = pac_attrs_blob; + } return NT_STATUS_OK; } @@ -731,7 +870,9 @@ NTSTATUS samba_kdc_get_pac_blob(TALLOC_CTX *mem_ctx, nt_status = samba_kdc_get_pac_blobs(mem_ctx, p, _logon_info_blob, NULL, /* cred_blob */ - &upn_blob); + &upn_blob, + NULL, + NULL); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h index e83446647b33..1b6264cb2c30 100644 --- a/source4/kdc/pac-glue.h +++ b/source4/kdc/pac-glue.h @@ -31,11 +31,17 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, const DATA_BLOB *logon_blob, const DATA_BLOB *cred_blob, const DATA_BLOB *upn_blob, + const DATA_BLOB *pac_attrs_blob, const DATA_BLOB *deleg_blob, krb5_pac *pac); bool samba_princ_needs_pac(struct samba_kdc_entry *skdc_entry); +int samba_client_requested_pac(krb5_context context, + krb5_pac *pac, + TALLOC_CTX *mem_ctx, + bool *requested_pac); + int samba_krbtgt_is_in_db(struct samba_kdc_entry *skdc_entry, bool *is_in_db, bool *is_untrusted); @@ -44,7 +50,9 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, struct samba_kdc_entry *skdc_entry, DATA_BLOB **_logon_info_blob, DATA_BLOB **_cred_ndr_blob, - DATA_BLOB **_upn_info_blob); + DATA_BLOB **_upn_info_blob, + DATA_BLOB **_pac_attrs_blob, + const krb5_boolean *pac_request); NTSTATUS samba_kdc_get_pac_blob(TALLOC_CTX *mem_ctx, struct samba_kdc_entry *skdc_entry, DATA_BLOB **_logon_info_blob); diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index ed6e9fb9b633..11d9ff84f04b 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -37,6 +37,7 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, struct hdb_entry_ex *client, const krb5_keyblock *pk_reply_key, + const krb5_boolean *pac_request, krb5_pac *pac) { TALLOC_CTX *mem_ctx; @@ -46,6 +47,7 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, DATA_BLOB _cred_blob = data_blob_null; DATA_BLOB *cred_blob = NULL; DATA_BLOB *upn_blob = NULL; + DATA_BLOB *pac_attrs_blob = NULL; krb5_error_code ret; NTSTATUS nt_status; struct samba_kdc_entry *skdc_entry = @@ -64,7 +66,9 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, nt_status = samba_kdc_get_pac_blobs(mem_ctx, skdc_entry, &logon_blob, cred_ndr_ptr, - &upn_blob); + &upn_blob, + &pac_attrs_blob, + pac_request); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); return EINVAL; @@ -84,7 +88,8 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, } ret = samba_make_krb5_pac(context, logon_blob, cred_blob, - upn_blob, NULL, pac); + upn_blob, pac_attrs_blob, + NULL, pac); talloc_free(mem_ctx); return ret; @@ -92,9 +97,10 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, static krb5_error_code samba_wdc_get_pac_compat(void *priv, krb5_context context, struct hdb_entry_ex *client, + const krb5_boolean *pac_request, krb5_pac *pac) { - return samba_wdc_get_pac(priv, context, client, NULL, pac); + return samba_wdc_get_pac(priv, context, client, NULL, pac_request, pac); } static krb5_error_code samba_wdc_reget_pac2(krb5_context context, @@ -132,6 +138,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, ssize_t srv_checksum_idx = -1; ssize_t kdc_checksum_idx = -1; ssize_t tkt_checksum_idx = -1; + ssize_t attrs_info_idx = -1; if (!mem_ctx) { return ENOMEM; @@ -239,7 +246,8 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, struct samba_kdc_entry); nt_status = samba_kdc_get_pac_blobs(mem_ctx, client_skdc_entry, - &pac_blob, NULL, &upn_blob); + &pac_blob, NULL, &upn_blob, + NULL, NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); return EINVAL; @@ -356,6 +364,18 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, } tkt_checksum_idx = i; break; + case PAC_TYPE_ATTRIBUTES_INFO: + if (attrs_info_idx != -1) { + DEBUG(1, ("attributes info type[%"PRIu32"] twice [%zd] and [%zu]: \n", + types[i], + attrs_info_idx, + i)); + SAFE_FREE(types); + talloc_free(mem_ctx); + return EINVAL; + } + attrs_info_idx = i; + break; default: continue; } @@ -403,6 +423,20 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, goto out; } + if (!server_skdc_entry->is_krbtgt) { + /* + * The client may have requested no PAC when obtaining the + * TGT. + */ + bool requested_pac; + ret = samba_client_requested_pac(context, pac, mem_ctx, + &requested_pac); + if (ret != 0 || !requested_pac) { + new_pac = NULL; + goto out; + } + } + /* Otherwise build an updated PAC */ ret = krb5_pac_init(context, &new_pac); if (ret != 0) { @@ -488,6 +522,9 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, */ type_blob = data_blob_const(&zero_byte, 1); break; + case PAC_TYPE_ATTRIBUTES_INFO: + /* just copy... */ + break; default: /* just copy... */ break; -- 2.25.1 From a23a09a427d8894dc5b761dd5f3bb1e6e0150fff Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:07:58 +1300 Subject: [PATCH 192/224] CVE-2020-25719 heimdal:kdc: Require authdata to be present BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 11 ----------- source4/heimdal/lib/krb5/pac.c | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 0f03e17c2428..c315446386d3 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -243,7 +243,6 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_client_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_service_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable -^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_no_pac ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed # # The lack of KRB5SignedPath means we no longer return @@ -263,16 +262,9 @@ ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer ^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer # -# KDC TGS PAC tests -# -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_client_no_auth_data_required -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_remove_pac_service_no_auth_data_required -# # KDC TGT tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_krbtgt_link @@ -280,7 +272,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_krbtgt_link @@ -288,7 +279,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link @@ -326,7 +316,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_krbtgt_link diff --git a/source4/heimdal/lib/krb5/pac.c b/source4/heimdal/lib/krb5/pac.c index 05bcc5230800..749d0fdb4ebb 100644 --- a/source4/heimdal/lib/krb5/pac.c +++ b/source4/heimdal/lib/krb5/pac.c @@ -1369,7 +1369,7 @@ _krb5_kdc_pac_ticket_parse(krb5_context context, *ppac = NULL; if (ad == NULL || ad->len == 0) - return 0; + return KRB5KDC_ERR_BADOPTION; for (i = 0; i < ad->len; i++) { AuthorizationData child; -- 2.25.1 From 8de9e2e1b7979d6f8bb78cc3d109ede7b94f056e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 30 Sep 2021 14:55:06 +1300 Subject: [PATCH 193/224] CVE-2020-25718 kdc: Remove unused samba_kdc_get_pac_blob() BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/kdc/pac-glue.c | 21 --------------------- source4/kdc/pac-glue.h | 3 --- 2 files changed, 24 deletions(-) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 06019e579ebb..7d45391dba44 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -860,27 +860,6 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } -NTSTATUS samba_kdc_get_pac_blob(TALLOC_CTX *mem_ctx, - struct samba_kdc_entry *p, - DATA_BLOB **_logon_info_blob) -{ - NTSTATUS nt_status; - DATA_BLOB *upn_blob = NULL; - - nt_status = samba_kdc_get_pac_blobs(mem_ctx, p, - _logon_info_blob, - NULL, /* cred_blob */ - &upn_blob, - NULL, - NULL); - if (!NT_STATUS_IS_OK(nt_status)) { - return nt_status; - } - - TALLOC_FREE(upn_blob); - return NT_STATUS_OK; -} - NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, krb5_context context, struct ldb_context *samdb, diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h index 1b6264cb2c30..2a7cb68f274f 100644 --- a/source4/kdc/pac-glue.h +++ b/source4/kdc/pac-glue.h @@ -53,9 +53,6 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, DATA_BLOB **_upn_info_blob, DATA_BLOB **_pac_attrs_blob, const krb5_boolean *pac_request); -NTSTATUS samba_kdc_get_pac_blob(TALLOC_CTX *mem_ctx, - struct samba_kdc_entry *skdc_entry, - DATA_BLOB **_logon_info_blob); NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, krb5_context context, -- 2.25.1 From b19820202627fb5197ec1128da47c2f799156e39 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 10:47:29 +1300 Subject: [PATCH 194/224] CVE-2020-25718 s4-rpc_server: Change sid list functions to operate on a array of struct dom_sid This is instead of an array of struct dom_sid *. The reason is that auth_user_info_dc has an array of struct dom_sid (the user token) and for checking if an RODC should be allowed to print a particular ticket, we want to reuse that a rather then reconstruct it via tokenGroups. This also avoids a lot of memory allocation. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 44 +++++++++---------- source4/rpc_server/drsuapi/getncchanges.c | 33 +++++++++----- source4/rpc_server/netlogon/dcerpc_netlogon.c | 33 +++++++++----- 3 files changed, 67 insertions(+), 43 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 698249391ef5..65d7e7c7271e 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -29,13 +29,16 @@ /* see if any SIDs in list1 are in list2 */ -bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2) +bool sid_list_match(uint32_t num_sids1, + const struct dom_sid *list1, + uint32_t num_sids2, + const struct dom_sid *list2) { unsigned int i, j; /* do we ever have enough SIDs here to worry about O(n^2) ? */ - for (i=0; list1[i]; i++) { - for (j=0; list2[j]; j++) { - if (dom_sid_equal(list1[i], list2[j])) { + for (i=0; i < num_sids1; i++) { + for (j=0; j < num_sids2; j++) { + if (dom_sid_equal(&list1[i], &list2[j])) { return true; } } @@ -51,9 +54,10 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, struct ldb_message *msg, TALLOC_CTX *mem_ctx, const char *attr, - const struct dom_sid ***sids, - const struct dom_sid **additional_sids, - unsigned int num_additional) + uint32_t *num_sids, + struct dom_sid **sids, + const struct dom_sid *additional_sids, + unsigned int num_additional) { struct ldb_message_element *el; unsigned int i, j; @@ -65,30 +69,25 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, } /* Make array long enough for NULL and additional SID */ - (*sids) = talloc_array(mem_ctx, const struct dom_sid *, - el->num_values + num_additional + 1); + (*sids) = talloc_array(mem_ctx, struct dom_sid, + el->num_values + num_additional); W_ERROR_HAVE_NO_MEMORY(*sids); for (i=0; inum_values; i++) { enum ndr_err_code ndr_err; - struct dom_sid *sid; - sid = talloc(*sids, struct dom_sid); - W_ERROR_HAVE_NO_MEMORY(sid); - - ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid, + ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &(*sids)[i], (ndr_pull_flags_fn_t)ndr_pull_dom_sid); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_INTERNAL_DB_CORRUPTION; } - (*sids)[i] = sid; } for (j = 0; j < num_additional; j++) { (*sids)[i++] = additional_sids[j]; } - (*sids)[i] = NULL; + *num_sids = i; return WERR_OK; } @@ -101,7 +100,8 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, struct ldb_message *msg, TALLOC_CTX *mem_ctx, const char *attr, - const struct dom_sid ***sids) + uint32_t *num_sids, + struct dom_sid **sids) { struct ldb_message_element *el; unsigned int i; @@ -112,23 +112,21 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, return WERR_OK; } - (*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1); + (*sids) = talloc_array(mem_ctx, struct dom_sid, el->num_values + 1); W_ERROR_HAVE_NO_MEMORY(*sids); for (i=0; inum_values; i++) { struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]); NTSTATUS status; - struct dom_sid *sid; + struct dom_sid sid = { 0, }; - sid = talloc(*sids, struct dom_sid); - W_ERROR_HAVE_NO_MEMORY(sid); - status = dsdb_get_extended_dn_sid(dn, sid, "SID"); + status = dsdb_get_extended_dn_sid(dn, &sid, "SID"); if (!NT_STATUS_IS_OK(status)) { return WERR_INTERNAL_DB_CORRUPTION; } (*sids)[i] = sid; } - (*sids)[i] = NULL; + *num_sids = i; return WERR_OK; } diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index e458b2a99311..c7d2addd104d 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1171,10 +1171,10 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_result *rodc_res = NULL, *obj_res = NULL; - const struct dom_sid **never_reveal_sids, **reveal_sids, **token_sids; + uint32_t num_never_reveal_sids, num_reveal_sids, num_token_sids; + struct dom_sid *never_reveal_sids, *reveal_sids, *token_sids; const struct dom_sid *object_sid = NULL; WERROR werr; - const struct dom_sid *additional_sids[] = { NULL, NULL }; DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_SECRET extended op on %s\n", drs_ObjectIdentifier_to_string(mem_ctx, ncRoot))); @@ -1259,12 +1259,13 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, /* if the object SID is equal to the user_sid, allow */ object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"); + if (object_sid == NULL) { + goto failed; + } if (dom_sid_equal(user_sid, object_sid)) { goto allowed; } - additional_sids[0] = object_sid; - /* * Must be an RODC account at this point, verify machine DN matches the * SID account @@ -1294,13 +1295,17 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, } werr = samdb_result_sid_array_dn(b_state->sam_ctx_system, rodc_res->msgs[0], - mem_ctx, "msDS-NeverRevealGroup", &never_reveal_sids); + mem_ctx, "msDS-NeverRevealGroup", + &num_never_reveal_sids, + &never_reveal_sids); if (!W_ERROR_IS_OK(werr)) { goto denied; } werr = samdb_result_sid_array_dn(b_state->sam_ctx_system, rodc_res->msgs[0], - mem_ctx, "msDS-RevealOnDemandGroup", &reveal_sids); + mem_ctx, "msDS-RevealOnDemandGroup", + &num_reveal_sids, + &reveal_sids); if (!W_ERROR_IS_OK(werr)) { goto denied; } @@ -1311,19 +1316,27 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, * TODO determine if sIDHistory is required for this check */ werr = samdb_result_sid_array_ndr(b_state->sam_ctx_system, obj_res->msgs[0], - mem_ctx, "tokenGroups", &token_sids, - additional_sids, 1); + mem_ctx, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid, 1); if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { goto denied; } if (never_reveal_sids && - sid_list_match(token_sids, never_reveal_sids)) { + sid_list_match(num_token_sids, + token_sids, + num_never_reveal_sids, + never_reveal_sids)) { goto denied; } if (reveal_sids && - sid_list_match(token_sids, reveal_sids)) { + sid_list_match(num_token_sids, + token_sids, + num_reveal_sids, + reveal_sids)) { goto allowed; } diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index ac8f2eab657a..d82a05d58690 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2852,10 +2852,10 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, struct ldb_dn *rodc_dn; int ret; struct ldb_result *rodc_res = NULL, *obj_res = NULL; - const struct dom_sid *additional_sids[] = { NULL, NULL }; WERROR werr; struct dom_sid *object_sid; - const struct dom_sid **never_reveal_sids, **reveal_sids, **token_sids; + uint32_t num_never_reveal_sids, num_reveal_sids, num_token_sids; + struct dom_sid *never_reveal_sids, *reveal_sids, *token_sids; rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "", dom_sid_string(mem_ctx, user_sid)); @@ -2870,17 +2870,22 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied; object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"); - - additional_sids[0] = object_sid; + if (object_sid == NULL) { + goto denied; + } werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], - mem_ctx, "msDS-NeverRevealGroup", &never_reveal_sids); + mem_ctx, "msDS-NeverRevealGroup", + &num_never_reveal_sids, + &never_reveal_sids); if (!W_ERROR_IS_OK(werr)) { goto denied; } werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], - mem_ctx, "msDS-RevealOnDemandGroup", &reveal_sids); + mem_ctx, "msDS-RevealOnDemandGroup", + &num_reveal_sids, + &reveal_sids); if (!W_ERROR_IS_OK(werr)) { goto denied; } @@ -2891,19 +2896,27 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, * TODO determine if sIDHistory is required for this check */ werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0], - mem_ctx, "tokenGroups", &token_sids, - additional_sids, 1); + mem_ctx, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid, 1); if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { goto denied; } if (never_reveal_sids && - sid_list_match(token_sids, never_reveal_sids)) { + sid_list_match(num_token_sids, + token_sids, + num_never_reveal_sids, + never_reveal_sids)) { goto denied; } if (reveal_sids && - sid_list_match(token_sids, reveal_sids)) { + sid_list_match(num_token_sids, + token_sids, + num_reveal_sids, + reveal_sids)) { goto allowed; } -- 2.25.1 From 9cbcc906a7b0f50cfe3dd63497caa00a03d7bc0c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:09:48 +1300 Subject: [PATCH 195/224] CVE-2020-25718 s4-rpc_server: Obtain the user tokenGroups earlier This will allow the creation of a common helper routine that takes the token SID list (from tokenGroups or struct auth_user_info_dc) and returns the allowed/denied result. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/drsuapi/getncchanges.c | 28 +++++++++---------- source4/rpc_server/netlogon/dcerpc_netlogon.c | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index c7d2addd104d..bc30e73e06bf 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1282,6 +1282,20 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto allowed; } + /* + * The SID list needs to include itself as well as the tokenGroups. + * + * TODO determine if sIDHistory is required for this check + */ + werr = samdb_result_sid_array_ndr(b_state->sam_ctx_system, obj_res->msgs[0], + mem_ctx, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid, 1); + if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + goto denied; + } + /* but it isn't allowed to get anyone elses krbtgt secrets */ if (samdb_result_dn(b_state->sam_ctx_system, mem_ctx, obj_res->msgs[0], "msDS-KrbTgtLinkBL", NULL)) { @@ -1310,20 +1324,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto denied; } - /* - * The SID list needs to include itself as well as the tokenGroups. - * - * TODO determine if sIDHistory is required for this check - */ - werr = samdb_result_sid_array_ndr(b_state->sam_ctx_system, obj_res->msgs[0], - mem_ctx, "tokenGroups", - &num_token_sids, - &token_sids, - object_sid, 1); - if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { - goto denied; - } - if (never_reveal_sids && sid_list_match(num_token_sids, token_sids, diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index d82a05d58690..4da28c887cfb 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2874,6 +2874,20 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, goto denied; } + /* + * The SID list needs to include itself as well as the tokenGroups. + * + * TODO determine if sIDHistory is required for this check + */ + werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0], + mem_ctx, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid, 1); + if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + goto denied; + } + werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], mem_ctx, "msDS-NeverRevealGroup", &num_never_reveal_sids, @@ -2890,20 +2904,6 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, goto denied; } - /* - * The SID list needs to include itself as well as the tokenGroups. - * - * TODO determine if sIDHistory is required for this check - */ - werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0], - mem_ctx, "tokenGroups", - &num_token_sids, - &token_sids, - object_sid, 1); - if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { - goto denied; - } - if (never_reveal_sids && sid_list_match(num_token_sids, token_sids, -- 2.25.1 From 4193bc1424f06e2b5b1c93fd72246e83588b792f Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:38:16 +1300 Subject: [PATCH 196/224] CVE-2020-25718 s4-rpc_server: Put RODC reveal/never reveal logic into a single helper function BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 49 +++++++++++++++++++ source4/rpc_server/drsuapi/getncchanges.c | 37 +++----------- source4/rpc_server/netlogon/dcerpc_netlogon.c | 38 +++----------- 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 65d7e7c7271e..eaeab236fc01 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -130,3 +130,52 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, return WERR_OK; } + +WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ctx, + struct ldb_message *rodc_msg, + uint32_t num_token_sids, + struct dom_sid *token_sids) +{ + uint32_t num_never_reveal_sids, num_reveal_sids; + struct dom_sid *never_reveal_sids, *reveal_sids; + TALLOC_CTX *frame = talloc_stackframe(); + WERROR werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, + frame, "msDS-NeverRevealGroup", + &num_never_reveal_sids, + &never_reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, + frame, "msDS-RevealOnDemandGroup", + &num_reveal_sids, + &reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + if (never_reveal_sids && + sid_list_match(num_token_sids, + token_sids, + num_never_reveal_sids, + never_reveal_sids)) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + if (reveal_sids && + sid_list_match(num_token_sids, + token_sids, + num_reveal_sids, + reveal_sids)) { + TALLOC_FREE(frame); + return WERR_OK; + } + + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + +} diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index bc30e73e06bf..3b1d674573ff 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1171,8 +1171,8 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_result *rodc_res = NULL, *obj_res = NULL; - uint32_t num_never_reveal_sids, num_reveal_sids, num_token_sids; - struct dom_sid *never_reveal_sids, *reveal_sids, *token_sids; + uint32_t num_token_sids; + struct dom_sid *token_sids; const struct dom_sid *object_sid = NULL; WERROR werr; @@ -1308,35 +1308,12 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto denied; } - werr = samdb_result_sid_array_dn(b_state->sam_ctx_system, rodc_res->msgs[0], - mem_ctx, "msDS-NeverRevealGroup", - &num_never_reveal_sids, - &never_reveal_sids); - if (!W_ERROR_IS_OK(werr)) { - goto denied; - } - - werr = samdb_result_sid_array_dn(b_state->sam_ctx_system, rodc_res->msgs[0], - mem_ctx, "msDS-RevealOnDemandGroup", - &num_reveal_sids, - &reveal_sids); - if (!W_ERROR_IS_OK(werr)) { - goto denied; - } - - if (never_reveal_sids && - sid_list_match(num_token_sids, - token_sids, - num_never_reveal_sids, - never_reveal_sids)) { - goto denied; - } + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(b_state->sam_ctx_system, + rodc_res->msgs[0], + num_token_sids, + token_sids); - if (reveal_sids && - sid_list_match(num_token_sids, - token_sids, - num_reveal_sids, - reveal_sids)) { + if (W_ERROR_IS_OK(werr)) { goto allowed; } diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 4da28c887cfb..a42ce5555862 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2854,8 +2854,8 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, struct ldb_result *rodc_res = NULL, *obj_res = NULL; WERROR werr; struct dom_sid *object_sid; - uint32_t num_never_reveal_sids, num_reveal_sids, num_token_sids; - struct dom_sid *never_reveal_sids, *reveal_sids, *token_sids; + uint32_t num_token_sids; + struct dom_sid *token_sids; rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "", dom_sid_string(mem_ctx, user_sid)); @@ -2888,38 +2888,14 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, goto denied; } - werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], - mem_ctx, "msDS-NeverRevealGroup", - &num_never_reveal_sids, - &never_reveal_sids); - if (!W_ERROR_IS_OK(werr)) { - goto denied; - } - - werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], - mem_ctx, "msDS-RevealOnDemandGroup", - &num_reveal_sids, - &reveal_sids); - if (!W_ERROR_IS_OK(werr)) { - goto denied; - } + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, + rodc_res->msgs[0], + num_token_sids, + token_sids); - if (never_reveal_sids && - sid_list_match(num_token_sids, - token_sids, - num_never_reveal_sids, - never_reveal_sids)) { - goto denied; - } - - if (reveal_sids && - sid_list_match(num_token_sids, - token_sids, - num_reveal_sids, - reveal_sids)) { + if (W_ERROR_IS_OK(werr)) { goto allowed; } - denied: return false; allowed: -- 2.25.1 From a629721bb76e7bfdca43cd61e9349d8d79218229 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:55:11 +1300 Subject: [PATCH 197/224] CVE-2020-25718 s4-rpc_server: Put msDS-KrbTgtLinkBL and UF_INTERDOMAIN_TRUST_ACCOUNT RODC checks in common While these checks were not in the NETLOGON case, there is no sense where an RODC should be resetting a bad password count on either a UF_INTERDOMAIN_TRUST_ACCOUNT nor a RODC krbtgt account. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 29 ++++++++++++++++--- source4/rpc_server/drsuapi/getncchanges.c | 13 +-------- source4/rpc_server/netlogon/dcerpc_netlogon.c | 1 + 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index eaeab236fc01..ab2b4373b473 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -133,16 +133,37 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ctx, struct ldb_message *rodc_msg, + struct ldb_message *obj_msg, uint32_t num_token_sids, struct dom_sid *token_sids) { uint32_t num_never_reveal_sids, num_reveal_sids; struct dom_sid *never_reveal_sids, *reveal_sids; TALLOC_CTX *frame = talloc_stackframe(); - WERROR werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, - frame, "msDS-NeverRevealGroup", - &num_never_reveal_sids, - &never_reveal_sids); + WERROR werr; + + /* + * We are not allowed to get anyone elses krbtgt secrets (and + * in callers that don't shortcut before this, the RODC should + * not deal with any krbtgt) + */ + if (samdb_result_dn(sam_ctx, frame, + obj_msg, "msDS-KrbTgtLinkBL", NULL)) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + if (ldb_msg_find_attr_as_uint(obj_msg, + "userAccountControl", 0) & + UF_INTERDOMAIN_TRUST_ACCOUNT) { + TALLOC_FREE(frame); + return WERR_DS_DRA_SECRETS_DENIED; + } + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, + frame, "msDS-NeverRevealGroup", + &num_never_reveal_sids, + &never_reveal_sids); if (!W_ERROR_IS_OK(werr)) { TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 3b1d674573ff..a9d305fc9a05 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1296,20 +1296,9 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto denied; } - /* but it isn't allowed to get anyone elses krbtgt secrets */ - if (samdb_result_dn(b_state->sam_ctx_system, mem_ctx, - obj_res->msgs[0], "msDS-KrbTgtLinkBL", NULL)) { - goto denied; - } - - if (ldb_msg_find_attr_as_uint(obj_res->msgs[0], - "userAccountControl", 0) & - UF_INTERDOMAIN_TRUST_ACCOUNT) { - goto denied; - } - werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(b_state->sam_ctx_system, rodc_res->msgs[0], + obj_res->msgs[0], num_token_sids, token_sids); diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index a42ce5555862..486fa794ce76 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2890,6 +2890,7 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, rodc_res->msgs[0], + obj_res->msgs[0], num_token_sids, token_sids); -- 2.25.1 From dc881eabbbb6d6bf695a8fef11f96baf5bf5cc56 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:01:12 +1300 Subject: [PATCH 198/224] CVE-2020-25718 s4-rpc_server: Confirm that the RODC has the UF_PARTIAL_SECRETS_ACCOUNT bit BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 13 +++++++++++++ source4/rpc_server/drsuapi/getncchanges.c | 7 ++++++- source4/rpc_server/netlogon/dcerpc_netlogon.c | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index ab2b4373b473..99c5fc20d9d5 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -141,6 +141,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct struct dom_sid *never_reveal_sids, *reveal_sids; TALLOC_CTX *frame = talloc_stackframe(); WERROR werr; + uint32_t rodc_uac; /* * We are not allowed to get anyone elses krbtgt secrets (and @@ -160,6 +161,18 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct return WERR_DS_DRA_SECRETS_DENIED; } + /* Be very sure the RODC is really an RODC */ + rodc_uac = ldb_msg_find_attr_as_uint(rodc_msg, + "userAccountControl", + 0); + if ((rodc_uac & UF_PARTIAL_SECRETS_ACCOUNT) + != UF_PARTIAL_SECRETS_ACCOUNT) { + TALLOC_FREE(frame); + DBG_ERR("Attempt to use an RODC account that is not an RODC: %s\n", + ldb_dn_get_linearized(rodc_msg->dn)); + return WERR_DS_DRA_SECRETS_DENIED; + } + werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, frame, "msDS-NeverRevealGroup", &num_never_reveal_sids, diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index a9d305fc9a05..2fbd178cedca 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1168,7 +1168,12 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, struct ldb_dn *ntds_dn = NULL, *server_dn = NULL; struct ldb_dn *rodc_dn, *krbtgt_link_dn; int ret; - const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL }; + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "objectGUID", + "userAccountControl", + NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_result *rodc_res = NULL, *obj_res = NULL; uint32_t num_token_sids; diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 486fa794ce76..bb3f83f435af 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2847,7 +2847,12 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, struct dom_sid *user_sid, struct ldb_dn *obj_dn) { - const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL }; + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "objectGUID", + "userAccountControl", + NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_dn *rodc_dn; int ret; -- 2.25.1 From d6fec6218e195506bcabc7234e35776471ce8e43 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:25:30 +1300 Subject: [PATCH 199/224] CVE-2020-25718 s4-rpc_server: Provide wrapper samdb_confirm_rodc_allowed_to_repl_to() This shares the lookup of the tokenGroups attribute. There will be a new caller that does not want to do this step, so this is a wrapper of samdb_confirm_rodc_allowed_to_repl_to_sid_list() rather than part of it BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 45 +++++++++++++++++++ source4/rpc_server/drsuapi/getncchanges.c | 24 ++-------- source4/rpc_server/netlogon/dcerpc_netlogon.c | 30 ++----------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 99c5fc20d9d5..78cb35d3fc15 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -213,3 +213,48 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct return WERR_DS_DRA_SECRETS_DENIED; } + +/* + * This is a wrapper for the above that pulls in the tokenGroups + * rather than relying on the caller providing those + */ +WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, + struct ldb_message *rodc_msg, + struct ldb_message *obj_msg) +{ + TALLOC_CTX *frame = talloc_stackframe(); + WERROR werr; + uint32_t num_token_sids; + struct dom_sid *token_sids; + const struct dom_sid *object_sid = NULL; + + object_sid = samdb_result_dom_sid(frame, + obj_msg, + "objectSid"); + if (object_sid == NULL) { + return WERR_DS_DRA_BAD_DN; + } + + /* + * The SID list needs to include itself as well as the tokenGroups. + * + * TODO determine if sIDHistory is required for this check + */ + werr = samdb_result_sid_array_ndr(sam_ctx, + obj_msg, + frame, "tokenGroups", + &num_token_sids, + &token_sids, + object_sid, 1); + if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + return WERR_DS_DRA_SECRETS_DENIED; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, + rodc_msg, + obj_msg, + num_token_sids, + token_sids); + TALLOC_FREE(frame); + return werr; +} diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 2fbd178cedca..11a6c93d4cdb 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1176,8 +1176,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_result *rodc_res = NULL, *obj_res = NULL; - uint32_t num_token_sids; - struct dom_sid *token_sids; const struct dom_sid *object_sid = NULL; WERROR werr; @@ -1287,25 +1285,9 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto allowed; } - /* - * The SID list needs to include itself as well as the tokenGroups. - * - * TODO determine if sIDHistory is required for this check - */ - werr = samdb_result_sid_array_ndr(b_state->sam_ctx_system, obj_res->msgs[0], - mem_ctx, "tokenGroups", - &num_token_sids, - &token_sids, - object_sid, 1); - if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { - goto denied; - } - - werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(b_state->sam_ctx_system, - rodc_res->msgs[0], - obj_res->msgs[0], - num_token_sids, - token_sids); + werr = samdb_confirm_rodc_allowed_to_repl_to(b_state->sam_ctx_system, + rodc_res->msgs[0], + obj_res->msgs[0]); if (W_ERROR_IS_OK(werr)) { goto allowed; diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index bb3f83f435af..b3f06cfd505f 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2858,9 +2858,6 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, int ret; struct ldb_result *rodc_res = NULL, *obj_res = NULL; WERROR werr; - struct dom_sid *object_sid; - uint32_t num_token_sids; - struct dom_sid *token_sids; rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "", dom_sid_string(mem_ctx, user_sid)); @@ -2874,30 +2871,9 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, ret = dsdb_search_dn(sam_ctx, mem_ctx, &obj_res, obj_dn, obj_attrs, 0); if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied; - object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"); - if (object_sid == NULL) { - goto denied; - } - - /* - * The SID list needs to include itself as well as the tokenGroups. - * - * TODO determine if sIDHistory is required for this check - */ - werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0], - mem_ctx, "tokenGroups", - &num_token_sids, - &token_sids, - object_sid, 1); - if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { - goto denied; - } - - werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, - rodc_res->msgs[0], - obj_res->msgs[0], - num_token_sids, - token_sids); + werr = samdb_confirm_rodc_allowed_to_repl_to(sam_ctx, + rodc_res->msgs[0], + obj_res->msgs[0]); if (W_ERROR_IS_OK(werr)) { goto allowed; -- 2.25.1 From e7ddd2cabf44da291137a1c9b96b50cfc8329247 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:29:49 +1300 Subject: [PATCH 200/224] CVE-2020-25718 s4-rpc_server: Remove unused attributes in RODC check In particular the objectGUID is no longer used, and in the NETLOGON case the special case for msDS-KrbTgtLink does not apply. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/drsuapi/getncchanges.c | 1 - source4/rpc_server/netlogon/dcerpc_netlogon.c | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 11a6c93d4cdb..3ec5acb5353d 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1171,7 +1171,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", - "objectGUID", "userAccountControl", NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index b3f06cfd505f..78f5f5dc7412 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2847,10 +2847,8 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, struct dom_sid *user_sid, struct ldb_dn *obj_dn) { - const char *rodc_attrs[] = { "msDS-KrbTgtLink", - "msDS-NeverRevealGroup", + const char *rodc_attrs[] = { "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", - "objectGUID", "userAccountControl", NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; -- 2.25.1 From f60707475b8ae7ade8f31fbb7a6fd7084bbd4ab7 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 14:31:00 +1300 Subject: [PATCH 201/224] CVE-2020-25718 s4-rpc_server: Explain why we use DSDB_SEARCH_SHOW_EXTENDED_DN in RODC access check BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/drsuapi/getncchanges.c | 6 +++++- source4/rpc_server/netlogon/dcerpc_netlogon.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 3ec5acb5353d..8a5243aba528 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1251,7 +1251,11 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, dom_sid_string(mem_ctx, user_sid)); if (!ldb_dn_validate(rodc_dn)) goto failed; - /* do the two searches we need */ + /* + * do the two searches we need + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists + * out of the extended DNs + */ ret = dsdb_search_dn(b_state->sam_ctx_system, mem_ctx, &rodc_res, rodc_dn, rodc_attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); if (ret != LDB_SUCCESS || rodc_res->count != 1) goto failed; diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 78f5f5dc7412..091b358ede63 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2861,7 +2861,11 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, dom_sid_string(mem_ctx, user_sid)); if (!ldb_dn_validate(rodc_dn)) goto denied; - /* do the two searches we need */ + /* + * do the two searches we need + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID list + * out of the extended DNs + */ ret = dsdb_search_dn(sam_ctx, mem_ctx, &rodc_res, rodc_dn, rodc_attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); if (ret != LDB_SUCCESS || rodc_res->count != 1) goto denied; -- 2.25.1 From ec3cd28a8171818e6007411d805769b6c721f40a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 15:57:41 +1300 Subject: [PATCH 202/224] CVE-2020-25718 s4-rpc_server: Add in debug messages into RODC processing These are added for the uncommon cases. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/rpc_server/common/sid_helper.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 78cb35d3fc15..c6e7fbeb7ab2 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -151,12 +151,18 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct if (samdb_result_dn(sam_ctx, frame, obj_msg, "msDS-KrbTgtLinkBL", NULL)) { TALLOC_FREE(frame); + DBG_INFO("Denied attempt to replicate to/act as a RODC krbtgt trust account %s using RODC: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn)); return WERR_DS_DRA_SECRETS_DENIED; } if (ldb_msg_find_attr_as_uint(obj_msg, "userAccountControl", 0) & UF_INTERDOMAIN_TRUST_ACCOUNT) { + DBG_INFO("Denied attempt to replicate to/act as a inter-domain trust account %s using RODC: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -167,9 +173,9 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct 0); if ((rodc_uac & UF_PARTIAL_SECRETS_ACCOUNT) != UF_PARTIAL_SECRETS_ACCOUNT) { - TALLOC_FREE(frame); DBG_ERR("Attempt to use an RODC account that is not an RODC: %s\n", ldb_dn_get_linearized(rodc_msg->dn)); + TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -178,6 +184,9 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct &num_never_reveal_sids, &never_reveal_sids); if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to parse msDS-NeverRevealGroup on %s: %s\n", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -187,6 +196,9 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct &num_reveal_sids, &reveal_sids); if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to parse msDS-RevealOnDemandGroup on %s: %s\n", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -247,6 +259,10 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, &token_sids, object_sid, 1); if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + DBG_ERR("Failed to get tokenGroups on %s to confirm access via RODC %s: %s\n", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); return WERR_DS_DRA_SECRETS_DENIED; } -- 2.25.1 From 4a366d10b16d7b9fb650d52063149f41a4019272 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 15:59:28 +1300 Subject: [PATCH 203/224] CVE-2020-25718 dsdb: Bring sid_helper.c into common code as rodc_helper.c These common routines will assist the KDC to do the same access checking as the RPC servers need to do regarding which accounts a RODC can act with regard to. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- .../common/sid_helper.c => dsdb/common/rodc_helper.c} | 1 - source4/dsdb/wscript_build | 2 +- source4/rpc_server/drsuapi/getncchanges.c | 1 - source4/rpc_server/netlogon/dcerpc_netlogon.c | 1 - source4/rpc_server/wscript_build | 9 +-------- 5 files changed, 2 insertions(+), 12 deletions(-) rename source4/{rpc_server/common/sid_helper.c => dsdb/common/rodc_helper.c} (99%) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/dsdb/common/rodc_helper.c similarity index 99% rename from source4/rpc_server/common/sid_helper.c rename to source4/dsdb/common/rodc_helper.c index c6e7fbeb7ab2..09aa3f5e710d 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/dsdb/common/rodc_helper.c @@ -23,7 +23,6 @@ #include "rpc_server/dcerpc_server.h" #include "librpc/gen_ndr/ndr_security.h" #include "source4/dsdb/samdb/samdb.h" -#include "rpc_server/common/sid_helper.h" #include "libcli/security/security.h" /* diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build index dbe58859a14b..98364667b667 100644 --- a/source4/dsdb/wscript_build +++ b/source4/dsdb/wscript_build @@ -13,7 +13,7 @@ bld.SAMBA_LIBRARY('samdb', ) bld.SAMBA_LIBRARY('samdb-common', - source='common/util.c common/util_trusts.c common/util_groups.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c common/util_links.c', + source='common/util.c common/util_trusts.c common/util_groups.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c common/util_links.c common/rodc_helper.c', autoproto='common/proto.h', private_library=True, deps='ldb NDR_DRSBLOBS util_ldb LIBCLI_AUTH samba-hostconfig samba_socket cli-ldap-common flag_mapping UTIL_RUNCMD' diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 8a5243aba528..28223104c946 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -31,7 +31,6 @@ #include "libcli/security/security.h" #include "libcli/security/session.h" #include "rpc_server/drsuapi/dcesrv_drsuapi.h" -#include "rpc_server/common/sid_helper.h" #include "../libcli/drsuapi/drsuapi.h" #include "lib/util/binsearch.h" #include "lib/util/tsort.h" diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 091b358ede63..af427af59e97 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -42,7 +42,6 @@ #include "librpc/gen_ndr/ndr_winbind.h" #include "librpc/gen_ndr/ndr_winbind_c.h" #include "lib/socket/netif.h" -#include "rpc_server/common/sid_helper.h" #include "lib/util/util_str_escape.h" #define DCESRV_INTERFACE_NETLOGON_BIND(context, iface) \ diff --git a/source4/rpc_server/wscript_build b/source4/rpc_server/wscript_build index c9c1978f2235..8c756721232a 100644 --- a/source4/rpc_server/wscript_build +++ b/source4/rpc_server/wscript_build @@ -7,17 +7,10 @@ bld.SAMBA_SUBSYSTEM('DCERPC_SHARE', enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER'), ) -bld.SAMBA_SUBSYSTEM('DCERPC_SID_HELPER', - source='common/sid_helper.c', - autoproto='common/sid_helper.h', - deps='ldb', - enabled=bld.AD_DC_BUILD_IS_ENABLED(), - ) - bld.SAMBA_SUBSYSTEM('DCERPC_COMMON', source='common/server_info.c common/forward.c common/loadparm.c', autoproto='common/proto.h', - deps='ldb DCERPC_SHARE DCERPC_SID_HELPER', + deps='ldb DCERPC_SHARE', enabled=bld.AD_DC_BUILD_IS_ENABLED() ) -- 2.25.1 From 96eb40b5a7e74c1ad6b1b61d9deac21eb9c7a280 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 16:14:37 +1300 Subject: [PATCH 204/224] CVE-2020-25718 kdc: Confirm the RODC was allowed to issue a particular ticket BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- selftest/knownfail_heimdal_kdc | 12 -- source4/auth/sam.c | 5 +- source4/dsdb/common/rodc_helper.c | 47 ++++---- source4/kdc/mit_samba.c | 6 +- source4/kdc/pac-glue.c | 106 +++++++++++++++++- source4/kdc/pac-glue.h | 13 ++- source4/kdc/wdc-samba4.c | 40 ++++++- source4/rpc_server/drsuapi/getncchanges.c | 11 +- source4/rpc_server/netlogon/dcerpc_netlogon.c | 1 + 9 files changed, 187 insertions(+), 54 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index c315446386d3..7d0597fa2790 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -265,25 +265,16 @@ # KDC TGT tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_allowed_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_krbtgt_link ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_partial_secrets -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_allowed_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_krbtgt_link ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_partial_secrets -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_partial_secrets -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac @@ -316,11 +307,8 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_allowed_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_krbtgt_link ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_partial_secrets -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed # # PAC request tests diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 39e48c26b523..93b41be3b210 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -57,7 +57,10 @@ \ "pwdLastSet", \ "msDS-UserPasswordExpiryTimeComputed", \ - "accountExpires" + "accountExpires", \ + \ + /* Needed for RODC rule processing */ \ + "msDS-KrbTgtLinkBL" const char *krbtgt_attrs[] = { KRBTGT_ATTRS, NULL diff --git a/source4/dsdb/common/rodc_helper.c b/source4/dsdb/common/rodc_helper.c index 09aa3f5e710d..1cd644c6def0 100644 --- a/source4/dsdb/common/rodc_helper.c +++ b/source4/dsdb/common/rodc_helper.c @@ -47,19 +47,18 @@ bool sid_list_match(uint32_t num_sids1, /* * Return an array of SIDs from a ldb_message given an attribute name assumes - * the SIDs are in NDR form (with additional sids applied on the end). + * the SIDs are in NDR form (with primary_sid applied on the start). */ -WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, - struct ldb_message *msg, - TALLOC_CTX *mem_ctx, - const char *attr, - uint32_t *num_sids, - struct dom_sid **sids, - const struct dom_sid *additional_sids, - unsigned int num_additional) +static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, + struct ldb_message *msg, + TALLOC_CTX *mem_ctx, + const char *attr, + uint32_t *num_sids, + struct dom_sid **sids, + const struct dom_sid *primary_sid) { struct ldb_message_element *el; - unsigned int i, j; + unsigned int i; el = ldb_msg_find_element(msg, attr); if (!el) { @@ -69,24 +68,25 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, /* Make array long enough for NULL and additional SID */ (*sids) = talloc_array(mem_ctx, struct dom_sid, - el->num_values + num_additional); + el->num_values + 1); W_ERROR_HAVE_NO_MEMORY(*sids); - for (i=0; inum_values; i++) { + (*sids)[0] = *primary_sid; + + for (i = 0; inum_values; i++) { enum ndr_err_code ndr_err; + struct dom_sid sid = { 0, }; - ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &(*sids)[i], + ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_INTERNAL_DB_CORRUPTION; } + /* Primary SID is already in position zero. */ + (*sids)[i+1] = sid; } - for (j = 0; j < num_additional; j++) { - (*sids)[i++] = additional_sids[j]; - } - - *num_sids = i; + *num_sids = i+1; return WERR_OK; } @@ -131,6 +131,7 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, } WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ctx, + const struct dom_sid *rodc_machine_account_sid, struct ldb_message *rodc_msg, struct ldb_message *obj_msg, uint32_t num_token_sids, @@ -202,6 +203,12 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct return WERR_DS_DRA_SECRETS_DENIED; } + /* The RODC can replicate and print tickets for itself. */ + if (dom_sid_equal(&token_sids[0], rodc_machine_account_sid)) { + TALLOC_FREE(frame); + return WERR_OK; + } + if (never_reveal_sids && sid_list_match(num_token_sids, token_sids, @@ -230,6 +237,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct * rather than relying on the caller providing those */ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, + struct dom_sid *rodc_machine_account_sid, struct ldb_message *rodc_msg, struct ldb_message *obj_msg) { @@ -256,7 +264,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, frame, "tokenGroups", &num_token_sids, &token_sids, - object_sid, 1); + object_sid); if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { DBG_ERR("Failed to get tokenGroups on %s to confirm access via RODC %s: %s\n", ldb_dn_get_linearized(obj_msg->dn), @@ -266,6 +274,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, } werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx, + rodc_machine_account_sid, rodc_msg, obj_msg, num_token_sids, diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index 69cdbfba929f..99a5214a8962 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -435,7 +435,8 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, &logon_info_blob, cred_ndr_ptr, &upn_dns_info_blob, - NULL, NULL); + NULL, NULL, + NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(tmp_ctx); if (NT_STATUS_EQUAL(nt_status, @@ -567,7 +568,8 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, &pac_blob, NULL, &upn_blob, - NULL, NULL); + NULL, NULL, + NULL); if (!NT_STATUS_IS_OK(nt_status)) { code = EINVAL; goto done; diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 7d45391dba44..c94e4f08e76c 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -35,6 +35,7 @@ #include "libcli/security/security.h" #include "dsdb/samdb/samdb.h" #include "auth/kerberos/pac_utils.h" +#include "source4/dsdb/common/util.h" static NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, @@ -744,13 +745,19 @@ int samba_krbtgt_is_in_db(struct samba_kdc_entry *p, return 0; } +/* + * We return not just the blobs, but also the user_info_dc because we + * will need, in the RODC case, to confirm that the returned user is + * permitted to be replicated to the KDC + */ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, struct samba_kdc_entry *p, DATA_BLOB **_logon_info_blob, DATA_BLOB **_cred_ndr_blob, DATA_BLOB **_upn_info_blob, DATA_BLOB **_pac_attrs_blob, - const krb5_boolean *pac_request) + const krb5_boolean *pac_request, + struct auth_user_info_dc **_user_info_dc) { struct auth_user_info_dc *user_info_dc; DATA_BLOB *logon_blob = NULL; @@ -848,7 +855,15 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, } } - TALLOC_FREE(user_info_dc); + /* + * Return to the caller to allow a check on the allowed/denied + * RODC replication groups + */ + if (_user_info_dc == NULL) { + TALLOC_FREE(user_info_dc); + } else { + *_user_info_dc = user_info_dc; + } *_logon_info_blob = logon_blob; if (_cred_ndr_blob != NULL) { *_cred_ndr_blob = cred_blob; @@ -1094,3 +1109,90 @@ out: TALLOC_FREE(frame); return code; } + + +/* + * In the RODC case, to confirm that the returned user is permitted to + * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc + */ +WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, + struct dom_sid *object_sids, + struct samba_kdc_entry *rodc, + struct samba_kdc_entry *object) +{ + int ret; + WERROR werr; + TALLOC_CTX *frame = talloc_stackframe(); + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + "objectSid", + NULL }; + struct ldb_result *rodc_machine_account = NULL; + struct ldb_dn *rodc_machine_account_dn = samdb_result_dn(rodc->kdc_db_ctx->samdb, + frame, + rodc->msg, + "msDS-KrbTgtLinkBL", + NULL); + const struct dom_sid *rodc_machine_account_sid = NULL; + + if (rodc_machine_account_dn == NULL) { + DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n", + ldb_dn_get_linearized(rodc->msg->dn)); + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + /* + * Follow the link and get the RODC account (the krbtgt + * account is the krbtgt_XXX account, but the + * msDS-NeverRevealGroup and msDS-RevealOnDemandGroup is on + * the RODC$ account) + * + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists + * out of the extended DNs + */ + + ret = dsdb_search_dn(rodc->kdc_db_ctx->samdb, + frame, + &rodc_machine_account, + rodc_machine_account_dn, + rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: %s\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + ldb_errstring(rodc->kdc_db_ctx->samdb)); + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + if (rodc_machine_account->count != 1) { + DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: (%d)\n", + ldb_dn_get_linearized(rodc_machine_account_dn), + ldb_dn_get_linearized(rodc->msg->dn), + rodc_machine_account->count); + TALLOC_FREE(frame); + return WERR_DS_DRA_BAD_DN; + } + + /* if the object SID is equal to the user_sid, allow */ + rodc_machine_account_sid = samdb_result_dom_sid(frame, + rodc_machine_account->msgs[0], + "objectSid"); + if (rodc_machine_account_sid == NULL) { + return WERR_DS_DRA_BAD_DN; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb, + rodc_machine_account_sid, + rodc_machine_account->msgs[0], + object->msg, + num_object_sids, + object_sids); + + TALLOC_FREE(frame); + return werr; +} diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h index 2a7cb68f274f..89aa8da63c39 100644 --- a/source4/kdc/pac-glue.h +++ b/source4/kdc/pac-glue.h @@ -52,8 +52,8 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, DATA_BLOB **_cred_ndr_blob, DATA_BLOB **_upn_info_blob, DATA_BLOB **_pac_attrs_blob, - const krb5_boolean *pac_request); - + const krb5_boolean *pac_request, + struct auth_user_info_dc **_user_info_dc); NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, krb5_context context, struct ldb_context *samdb, @@ -79,3 +79,12 @@ krb5_error_code samba_kdc_validate_pac_blob( krb5_context context, struct samba_kdc_entry *client_skdc_entry, const krb5_pac pac); + +/* + * In the RODC case, to confirm that the returned user is permitted to + * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc + */ +WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_sids, + struct dom_sid *sids, + struct samba_kdc_entry *rodc, + struct samba_kdc_entry *object); diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index 11d9ff84f04b..715070181207 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -27,6 +27,7 @@ #include "kdc/pac-glue.h" #include "sdb.h" #include "sdb_hdb.h" +#include "librpc/gen_ndr/auth.h" /* * Given the right private pointer from hdb_samba4, @@ -68,7 +69,8 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, cred_ndr_ptr, &upn_blob, &pac_attrs_blob, - pac_request); + pac_request, + NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); return EINVAL; @@ -161,9 +163,15 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, } } - /* If the krbtgt was generated by an RODC, and we are not that + /* + * If the krbtgt was generated by an RODC, and we are not that * RODC, then we need to regenerate the PAC - we can't trust - * it */ + * it, and confirm that the RODC was permitted to print this ticket + * + * Becasue of the samba_kdc_validate_pac_blob() step we can be + * sure that the record in 'client' matches the SID in the + * original PAC. + */ ret = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_untrusted); if (ret != 0) { talloc_free(mem_ctx); @@ -237,6 +245,8 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, if (is_untrusted) { struct samba_kdc_entry *client_skdc_entry = NULL; + struct auth_user_info_dc *user_info_dc = NULL; + WERROR werr; if (client == NULL) { return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; @@ -247,12 +257,30 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, nt_status = samba_kdc_get_pac_blobs(mem_ctx, client_skdc_entry, &pac_blob, NULL, &upn_blob, - NULL, NULL); + NULL, NULL, + &user_info_dc); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); - return EINVAL; + return KRB5KDC_ERR_TGT_REVOKED; + } + + /* + * Now check if the SID list in the user_info_dc + * intersects correctly with the RODC allow/deny + * lists + */ + + werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids, + user_info_dc->sids, + krbtgt_skdc_entry, + client_skdc_entry); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(mem_ctx); + return KRB5KDC_ERR_TGT_REVOKED; } - } else { + } + + if (!is_untrusted) { pac_blob = talloc_zero(mem_ctx, DATA_BLOB); if (!pac_blob) { talloc_free(mem_ctx); diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 28223104c946..c3330a622afd 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -1174,7 +1174,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, NULL }; const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; struct ldb_result *rodc_res = NULL, *obj_res = NULL; - const struct dom_sid *object_sid = NULL; WERROR werr; DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_SECRET extended op on %s\n", @@ -1262,15 +1261,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, ret = dsdb_search_dn(b_state->sam_ctx_system, mem_ctx, &obj_res, obj_dn, obj_attrs, 0); if (ret != LDB_SUCCESS || obj_res->count != 1) goto failed; - /* if the object SID is equal to the user_sid, allow */ - object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"); - if (object_sid == NULL) { - goto failed; - } - if (dom_sid_equal(user_sid, object_sid)) { - goto allowed; - } - /* * Must be an RODC account at this point, verify machine DN matches the * SID account @@ -1288,6 +1278,7 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, } werr = samdb_confirm_rodc_allowed_to_repl_to(b_state->sam_ctx_system, + user_sid, rodc_res->msgs[0], obj_res->msgs[0]); diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index af427af59e97..e29a82766044 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2873,6 +2873,7 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx, if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied; werr = samdb_confirm_rodc_allowed_to_repl_to(sam_ctx, + user_sid, rodc_res->msgs[0], obj_res->msgs[0]); -- 2.25.1 From 0437cd56ffe6bc7189e477c2648ba9f31f0918e8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 4 Oct 2021 12:43:13 +1300 Subject: [PATCH 205/224] CVE-2020-25718 kdc: Return ERR_POLICY if RODC krbtgt account is invalid BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 8 -------- source4/dsdb/common/rodc_helper.c | 2 +- source4/kdc/pac-glue.c | 4 ++-- source4/kdc/wdc-samba4.c | 6 +++++- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 7d0597fa2790..5b6fb0ddf692 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -265,16 +265,10 @@ # KDC TGT tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_krbtgt_link -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_krbtgt_link -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac @@ -307,8 +301,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_krbtgt_link -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed # # PAC request tests diff --git a/source4/dsdb/common/rodc_helper.c b/source4/dsdb/common/rodc_helper.c index 1cd644c6def0..e81ecef79c02 100644 --- a/source4/dsdb/common/rodc_helper.c +++ b/source4/dsdb/common/rodc_helper.c @@ -176,7 +176,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct DBG_ERR("Attempt to use an RODC account that is not an RODC: %s\n", ldb_dn_get_linearized(rodc_msg->dn)); TALLOC_FREE(frame); - return WERR_DS_DRA_SECRETS_DENIED; + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; } werr = samdb_result_sid_array_dn(sam_ctx, rodc_msg, diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index c94e4f08e76c..cb0a923fc2d8 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -1141,7 +1141,7 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n", ldb_dn_get_linearized(rodc->msg->dn)); TALLOC_FREE(frame); - return WERR_DS_DRA_BAD_DN; + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; } /* @@ -1166,7 +1166,7 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, ldb_dn_get_linearized(rodc->msg->dn), ldb_errstring(rodc->kdc_db_ctx->samdb)); TALLOC_FREE(frame); - return WERR_DS_DRA_BAD_DN; + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; } if (rodc_machine_account->count != 1) { diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index 715070181207..c9bf5dd9cf57 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -276,7 +276,11 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, client_skdc_entry); if (!W_ERROR_IS_OK(werr)) { talloc_free(mem_ctx); - return KRB5KDC_ERR_TGT_REVOKED; + if (W_ERROR_EQUAL(werr, WERR_DOMAIN_CONTROLLER_NOT_FOUND)) { + return KRB5KDC_ERR_POLICY; + } else { + return KRB5KDC_ERR_TGT_REVOKED; + } } } -- 2.25.1 From 8ae82531025a859375df89cdc7acde9fec57c1c3 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 8 Oct 2021 08:29:51 +1300 Subject: [PATCH 206/224] CVE-2020-25719 kdc: Avoid races and multiple DB lookups in s4u2self check Looking up the DB twice is subject to a race and is a poor use of resources, so instead just pass in the record we already got when trying to confirm that the server in S4U2Self is the same as the requesting client. The client record has already been bound to the the original client by the SID check in the PAC. Likewise by looking up server only once we ensure that the keys looked up originally are in the record we confirm the SID for here. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/heimdal/kdc/krb5tgs.c | 26 +++++++++++------ source4/heimdal/lib/hdb/hdb.h | 2 +- source4/kdc/db-glue.c | 54 ++++++++++++----------------------- source4/kdc/db-glue.h | 5 ++-- source4/kdc/hdb-samba4.c | 43 ++++++++-------------------- 5 files changed, 52 insertions(+), 78 deletions(-) diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 301ca92091a9..d4a1c78e153c 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -313,7 +313,7 @@ check_constrained_delegation(krb5_context context, * Determine if s4u2self is allowed from this client to this server * * For example, regardless of the principal being impersonated, if the - * 'client' and 'server' are the same, then it's safe. + * 'client' and 'server' (target) are the same, then it's safe. */ static krb5_error_code @@ -321,18 +321,28 @@ check_s4u2self(krb5_context context, krb5_kdc_configuration *config, HDB *clientdb, hdb_entry_ex *client, - krb5_const_principal server) + hdb_entry_ex *target_server, + krb5_const_principal target_server_principal) { krb5_error_code ret; - /* if client does a s4u2self to itself, that ok */ - if (krb5_principal_compare(context, client->entry.principal, server) == TRUE) - return 0; - + /* + * Always allow the plugin to check, this might be faster, allow a + * policy or audit check and can look into the DB records + * directly + */ if (clientdb->hdb_check_s4u2self) { - ret = clientdb->hdb_check_s4u2self(context, clientdb, client, server); + ret = clientdb->hdb_check_s4u2self(context, + clientdb, + client, + target_server); if (ret == 0) return 0; + } else if (krb5_principal_compare(context, + client->entry.principal, + target_server_principal) == TRUE) { + /* if client does a s4u2self to itself, and there is no plugin, that is ok */ + return 0; } else { ret = KRB5KDC_ERR_BADOPTION; } @@ -1774,7 +1784,7 @@ server_lookup: * Check that service doing the impersonating is * requesting a ticket to it-self. */ - ret = check_s4u2self(context, config, clientdb, client, sp); + ret = check_s4u2self(context, config, clientdb, client, server, sp); if (ret) { kdc_log(context, config, 0, "S4U2Self: %s is not allowed " "to impersonate to service " diff --git a/source4/heimdal/lib/hdb/hdb.h b/source4/heimdal/lib/hdb/hdb.h index 6a09ecb6fe18..5ef9d9565f3f 100644 --- a/source4/heimdal/lib/hdb/hdb.h +++ b/source4/heimdal/lib/hdb/hdb.h @@ -266,7 +266,7 @@ typedef struct HDB{ /** * Check if s4u2self is allowed from this client to this server */ - krb5_error_code (*hdb_check_s4u2self)(krb5_context, struct HDB *, hdb_entry_ex *, krb5_const_principal); + krb5_error_code (*hdb_check_s4u2self)(krb5_context, struct HDB *, hdb_entry_ex *, hdb_entry_ex *); }HDB; #define HDB_INTERFACE_VERSION 7 diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c index a560a1cd84b7..8fe4f1ea3e94 100644 --- a/source4/kdc/db-glue.c +++ b/source4/kdc/db-glue.c @@ -2510,53 +2510,37 @@ krb5_error_code samba_kdc_nextkey(krb5_context context, /* Check if a given entry may delegate or do s4u2self to this target principal * - * This is currently a very nasty hack - allowing only delegation to itself. + * The safest way to determine 'self' is to check the DB record made at + * the time the principal was presented to the KDC. */ krb5_error_code samba_kdc_check_s4u2self(krb5_context context, - struct samba_kdc_db_context *kdc_db_ctx, - struct samba_kdc_entry *skdc_entry, - krb5_const_principal target_principal) + struct samba_kdc_entry *skdc_entry_client, + struct samba_kdc_entry *skdc_entry_server_target) { - krb5_error_code ret; - struct ldb_dn *realm_dn; - struct ldb_message *msg; struct dom_sid *orig_sid; struct dom_sid *target_sid; - const char *delegation_check_attrs[] = { - "objectSid", NULL - }; - - TALLOC_CTX *mem_ctx = talloc_named(kdc_db_ctx, 0, "samba_kdc_check_s4u2self"); - - if (!mem_ctx) { - ret = ENOMEM; - krb5_set_error_message(context, ret, "samba_kdc_check_s4u2self: talloc_named() failed!"); - return ret; - } - - ret = samba_kdc_lookup_server(context, kdc_db_ctx, mem_ctx, target_principal, - SDB_F_GET_CLIENT|SDB_F_GET_SERVER, - delegation_check_attrs, &realm_dn, &msg); - - if (ret != 0) { - talloc_free(mem_ctx); - return ret; - } + TALLOC_CTX *frame = talloc_stackframe(); - orig_sid = samdb_result_dom_sid(mem_ctx, skdc_entry->msg, "objectSid"); - target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + orig_sid = samdb_result_dom_sid(frame, + skdc_entry_client->msg, + "objectSid"); + target_sid = samdb_result_dom_sid(frame, + skdc_entry_server_target->msg, + "objectSid"); - /* Allow delegation to the same principal, even if by a different - * name. The easy and safe way to prove this is by SID - * comparison */ + /* + * Allow delegation to the same record (representing a + * principal), even if by a different name. The easy and safe + * way to prove this is by SID comparison + */ if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) { - talloc_free(mem_ctx); + talloc_free(frame); return KRB5KDC_ERR_BADOPTION; } - talloc_free(mem_ctx); - return ret; + talloc_free(frame); + return 0; } /* Certificates printed by a the Certificate Authority might have a diff --git a/source4/kdc/db-glue.h b/source4/kdc/db-glue.h index aa630f5d3491..cadfac1deb86 100644 --- a/source4/kdc/db-glue.h +++ b/source4/kdc/db-glue.h @@ -40,9 +40,8 @@ krb5_error_code samba_kdc_nextkey(krb5_context context, krb5_error_code samba_kdc_check_s4u2self(krb5_context context, - struct samba_kdc_db_context *kdc_db_ctx, - struct samba_kdc_entry *skdc_entry, - krb5_const_principal target_principal); + struct samba_kdc_entry *skdc_entry_client, + struct samba_kdc_entry *skdc_entry_server_target); krb5_error_code samba_kdc_check_pkinit_ms_upn_match(krb5_context context, diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c index 38ce9807c029..f0939193ad78 100644 --- a/source4/kdc/hdb-samba4.c +++ b/source4/kdc/hdb-samba4.c @@ -274,38 +274,19 @@ hdb_samba4_check_pkinit_ms_upn_match(krb5_context context, HDB *db, static krb5_error_code hdb_samba4_check_s4u2self(krb5_context context, HDB *db, - hdb_entry_ex *entry, - krb5_const_principal target_principal) + hdb_entry_ex *client_entry, + hdb_entry_ex *server_target_entry) { - struct samba_kdc_db_context *kdc_db_ctx; - struct samba_kdc_entry *skdc_entry; - krb5_error_code ret; - - kdc_db_ctx = talloc_get_type_abort(db->hdb_db, - struct samba_kdc_db_context); - skdc_entry = talloc_get_type_abort(entry->ctx, - struct samba_kdc_entry); - - ret = samba_kdc_check_s4u2self(context, kdc_db_ctx, - skdc_entry, - target_principal); - switch (ret) { - case 0: - break; - case SDB_ERR_WRONG_REALM: - ret = HDB_ERR_WRONG_REALM; - break; - case SDB_ERR_NOENTRY: - ret = HDB_ERR_NOENTRY; - break; - case SDB_ERR_NOT_FOUND_HERE: - ret = HDB_ERR_NOT_FOUND_HERE; - break; - default: - break; - } - - return ret; + struct samba_kdc_entry *skdc_client_entry + = talloc_get_type_abort(client_entry->ctx, + struct samba_kdc_entry); + struct samba_kdc_entry *skdc_server_target_entry + = talloc_get_type_abort(server_target_entry->ctx, + struct samba_kdc_entry); + + return samba_kdc_check_s4u2self(context, + skdc_client_entry, + skdc_server_target_entry); } static void reset_bad_password_netlogon(TALLOC_CTX *mem_ctx, -- 2.25.1 From f9f5d899c6c177daf553c2e6fce2451ebbed4d68 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 27 Sep 2021 12:10:02 +1300 Subject: [PATCH 207/224] CVE-2020-25721 auth: Fill in the new HAS_SAM_NAME_AND_SID values BUG: https://bugzilla.samba.org/show_bug.cgi?id=14835 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- python/samba/tests/krb5/s4u_tests.py | 2 -- selftest/knownfail_heimdal_kdc | 10 ---------- selftest/knownfail_mit_kdc | 4 ---- source4/kdc/pac-glue.c | 8 ++++++++ 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index 5005affd6b39..a80a7b3427e0 100755 --- a/python/samba/tests/krb5/s4u_tests.py +++ b/python/samba/tests/krb5/s4u_tests.py @@ -309,7 +309,6 @@ class S4UKerberosTests(KDCBaseTest): tgt=service_tgt, authenticator_subkey=authenticator_subkey, kdc_options=str(kdc_options), - expect_upn_dns_info_ex=False, expect_claims=False) self._generic_kdc_exchange(kdc_exchange_dict, @@ -611,7 +610,6 @@ class S4UKerberosTests(KDCBaseTest): kdc_options=kdc_options, pac_options=pac_options, expect_edata=expect_edata, - expect_upn_dns_info_ex=False, expected_proxy_target=expected_proxy_target, expected_transited_services=expected_transited_services, expect_pac=expect_pac) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 5b6fb0ddf692..80044551c9ca 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -245,12 +245,6 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed # -# The lack of KRB5SignedPath means we no longer return -# KRB5KRB_ERR_RESPONSE_TOO_BIG in this specific case -# -^samba4.krb5.kdc with machine account.as-req-pac-request.fl2000dc:local -# -# ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_no_auth_data_required ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_auth_data_required ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_a @@ -270,10 +264,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index dfdd17c655a3..b5b131103531 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -432,10 +432,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_mac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_mac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_upn_user -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_upn_dns_info_ex_user ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index cb0a923fc2d8..95f71c04b232 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -101,6 +101,14 @@ NTSTATUS samba_get_upn_info_pac_blob(TALLOC_CTX *mem_ctx, pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_CONSTRUCTED; } + pac_upn.upn_dns_info.flags |= PAC_UPN_DNS_FLAG_HAS_SAM_NAME_AND_SID; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.samaccountname + = info->info->account_name; + + pac_upn.upn_dns_info.ex.sam_name_and_sid.objectsid + = &info->sids[0]; + ndr_err = ndr_push_union_blob(upn_data, mem_ctx, &pac_upn, PAC_TYPE_UPN_DNS_INFO, (ndr_push_flags_fn_t)ndr_push_PAC_INFO); -- 2.25.1 From 9e1100323aeb2be33557bb8062c4eb419d328e8a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 20 Oct 2021 11:36:58 +1300 Subject: [PATCH 208/224] CVE-2020-25722 Ensure the structural objectclass cannot be changed If the structural objectclass is allowed to change, then the restrictions locking an object to remaining a user or computer will not be enforcable. Likewise other LDAP inheritance rules, which allow only certain child objects can be bypassed, which can in turn allow creation of (unprivileged) users where only DNS objects were expected. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14889 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- selftest/knownfail.d/ldap | 1 - selftest/knownfail.d/modify-order | 2 +- selftest/knownfail.d/uac_mod_lock | 28 --------------- selftest/knownfail.d/uac_objectclass_restrict | 4 --- source4/dsdb/samdb/ldb_modules/objectclass.c | 36 +++++++++++++++++++ 5 files changed, 37 insertions(+), 34 deletions(-) delete mode 100644 selftest/knownfail.d/uac_mod_lock diff --git a/selftest/knownfail.d/ldap b/selftest/knownfail.d/ldap index 545dc93db8e7..0331d3687d41 100644 --- a/selftest/knownfail.d/ldap +++ b/selftest/knownfail.d/ldap @@ -1,4 +1,3 @@ # the attributes too long test returns the wrong error ^samba4.ldap.python.+test_attribute_ranges_too_long samba4.ldap.python\(ad_dc_default\).*__main__.BasicTests.test_ldapSearchNoAttributes -^samba4.ldap.python.+test_objectclasses diff --git a/selftest/knownfail.d/modify-order b/selftest/knownfail.d/modify-order index e14cd1eb3564..76d538e1f9cf 100644 --- a/selftest/knownfail.d/modify-order +++ b/selftest/knownfail.d/modify-order @@ -1,8 +1,8 @@ samba4.ldap_modify_order.python.+ModifyOrderTests.test_modify_order_account_locality_device samba4.ldap_modify_order.python.+ModifyOrderTests.test_modify_order_container_flags_multivalue -samba4.ldap_modify_order.python.+ModifyOrderTests.test_modify_order_objectclass samba4.ldap_modify_order.python.+ModifyOrderTests.test_modify_order_objectclass2 samba4.ldap_modify_order.python.+ModifyOrderTests.test_modify_order_singlevalue samba4.ldap_modify_order.normal_user.+ModifyOrderTests.test_modify_order_account_locality_device samba4.ldap_modify_order.normal_user.+ModifyOrderTests.test_modify_order_container_flags[^_] +samba4.ldap_modify_order.normal_user.+ModifyOrderTests.test_modify_order_objectclass[^2] samba4.ldap_modify_order.normal_user.+ModifyOrderTests.test_modify_order_objectclass2 diff --git a/selftest/knownfail.d/uac_mod_lock b/selftest/knownfail.d/uac_mod_lock deleted file mode 100644 index 068a47c6e616..000000000000 --- a/selftest/knownfail.d/uac_mod_lock +++ /dev/null @@ -1,28 +0,0 @@ -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_NORMAL_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_priv_user_UF_NORMAL_ACCOUNT_to_computer_UF_WORKSTATION_TRUST_ACCOUNT_remove_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_NORMAL_ACCOUNT_to_user_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_SERVER_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_SERVER_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_computer_UF_WORKSTATION_TRUST_ACCOUNT_to_user_UF_WORKSTATION_TRUST_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_keep_dollar -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_lock_wp_user_UF_NORMAL_ACCOUNT_to_computer_UF_NORMAL_ACCOUNT_remove_dollar diff --git a/selftest/knownfail.d/uac_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index c4d4507c833b..a9ed5e888cc0 100644 --- a/selftest/knownfail.d/uac_objectclass_restrict +++ b/selftest/knownfail.d/uac_objectclass_restrict @@ -15,7 +15,3 @@ ^samba4.priv_attrs.strict.python\(ad_dc_default\).__main__.PrivAttrsTests.test_priv_attr_userAccountControl-t4d-user_mod-replace_CC_default_computer\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_add_computer_sd_cc\(ad_dc_default\) ^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_mod_computer_cc\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_NORMAL_ACCOUNT_user_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_SERVER_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) -^samba4.user_account_control.python\(ad_dc_default\).__main__.UserAccountControlTests.test_objectclass_mod_lock_UF_WORKSTATION_TRUST_ACCOUNT_computer_replace\(ad_dc_default\) diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c index 17cb34f4befb..5b97c9dec024 100644 --- a/source4/dsdb/samdb/ldb_modules/objectclass.c +++ b/source4/dsdb/samdb/ldb_modules/objectclass.c @@ -813,6 +813,7 @@ static int objectclass_do_mod(struct oc_context *ac) struct ldb_message_element *oc_el_entry, *oc_el_change; struct ldb_val *vals; struct ldb_message *msg; + const struct dsdb_class *current_structural_objectclass; const struct dsdb_class *objectclass; unsigned int i, j, k; bool found; @@ -832,6 +833,22 @@ static int objectclass_do_mod(struct oc_context *ac) return ldb_operr(ldb); } + /* + * Get the current new top-most structural object class + * + * We must not allow this to change + */ + + current_structural_objectclass + = dsdb_get_last_structural_class(ac->schema, + oc_el_entry); + if (current_structural_objectclass == NULL) { + ldb_asprintf_errstring(ldb, + "objectclass: cannot find current structural objectclass on %s!", + ldb_dn_get_linearized(ac->search_res->message->dn)); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + /* use a new message structure */ msg = ldb_msg_new(ac); if (msg == NULL) { @@ -941,6 +958,25 @@ static int objectclass_do_mod(struct oc_context *ac) return LDB_ERR_OBJECT_CLASS_VIOLATION; } + /* + * Has (so far, we re-check for each and every + * "objectclass" in the message) the structural + * objectclass changed? + */ + + if (objectclass != current_structural_objectclass) { + const char *dn + = ldb_dn_get_linearized(ac->search_res->message->dn); + ldb_asprintf_errstring(ldb, + "objectclass: not permitted " + "to change the structural " + "objectClass on %s [%s] => [%s]!", + dn, + current_structural_objectclass->lDAPDisplayName, + objectclass->lDAPDisplayName); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + /* Check for unrelated objectclasses */ ret = check_unrelated_objectclasses(ac->module, ac->schema, objectclass, -- 2.25.1 From 3b6d5316d229bd96bb9f74954f36648d211739de Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:42:41 +1300 Subject: [PATCH 209/224] CVE-2020-25719 s4:kdc: Add KDC support for PAC_REQUESTER_SID PAC buffer BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 57 ------------ source4/kdc/mit_samba.c | 4 +- source4/kdc/pac-glue.c | 163 ++++++++++++++++++++++++++++++--- source4/kdc/pac-glue.h | 2 + source4/kdc/wdc-samba4.c | 34 ++++++- 5 files changed, 185 insertions(+), 75 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 80044551c9ca..9cad1ca4d052 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -301,60 +301,3 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true -# -# PAC requester SID tests -# -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_as_requester_sid -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_logon_info_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_requester_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_req_from_rodc_no_requester_sid -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_renew -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_missing_rodc_renew -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_renew -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_requester_sid_rodc_renew -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_logon_info_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_requester_sid_mismatch_nonexisting -# -# PAC tests -# -^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc:local -^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc_ntvfs:local -^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc:local -^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc_ntvfs:local -^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc:local -^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc_ntvfs:local -^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc:local -^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc_ntvfs:local -^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc:local -^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc_ntvfs:local -^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc:local -^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc_ntvfs:local -^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008r2dc -^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008r2dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008r2dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008r2dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008r2dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2000dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2003dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008dc -^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008r2dc diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index 99a5214a8962..e015c5a52dbe 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -435,7 +435,7 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, &logon_info_blob, cred_ndr_ptr, &upn_dns_info_blob, - NULL, NULL, + NULL, NULL, NULL, NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(tmp_ctx); @@ -465,6 +465,7 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, upn_dns_info_blob, NULL, NULL, + NULL, pac); talloc_free(tmp_ctx); @@ -569,6 +570,7 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, NULL, &upn_blob, NULL, NULL, + NULL, NULL); if (!NT_STATUS_IS_OK(nt_status)) { code = EINVAL; diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 95f71c04b232..e0e483662c0f 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -40,7 +40,8 @@ static NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, const struct auth_user_info_dc *info, - DATA_BLOB *pac_data) + DATA_BLOB *pac_data, + DATA_BLOB *requester_sid_blob) { struct netr_SamInfo3 *info3; union PAC_INFO pac_info; @@ -50,6 +51,9 @@ NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, ZERO_STRUCT(pac_info); *pac_data = data_blob_null; + if (requester_sid_blob != NULL) { + *requester_sid_blob = data_blob_null; + } nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, info, &info3); if (!NT_STATUS_IS_OK(nt_status)) { @@ -75,6 +79,25 @@ NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx, return nt_status; } + if (requester_sid_blob != NULL && info->num_sids > 0) { + union PAC_INFO pac_requester_sid; + + ZERO_STRUCT(pac_requester_sid); + + pac_requester_sid.requester_sid.sid = info->sids[0]; + + ndr_err = ndr_push_union_blob(requester_sid_blob, mem_ctx, + &pac_requester_sid, + PAC_TYPE_REQUESTER_SID, + (ndr_push_flags_fn_t)ndr_push_PAC_INFO); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC_REQUESTER_SID (presig) push failed: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } + return NT_STATUS_OK; } @@ -460,6 +483,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, const DATA_BLOB *cred_blob, const DATA_BLOB *upn_blob, const DATA_BLOB *pac_attrs_blob, + const DATA_BLOB *requester_sid_blob, const DATA_BLOB *deleg_blob, krb5_pac *pac) { @@ -467,6 +491,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, krb5_data cred_data; krb5_data upn_data; krb5_data pac_attrs_data; + krb5_data requester_sid_data; krb5_data deleg_data; krb5_error_code ret; #ifdef SAMBA4_USES_HEIMDAL @@ -524,6 +549,20 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, } } + ZERO_STRUCT(requester_sid_data); + if (requester_sid_blob != NULL) { + ret = smb_krb5_copy_data_contents(&requester_sid_data, + requester_sid_blob->data, + requester_sid_blob->length); + if (ret != 0) { + smb_krb5_free_data_contents(context, &logon_data); + smb_krb5_free_data_contents(context, &cred_data); + smb_krb5_free_data_contents(context, &upn_data); + smb_krb5_free_data_contents(context, &pac_attrs_data); + return ret; + } + } + ZERO_STRUCT(deleg_data); if (deleg_blob != NULL) { ret = smb_krb5_copy_data_contents(&deleg_data, @@ -534,6 +573,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &cred_data); smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); return ret; } } @@ -544,6 +584,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &cred_data); smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -554,6 +595,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &cred_data); smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -566,6 +608,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, if (ret != 0) { smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -585,6 +628,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, if (ret != 0) { smb_krb5_free_data_contents(context, &upn_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -597,6 +641,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, smb_krb5_free_data_contents(context, &upn_data); if (ret != 0) { smb_krb5_free_data_contents(context, &pac_attrs_data); + smb_krb5_free_data_contents(context, &requester_sid_data); smb_krb5_free_data_contents(context, &deleg_data); return ret; } @@ -607,6 +652,18 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, PAC_TYPE_ATTRIBUTES_INFO, &pac_attrs_data); smb_krb5_free_data_contents(context, &pac_attrs_data); + if (ret != 0) { + smb_krb5_free_data_contents(context, &requester_sid_data); + smb_krb5_free_data_contents(context, &deleg_data); + return ret; + } + } + + if (requester_sid_blob != NULL) { + ret = krb5_pac_add_buffer(context, *pac, + PAC_TYPE_REQUESTER_SID, + &requester_sid_data); + smb_krb5_free_data_contents(context, &requester_sid_data); if (ret != 0) { smb_krb5_free_data_contents(context, &deleg_data); return ret; @@ -765,6 +822,7 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, DATA_BLOB **_upn_info_blob, DATA_BLOB **_pac_attrs_blob, const krb5_boolean *pac_request, + DATA_BLOB **_requester_sid_blob, struct auth_user_info_dc **_user_info_dc) { struct auth_user_info_dc *user_info_dc; @@ -772,6 +830,7 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, DATA_BLOB *cred_blob = NULL; DATA_BLOB *upn_blob = NULL; DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; NTSTATUS nt_status; *_logon_info_blob = NULL; @@ -782,6 +841,9 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, if (_pac_attrs_blob != NULL) { *_pac_attrs_blob = NULL; } + if (_requester_sid_blob != NULL) { + *_requester_sid_blob = NULL; + } logon_blob = talloc_zero(mem_ctx, DATA_BLOB); if (logon_blob == NULL) { @@ -807,6 +869,13 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, } } + if (_requester_sid_blob != NULL) { + requester_sid_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (requester_sid_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + nt_status = authsam_make_user_info_dc(mem_ctx, p->kdc_db_ctx->samdb, lpcfg_netbios_name(p->kdc_db_ctx->lp_ctx), lpcfg_sam_name(p->kdc_db_ctx->lp_ctx), @@ -824,7 +893,8 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, nt_status = samba_get_logon_info_pac_blob(logon_blob, user_info_dc, - logon_blob); + logon_blob, + requester_sid_blob); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(0, ("Building PAC LOGON INFO failed: %s\n", nt_errstr(nt_status))); @@ -880,6 +950,9 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, if (_pac_attrs_blob != NULL) { *_pac_attrs_blob = pac_attrs_blob; } + if (_requester_sid_blob != NULL) { + *_requester_sid_blob = requester_sid_blob; + } return NT_STATUS_OK; } @@ -912,7 +985,7 @@ NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, } nt_status = samba_get_logon_info_pac_blob(mem_ctx, - user_info_dc, pac_blob); + user_info_dc, pac_blob, NULL); return nt_status; } @@ -1062,6 +1135,52 @@ NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, return nt_status; } +static krb5_error_code samba_get_requester_sid(TALLOC_CTX *mem_ctx, + krb5_pac pac, + krb5_context context, + struct dom_sid *sid) +{ + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + + DATA_BLOB pac_requester_sid_in; + krb5_data k5pac_requester_sid_in; + + union PAC_INFO info; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_REQUESTER_SID, + &k5pac_requester_sid_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_requester_sid_in = data_blob_const(k5pac_requester_sid_in.data, + k5pac_requester_sid_in.length); + + ndr_err = ndr_pull_union_blob(&pac_requester_sid_in, tmp_ctx, &info, + PAC_TYPE_REQUESTER_SID, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_requester_sid_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC REQUESTER_SID: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return EINVAL; + } + + *sid = info.requester_sid.sid; + + talloc_free(tmp_ctx); + return 0; +} + /* Does a parse and SID check, but no crypto. */ krb5_error_code samba_kdc_validate_pac_blob( krb5_context context, @@ -1075,22 +1194,36 @@ krb5_error_code samba_kdc_validate_pac_blob( krb5_error_code code; bool ok; - code = kerberos_pac_to_user_info_dc(frame, - pac, - context, - &pac_user_info, - NULL, - NULL); - if (code != 0) { - goto out; - } + /* + * First, try to get the SID from the requester SID buffer in the PAC. + */ + code = samba_get_requester_sid(frame, pac, context, &pac_sid); + + if (code == ENOENT) { + /* + * If the requester SID buffer isn't present, fall back to the + * SID in the LOGON_INFO PAC buffer. + */ + code = kerberos_pac_to_user_info_dc(frame, + pac, + context, + &pac_user_info, + NULL, + NULL); + if (code != 0) { + goto out; + } + + if (pac_user_info->num_sids == 0) { + code = EINVAL; + goto out; + } - if (pac_user_info->num_sids == 0) { - code = EINVAL; + pac_sid = pac_user_info->sids[0]; + } else if (code != 0) { goto out; } - pac_sid = pac_user_info->sids[0]; client_sid = samdb_result_dom_sid(frame, client_skdc_entry->msg, "objectSid"); diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h index 89aa8da63c39..266e000f9cdd 100644 --- a/source4/kdc/pac-glue.h +++ b/source4/kdc/pac-glue.h @@ -32,6 +32,7 @@ krb5_error_code samba_make_krb5_pac(krb5_context context, const DATA_BLOB *cred_blob, const DATA_BLOB *upn_blob, const DATA_BLOB *pac_attrs_blob, + const DATA_BLOB *requester_sid_blob, const DATA_BLOB *deleg_blob, krb5_pac *pac); @@ -53,6 +54,7 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, DATA_BLOB **_upn_info_blob, DATA_BLOB **_pac_attrs_blob, const krb5_boolean *pac_request, + DATA_BLOB **_requester_sid_blob, struct auth_user_info_dc **_user_info_dc); NTSTATUS samba_kdc_update_pac_blob(TALLOC_CTX *mem_ctx, krb5_context context, diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index c9bf5dd9cf57..ecd182702c34 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -49,6 +49,7 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, DATA_BLOB *cred_blob = NULL; DATA_BLOB *upn_blob = NULL; DATA_BLOB *pac_attrs_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; krb5_error_code ret; NTSTATUS nt_status; struct samba_kdc_entry *skdc_entry = @@ -70,6 +71,7 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, &upn_blob, &pac_attrs_blob, pac_request, + &requester_sid_blob, NULL); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); @@ -91,7 +93,7 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context, ret = samba_make_krb5_pac(context, logon_blob, cred_blob, upn_blob, pac_attrs_blob, - NULL, pac); + requester_sid_blob, NULL, pac); talloc_free(mem_ctx); return ret; @@ -125,6 +127,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, krb5_pac new_pac = NULL; DATA_BLOB *pac_blob = NULL; DATA_BLOB *upn_blob = NULL; + DATA_BLOB *requester_sid_blob = NULL; DATA_BLOB *deleg_blob = NULL; krb5_error_code ret; NTSTATUS nt_status; @@ -141,6 +144,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, ssize_t kdc_checksum_idx = -1; ssize_t tkt_checksum_idx = -1; ssize_t attrs_info_idx = -1; + ssize_t requester_sid_idx = -1; if (!mem_ctx) { return ENOMEM; @@ -257,7 +261,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, nt_status = samba_kdc_get_pac_blobs(mem_ctx, client_skdc_entry, &pac_blob, NULL, &upn_blob, - NULL, NULL, + NULL, NULL, &requester_sid_blob, &user_info_dc); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(mem_ctx); @@ -408,6 +412,18 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, } attrs_info_idx = i; break; + case PAC_TYPE_REQUESTER_SID: + if (requester_sid_idx != -1) { + DEBUG(1, ("requester sid type[%"PRIu32"] twice [%zd] and [%zu]: \n", + types[i], + requester_sid_idx, + i)); + SAFE_FREE(types); + talloc_free(mem_ctx); + return EINVAL; + } + requester_sid_idx = i; + break; default: continue; } @@ -546,6 +562,11 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, * we just add a place holder here. */ type_blob = data_blob_const(&zero_byte, 1); + + if (requester_sid_idx == -1 && requester_sid_blob != NULL) { + /* inject REQUESTER_SID behind */ + forced_next_type = PAC_TYPE_REQUESTER_SID; + } break; case PAC_TYPE_KDC_CHECKSUM: /* @@ -557,6 +578,15 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, case PAC_TYPE_ATTRIBUTES_INFO: /* just copy... */ break; + case PAC_TYPE_REQUESTER_SID: + /* + * Replace in the RODC case, otherwise + * requester_sid_blob is NULL and we just copy. + */ + if (requester_sid_blob != NULL) { + type_blob = *requester_sid_blob; + } + break; default: /* just copy... */ break; -- 2.25.1 From 9c577a5ff44818627b3ee0b46a92f27cddd951b0 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 13:53:25 +1300 Subject: [PATCH 210/224] CVE-2020-25719 heimdal:kdc: Check return code BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/heimdal/kdc/krb5tgs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index d4a1c78e153c..5cc45826cbe8 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1361,7 +1361,10 @@ tgs_build_reply(krb5_context context, ret = KRB5KDC_ERR_POLICY; goto out; } - _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm); + ret = _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm); + if (ret) { + goto out; + } if(t->enc_part.kvno){ second_kvno = *t->enc_part.kvno; kvno_ptr = &second_kvno; -- 2.25.1 From 2ca1a6cda9b3e9777bc552f24e6b6c12ae30bbf9 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:34:44 +1300 Subject: [PATCH 211/224] CVE-2020-25719 heimdal:kdc: Move fetching krbtgt entry to before enctype selection This allows us to use it when validating user-to-user. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/heimdal/kdc/krb5tgs.c | 70 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 5cc45826cbe8..9cad3ac7a76d 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1518,6 +1518,41 @@ server_lookup: goto out; } + /* Now refetch the primary krbtgt, and get the current kvno (the + * sign check may have been on an old kvno, and the server may + * have been an incoming trust) */ + ret = krb5_make_principal(context, &krbtgt_principal, + krb5_principal_get_comp_string(context, + krbtgt->entry.principal, + 1), + KRB5_TGS_NAME, + krb5_principal_get_comp_string(context, + krbtgt->entry.principal, + 1), NULL); + if(ret) { + kdc_log(context, config, 0, + "Failed to generate krbtgt principal"); + goto out; + } + + ret = _kdc_db_fetch(context, config, krbtgt_principal, HDB_F_GET_KRBTGT, NULL, NULL, &krbtgt_out); + krb5_free_principal(context, krbtgt_principal); + if (ret) { + krb5_error_code ret2; + char *ktpn, *ktpn2; + ret = krb5_unparse_name(context, krbtgt->entry.principal, &ktpn); + ret2 = krb5_unparse_name(context, krbtgt_principal, &ktpn2); + kdc_log(context, config, 0, + "Request with wrong krbtgt: %s, %s not found in our database", + (ret == 0) ? ktpn : "", (ret2 == 0) ? ktpn2 : ""); + if(ret == 0) + free(ktpn); + if(ret2 == 0) + free(ktpn2); + ret = KRB5KRB_AP_ERR_NOT_US; + goto out; + } + /* * Select enctype, return key and kvno. */ @@ -1568,41 +1603,6 @@ server_lookup: * backward. */ - /* Now refetch the primary krbtgt, and get the current kvno (the - * sign check may have been on an old kvno, and the server may - * have been an incoming trust) */ - ret = krb5_make_principal(context, &krbtgt_principal, - krb5_principal_get_comp_string(context, - krbtgt->entry.principal, - 1), - KRB5_TGS_NAME, - krb5_principal_get_comp_string(context, - krbtgt->entry.principal, - 1), NULL); - if(ret) { - kdc_log(context, config, 0, - "Failed to generate krbtgt principal"); - goto out; - } - - ret = _kdc_db_fetch(context, config, krbtgt_principal, HDB_F_GET_KRBTGT, NULL, NULL, &krbtgt_out); - krb5_free_principal(context, krbtgt_principal); - if (ret) { - krb5_error_code ret2; - char *ktpn, *ktpn2; - ret = krb5_unparse_name(context, krbtgt->entry.principal, &ktpn); - ret2 = krb5_unparse_name(context, krbtgt_principal, &ktpn2); - kdc_log(context, config, 0, - "Request with wrong krbtgt: %s, %s not found in our database", - (ret == 0) ? ktpn : "", (ret2 == 0) ? ktpn2 : ""); - if(ret == 0) - free(ktpn); - if(ret2 == 0) - free(ktpn2); - ret = KRB5KRB_AP_ERR_NOT_US; - goto out; - } - /* The first realm is the realm of the service, the second is * krbtgt//@REALM component of the krbtgt DN the request was * encrypted to. The redirection via the krbtgt_out entry allows -- 2.25.1 From 1cd83b8bd1f0bc97a4debf0960b9649acdaf2be3 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 13:50:03 +1300 Subject: [PATCH 212/224] CVE-2020-25719 heimdal:kdc: Use sname from request rather than user-to-user TGT client name BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 20 ------ source4/heimdal/kdc/krb5tgs.c | 113 ++++++++++++++++----------------- 2 files changed, 55 insertions(+), 78 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 9cad1ca4d052..852a02d6d184 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -265,39 +265,19 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_matching_sname_host ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_sname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_non_existent_sname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_other_sname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_req ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_krbtgt_link ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_partial_secrets ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_cname_host -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_cname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_correct_realm -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_other_cname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_wrong_realm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_srealm ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed -# -# PAC request tests -# -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_pac_request_true -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_false -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_none -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_user_pac_request_true diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 9cad3ac7a76d..d6ca1fe601cd 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1339,64 +1339,7 @@ tgs_build_reply(krb5_context context, if (b->kdc_options.canonicalize) flags |= HDB_F_CANON; - if(b->kdc_options.enc_tkt_in_skey){ - Ticket *t; - hdb_entry_ex *uu; - krb5_principal p; - Key *uukey; - krb5uint32 second_kvno = 0; - krb5uint32 *kvno_ptr = NULL; - - if(b->additional_tickets == NULL || - b->additional_tickets->len == 0){ - ret = KRB5KDC_ERR_BADOPTION; /* ? */ - kdc_log(context, config, 0, - "No second ticket present in request"); - goto out; - } - t = &b->additional_tickets->val[0]; - if(!get_krbtgt_realm(&t->sname)){ - kdc_log(context, config, 0, - "Additional ticket is not a ticket-granting ticket"); - ret = KRB5KDC_ERR_POLICY; - goto out; - } - ret = _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm); - if (ret) { - goto out; - } - if(t->enc_part.kvno){ - second_kvno = *t->enc_part.kvno; - kvno_ptr = &second_kvno; - } - ret = _kdc_db_fetch(context, config, p, - HDB_F_GET_KRBTGT, kvno_ptr, - NULL, &uu); - krb5_free_principal(context, p); - if(ret){ - if (ret == HDB_ERR_NOENTRY) - ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; - goto out; - } - ret = hdb_enctype2key(context, &uu->entry, - t->enc_part.etype, &uukey); - if(ret){ - _kdc_free_ent(context, uu); - ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ - goto out; - } - ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); - _kdc_free_ent(context, uu); - if(ret) - goto out; - - ret = verify_flags(context, config, &adtkt, spn); - if (ret) - goto out; - - s = &adtkt.cname; - r = adtkt.crealm; - } else if (s == NULL) { + if (s == NULL) { ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; krb5_set_error_message(context, ret, "No server in request"); goto out; @@ -1561,7 +1504,61 @@ server_lookup: krb5_enctype etype; if(b->kdc_options.enc_tkt_in_skey) { + Ticket *t; + hdb_entry_ex *uu; + krb5_principal p; + Key *uukey; + krb5uint32 second_kvno = 0; + krb5uint32 *kvno_ptr = NULL; size_t i; + + if(b->additional_tickets == NULL || + b->additional_tickets->len == 0){ + ret = KRB5KDC_ERR_BADOPTION; /* ? */ + kdc_log(context, config, 0, + "No second ticket present in request"); + goto out; + } + t = &b->additional_tickets->val[0]; + if(!get_krbtgt_realm(&t->sname)){ + kdc_log(context, config, 0, + "Additional ticket is not a ticket-granting ticket"); + ret = KRB5KDC_ERR_POLICY; + goto out; + } + ret = _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm); + if (ret) { + goto out; + } + if(t->enc_part.kvno){ + second_kvno = *t->enc_part.kvno; + kvno_ptr = &second_kvno; + } + ret = _kdc_db_fetch(context, config, p, + HDB_F_GET_KRBTGT, kvno_ptr, + NULL, &uu); + krb5_free_principal(context, p); + if(ret){ + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; + goto out; + } + ret = hdb_enctype2key(context, &uu->entry, + t->enc_part.etype, &uukey); + if(ret){ + _kdc_free_ent(context, uu); + ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ + goto out; + } + ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); + _kdc_free_ent(context, uu); + if(ret) + goto out; + + ret = verify_flags(context, config, &adtkt, spn); + if (ret) + goto out; + ekey = &adtkt.key; for(i = 0; i < b->etype.len; i++) if (b->etype.val[i] == adtkt.key.keytype) -- 2.25.1 From 985e4057c2fec75e393edc8df16151dd3d3b4b76 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 15:51:58 +1300 Subject: [PATCH 213/224] CVE-2020-25719 heimdal:kdc: Check name in request against name in user-to-user TGT BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 3 -- source4/heimdal/kdc/krb5tgs.c | 56 +++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 852a02d6d184..f1b3cfa6b56e 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -276,8 +276,5 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_tgt_cname_host -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_wrong_sname_krbtgt ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index d6ca1fe601cd..f59f99f369f5 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1302,9 +1302,12 @@ tgs_build_reply(krb5_context context, krb5_error_code ret; krb5_principal cp = NULL, sp = NULL, tp = NULL, dp = NULL; krb5_principal krbtgt_principal = NULL; + krb5_principal user2user_princ = NULL; char *spn = NULL, *cpn = NULL, *tpn = NULL, *dpn = NULL; + char *user2user_name = NULL; hdb_entry_ex *server = NULL, *client = NULL, *s4u2self_impersonated_client = NULL; HDB *clientdb, *s4u2self_impersonated_clientdb; + HDB *serverdb = NULL; krb5_realm ref_realm = NULL; EncTicketPart *tgt = &ticket->ticket; const char *tgt_realm = /* Realm of TGT issuer */ @@ -1370,7 +1373,7 @@ tgs_build_reply(krb5_context context, server_lookup: ret = _kdc_db_fetch(context, config, sp, HDB_F_GET_SERVER | flags, - NULL, NULL, &server); + NULL, &serverdb, &server); if(ret == HDB_ERR_NOT_FOUND_HERE) { kdc_log(context, config, 5, "target %s does not have secrets at this KDC, need to proxy", sp); @@ -1511,6 +1514,7 @@ server_lookup: krb5uint32 second_kvno = 0; krb5uint32 *kvno_ptr = NULL; size_t i; + hdb_entry_ex *user2user_client = NULL; if(b->additional_tickets == NULL || b->additional_tickets->len == 0){ @@ -1559,6 +1563,53 @@ server_lookup: if (ret) goto out; + /* Fetch the name from the TGT. */ + ret = _krb5_principalname2krb5_principal(context, &user2user_princ, + adtkt.cname, adtkt.crealm); + if (ret) { + goto out; + } + + ret = krb5_unparse_name(context, user2user_princ, &user2user_name); + if (ret) { + goto out; + } + + /* Look up the name given in the TGT in the database. */ + ret = db_fetch_client(context, config, flags, user2user_princ, user2user_name, + krb5_principal_get_realm(context, krbtgt_out->entry.principal), + NULL, &user2user_client); + if (ret) { + goto out; + } + + if (user2user_client != NULL) { + /* + * If the account is present in the database, check the account + * flags. + */ + ret = kdc_check_flags(context, config, + user2user_client, user2user_name, + NULL, NULL, + FALSE); + if (ret) { + _kdc_free_ent(context, user2user_client); + goto out; + } + + /* + * Also check that the account is the same one specified in the + * request. + */ + ret = check_s4u2self(context, config, serverdb, server, user2user_client, user2user_princ); + if (ret) { + _kdc_free_ent(context, user2user_client); + goto out; + } + } + + _kdc_free_ent(context, user2user_client); + ekey = &adtkt.key; for(i = 0; i < b->etype.len; i++) if (b->etype.val[i] == adtkt.key.keytype) @@ -2062,6 +2113,7 @@ server_lookup: reply); out: + free(user2user_name); if (tpn != cpn) free(tpn); free(spn); @@ -2079,6 +2131,8 @@ out: if(s4u2self_impersonated_client) _kdc_free_ent(context, s4u2self_impersonated_client); + if (user2user_princ) + krb5_free_principal(context, user2user_princ); if (tp && tp != cp) krb5_free_principal(context, tp); if (cp) -- 2.25.1 From 46a4f8794e2460214d3f686199e31a40e6c62940 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 15:52:06 +1300 Subject: [PATCH 214/224] CVE-2020-25719 heimdal:kdc: Verify PAC in TGT provided for user-to-user authentication BUG: https://bugzilla.samba.org/show_bug.cgi?id=14873 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 11 ----------- source4/heimdal/kdc/krb5tgs.c | 33 ++++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index f1b3cfa6b56e..4bde0f339775 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -264,17 +264,6 @@ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_authdata_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_no_pac -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_allowed_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_denied -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_krbtgt_link -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_no_partial_secrets -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_allowed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_sid_mismatch_nonexisting -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_existing -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index f59f99f369f5..ed1fd420a3aa 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -1306,6 +1306,7 @@ tgs_build_reply(krb5_context context, char *spn = NULL, *cpn = NULL, *tpn = NULL, *dpn = NULL; char *user2user_name = NULL; hdb_entry_ex *server = NULL, *client = NULL, *s4u2self_impersonated_client = NULL; + hdb_entry_ex *user2user_krbtgt = NULL; HDB *clientdb, *s4u2self_impersonated_clientdb; HDB *serverdb = NULL; krb5_realm ref_realm = NULL; @@ -1316,6 +1317,7 @@ tgs_build_reply(krb5_context context, krb5_keyblock sessionkey; krb5_kvno kvno; krb5_pac mspac = NULL; + krb5_pac user2user_pac = NULL; uint16_t rodc_id; krb5_boolean add_ticket_sig = FALSE; hdb_entry_ex *krbtgt_out = NULL; @@ -1508,13 +1510,13 @@ server_lookup: if(b->kdc_options.enc_tkt_in_skey) { Ticket *t; - hdb_entry_ex *uu; krb5_principal p; Key *uukey; krb5uint32 second_kvno = 0; krb5uint32 *kvno_ptr = NULL; size_t i; hdb_entry_ex *user2user_client = NULL; + krb5_boolean user2user_kdc_issued = FALSE; if(b->additional_tickets == NULL || b->additional_tickets->len == 0){ @@ -1540,22 +1542,20 @@ server_lookup: } ret = _kdc_db_fetch(context, config, p, HDB_F_GET_KRBTGT, kvno_ptr, - NULL, &uu); + NULL, &user2user_krbtgt); krb5_free_principal(context, p); if(ret){ if (ret == HDB_ERR_NOENTRY) ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto out; } - ret = hdb_enctype2key(context, &uu->entry, + ret = hdb_enctype2key(context, &user2user_krbtgt->entry, t->enc_part.etype, &uukey); if(ret){ - _kdc_free_ent(context, uu); ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ goto out; } ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); - _kdc_free_ent(context, uu); if(ret) goto out; @@ -1608,7 +1608,27 @@ server_lookup: } } + /* Verify the PAC of the TGT. */ + ret = check_PAC(context, config, user2user_princ, NULL, + user2user_client, user2user_krbtgt, user2user_krbtgt, user2user_krbtgt, + &uukey->key, &tkey_check->key, &adtkt, &user2user_kdc_issued, &user2user_pac); _kdc_free_ent(context, user2user_client); + if (ret) { + const char *msg = krb5_get_error_message(context, ret); + kdc_log(context, config, 0, + "Verify PAC failed for %s (%s) from %s with %s", + spn, user2user_name, from, msg); + krb5_free_error_message(context, msg); + goto out; + } + + if (user2user_pac == NULL || !user2user_kdc_issued) { + ret = KRB5KDC_ERR_BADOPTION; + kdc_log(context, config, 0, + "Ticket not signed with PAC; user-to-user failed (%s).", + mspac ? "Ticket unsigned" : "No PAC"); + goto out; + } ekey = &adtkt.key; for(i = 0; i < b->etype.len; i++) @@ -2130,6 +2150,8 @@ out: _kdc_free_ent(context, client); if(s4u2self_impersonated_client) _kdc_free_ent(context, s4u2self_impersonated_client); + if (user2user_krbtgt) + _kdc_free_ent(context, user2user_krbtgt); if (user2user_princ) krb5_free_principal(context, user2user_princ); @@ -2148,6 +2170,7 @@ out: free_EncTicketPart(&adtkt); krb5_pac_free(context, mspac); + krb5_pac_free(context, user2user_pac); return ret; } -- 2.25.1 From a0726903d21c8f48c9fbeda06cbbe8b9a2c864db Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 4 Oct 2021 15:18:34 +1300 Subject: [PATCH 215/224] CVE-2020-25722 kdc: Do not honour a request for a 3-part SPN (ending in our domain/realm) unless a DC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14776 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- selftest/knownfail_heimdal_kdc | 6 ------ selftest/knownfail_mit_kdc | 6 ------ source4/kdc/db-glue.c | 23 +++++++++++++++++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 4bde0f339775..8bf36faf8edc 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -250,12 +250,6 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_a ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_b # -# SPN tests -# -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer -# # KDC TGT tests # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index b5b131103531..b4e819c83dfb 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -381,12 +381,6 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008dc ^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008r2dc # -# SPN tests -# -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_instance_spn_computer -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_domain_spn_computer -^samba.tests.krb5.spn_tests.samba.tests.krb5.spn_tests.SpnTests.test_spn_3_part_our_realm_spn_computer -# # Alias tests # ^samba.tests.krb5.alias_tests.samba.tests.krb5.alias_tests.AliasTests.test_create_alias_delete diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c index 8fe4f1ea3e94..aff74f2ee715 100644 --- a/source4/kdc/db-glue.c +++ b/source4/kdc/db-glue.c @@ -970,6 +970,29 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context, entry_ex->entry.flags.server = 0; } } + + /* + * We restrict a 3-part SPN ending in my domain/realm to full + * domain controllers. + * + * This avoids any cases where (eg) a demoted DC still has + * these more restricted SPNs. + */ + if (krb5_princ_size(context, principal) > 2) { + char *third_part + = smb_krb5_principal_get_comp_string(mem_ctx, + context, + principal, + 2); + bool is_our_realm = + lpcfg_is_my_domain_or_realm(lp_ctx, + third_part); + bool is_dc = userAccountControl & + (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT); + if (is_our_realm && !is_dc) { + entry_ex->entry.flags.server = 0; + } + } /* * To give the correct type of error to the client, we must * not just return the entry without .server set, we must -- 2.25.1 From 011a84a0c2fceea26ab202a18d001d335d28e461 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 14:35:52 +1300 Subject: [PATCH 216/224] CVE-2020-25719 heimdal:kdc: Require PAC to be present BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 4 ---- source4/heimdal/kdc/krb5tgs.c | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 8bf36faf8edc..933b6c2af042 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -252,12 +252,8 @@ # # KDC TGT tests # -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_user2user_rodc_not_revealed -^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_validate_rodc_not_revealed diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index ed1fd420a3aa..fb2ef8230c92 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -74,9 +74,12 @@ check_PAC(krb5_context context, *ppac = NULL; ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac); - if (ret || pac == NULL) + if (ret) return ret; + if (pac == NULL) + return KRB5KDC_ERR_BADOPTION; + /* Verify the server signature. */ ret = krb5_pac_verify(context, pac, tkt->authtime, client_principal, server_check_key, NULL); -- 2.25.1 From c10c66332dbe0d927bb914e511f8f4d7a702a995 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:43:28 +1300 Subject: [PATCH 217/224] CVE-2020-25718 tests/krb5: Only fetch RODC account credentials when necessary BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_base_test.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py index 4b4f1486f60d..f64bd0b206ef 100644 --- a/python/samba/tests/krb5/kdc_base_test.py +++ b/python/samba/tests/krb5/kdc_base_test.py @@ -710,9 +710,6 @@ class KDCBaseTest(RawKerberosTest): self.assertFalse(not_delegated) samdb = self.get_samdb() - rodc_samdb = self.get_rodc_samdb() - - rodc_dn = self.get_server_dn(rodc_samdb) user_name = self.get_new_username() if name_prefix is not None: @@ -764,6 +761,9 @@ class KDCBaseTest(RawKerberosTest): # Handle secret replication to the RODC. if allowed_replication or revealed_to_rodc: + rodc_samdb = self.get_rodc_samdb() + rodc_dn = self.get_server_dn(rodc_samdb) + # Allow replicating this account's secrets if requested, or allow # it only temporarily if we're about to replicate them. allowed_cleanup = self.add_to_group( @@ -784,6 +784,9 @@ class KDCBaseTest(RawKerberosTest): revealed=revealed_to_rodc) if denied_replication: + rodc_samdb = self.get_rodc_samdb() + rodc_dn = self.get_server_dn(rodc_samdb) + # Deny replicating this account's secrets to the RODC. self.add_to_group(dn, rodc_dn, 'msDS-NeverRevealGroup') -- 2.25.1 From fff18e2da9d62c037bcf8d7ef35f48b846197c68 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:07:07 +1300 Subject: [PATCH 218/224] CVE-2020-25719 tests/krb5: Add tests for using a ticket with a renamed account BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/kdc_tgs_tests.py | 17 ++++++++++ python/samba/tests/krb5/test_ccache.py | 41 ++++++++++++++++------ python/samba/tests/krb5/test_ldap.py | 33 ++++++++++++++---- python/samba/tests/krb5/test_rpc.py | 27 ++++++++++++--- python/samba/tests/krb5/test_smb.py | 43 ++++++++++++++++++------ selftest/knownfail_mit_kdc | 1 + 6 files changed, 129 insertions(+), 33 deletions(-) diff --git a/python/samba/tests/krb5/kdc_tgs_tests.py b/python/samba/tests/krb5/kdc_tgs_tests.py index cfe1ad42d615..abac5a47a560 100755 --- a/python/samba/tests/krb5/kdc_tgs_tests.py +++ b/python/samba/tests/krb5/kdc_tgs_tests.py @@ -1769,6 +1769,23 @@ class KdcTgsTests(KDCBaseTest): pac = self.get_ticket_pac(ticket) self.assertIsNotNone(pac) + def test_tgs_rename(self): + creds = self.get_cached_creds(account_type=self.AccountType.USER, + use_cache=False) + tgt = self.get_tgt(creds) + + # Rename the account. + new_name = self.get_new_username() + + samdb = self.get_samdb() + msg = ldb.Message(creds.get_dn()) + msg['sAMAccountName'] = ldb.MessageElement(new_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + + self._run_tgs(tgt, expected_error=KDC_ERR_C_PRINCIPAL_UNKNOWN) + def _get_tgt(self, client_creds, renewable=False, diff --git a/python/samba/tests/krb5/test_ccache.py b/python/samba/tests/krb5/test_ccache.py index d21ec84796e5..75038ea5cc15 100755 --- a/python/samba/tests/krb5/test_ccache.py +++ b/python/samba/tests/krb5/test_ccache.py @@ -20,6 +20,8 @@ import sys import os +import ldb + from ldb import SCOPE_SUBTREE from samba import NTSTATUSError, gensec from samba.auth import AuthContext @@ -42,13 +44,16 @@ class CcacheTests(KDCBaseTest): """ def test_ccache(self): - self._run_ccache_test("ccacheusr") + self._run_ccache_test() + + def test_ccache_rename(self): + self._run_ccache_test(rename=True) def test_ccache_no_pac(self): - self._run_ccache_test("ccacheusr_nopac", include_pac=False, + self._run_ccache_test(include_pac=False, expect_anon=True, allow_error=True) - def _run_ccache_test(self, user_name, include_pac=True, + def _run_ccache_test(self, rename=False, include_pac=True, expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the @@ -60,7 +65,10 @@ class CcacheTests(KDCBaseTest): samdb = self.get_samdb() # Create the user account. - (user_credentials, _) = self.create_account(samdb, user_name) + user_credentials = self.get_cached_creds( + account_type=self.AccountType.USER, + use_cache=False) + user_name = user_credentials.get_username() # Create the machine account. (mach_credentials, _) = self.create_account( @@ -80,6 +88,24 @@ class CcacheTests(KDCBaseTest): # Remove the cached credentials file. self.addCleanup(os.remove, cachefile.name) + # Retrieve the user account's SID. + ldb_res = samdb.search(scope=SCOPE_SUBTREE, + expression="(sAMAccountName=%s)" % user_name, + attrs=["objectSid"]) + self.assertEqual(1, len(ldb_res)) + sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) + + if rename: + # Rename the account. + + new_name = self.get_new_username() + + msg = ldb.Message(user_credentials.get_dn()) + msg['sAMAccountName'] = ldb.MessageElement(new_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + # Authenticate in-process to the machine account using the user's # cached credentials. @@ -121,13 +147,6 @@ class CcacheTests(KDCBaseTest): # Ensure that the first SID contained within the obtained security # token is the SID of the user we created. - # Retrieve the user account's SID. - ldb_res = samdb.search(scope=SCOPE_SUBTREE, - expression="(sAMAccountName=%s)" % user_name, - attrs=["objectSid"]) - self.assertEqual(1, len(ldb_res)) - sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) - # Retrieve the SIDs from the security token. try: session = gensec_server.session_info() diff --git a/python/samba/tests/krb5/test_ldap.py b/python/samba/tests/krb5/test_ldap.py index 0205bdf6fb73..c1375730e6fb 100755 --- a/python/samba/tests/krb5/test_ldap.py +++ b/python/samba/tests/krb5/test_ldap.py @@ -20,6 +20,8 @@ import sys import os +import ldb + from ldb import LdbError, ERR_OPERATIONS_ERROR, SCOPE_BASE, SCOPE_SUBTREE from samba.dcerpc import security from samba.ndr import ndr_unpack @@ -41,13 +43,16 @@ class LdapTests(KDCBaseTest): """ def test_ldap(self): - self._run_ldap_test("ldapusr") + self._run_ldap_test() + + def test_ldap_rename(self): + self._run_ldap_test(rename=True) def test_ldap_no_pac(self): - self._run_ldap_test("ldapusr_nopac", include_pac=False, + self._run_ldap_test(include_pac=False, expect_anon=True, allow_error=True) - def _run_ldap_test(self, user_name, include_pac=True, + def _run_ldap_test(self, rename=False, include_pac=True, expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the @@ -59,7 +64,10 @@ class LdapTests(KDCBaseTest): service = "ldap" # Create the user account. - (user_credentials, _) = self.create_account(samdb, user_name) + user_credentials = self.get_cached_creds( + account_type=self.AccountType.USER, + use_cache=False) + user_name = user_credentials.get_username() mach_credentials = self.get_dc_creds() @@ -75,9 +83,6 @@ class LdapTests(KDCBaseTest): # Remove the cached credentials file. self.addCleanup(os.remove, cachefile.name) - # Authenticate in-process to the machine account using the user's - # cached credentials. - # Retrieve the user account's SID. ldb_res = samdb.search(scope=SCOPE_SUBTREE, expression="(sAMAccountName=%s)" % user_name, @@ -85,6 +90,20 @@ class LdapTests(KDCBaseTest): self.assertEqual(1, len(ldb_res)) sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) + if rename: + # Rename the account. + + new_name = self.get_new_username() + + msg = ldb.Message(user_credentials.get_dn()) + msg['sAMAccountName'] = ldb.MessageElement(new_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + + # Authenticate in-process to the machine account using the user's + # cached credentials. + # Connect to the machine account and retrieve the user SID. try: ldb_as_user = SamDB(url="ldap://%s" % mach_name, diff --git a/python/samba/tests/krb5/test_rpc.py b/python/samba/tests/krb5/test_rpc.py index 0f2170a8dede..03c125f518a7 100755 --- a/python/samba/tests/krb5/test_rpc.py +++ b/python/samba/tests/krb5/test_rpc.py @@ -20,6 +20,8 @@ import sys import os +import ldb + from samba import NTSTATUSError, credentials from samba.dcerpc import lsa from samba.ntstatus import NT_STATUS_NO_IMPERSONATION_TOKEN @@ -39,13 +41,16 @@ class RpcTests(KDCBaseTest): """ def test_rpc(self): - self._run_rpc_test("rpcusr") + self._run_rpc_test() + + def test_rpc_rename(self): + self._run_rpc_test(rename=True) def test_rpc_no_pac(self): - self._run_rpc_test("rpcusr_nopac", include_pac=False, + self._run_rpc_test(include_pac=False, expect_anon=True, allow_error=True) - def _run_rpc_test(self, user_name, include_pac=True, + def _run_rpc_test(self, rename=False, include_pac=True, expect_anon=False, allow_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the @@ -57,7 +62,10 @@ class RpcTests(KDCBaseTest): service = "cifs" # Create the user account. - (user_credentials, _) = self.create_account(samdb, user_name) + user_credentials = self.get_cached_creds( + account_type=self.AccountType.USER, + use_cache=False) + user_name = user_credentials.get_username() mach_credentials = self.get_dc_creds() @@ -73,6 +81,17 @@ class RpcTests(KDCBaseTest): # Remove the cached credentials file. self.addCleanup(os.remove, cachefile.name) + if rename: + # Rename the account. + + new_name = self.get_new_username() + + msg = ldb.Message(user_credentials.get_dn()) + msg['sAMAccountName'] = ldb.MessageElement(new_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + # Authenticate in-process to the machine account using the user's # cached credentials. diff --git a/python/samba/tests/krb5/test_smb.py b/python/samba/tests/krb5/test_smb.py index 7408e5dbecea..47e9e48c971d 100755 --- a/python/samba/tests/krb5/test_smb.py +++ b/python/samba/tests/krb5/test_smb.py @@ -20,6 +20,8 @@ import sys import os +import ldb + from ldb import SCOPE_SUBTREE from samba import NTSTATUSError from samba.dcerpc import security @@ -43,13 +45,16 @@ class SmbTests(KDCBaseTest): """ def test_smb(self): - self._run_smb_test("smbusr") + self._run_smb_test() + + def test_smb_rename(self): + self._run_smb_test(rename=True) def test_smb_no_pac(self): - self._run_smb_test("smbusr_nopac", include_pac=False, + self._run_smb_test(include_pac=False, expect_error=True) - def _run_smb_test(self, user_name, include_pac=True, + def _run_smb_test(self, rename=False, include_pac=True, expect_error=False): # Create a user account and a machine account, along with a Kerberos # credentials cache file where the service ticket authenticating the @@ -62,7 +67,12 @@ class SmbTests(KDCBaseTest): share = "tmp" # Create the user account. - (user_credentials, _) = self.create_account(samdb, user_name) + user_credentials = self.get_cached_creds( + account_type=self.AccountType.USER, + use_cache=False) + user_name = user_credentials.get_username() + + mach_credentials = self.get_dc_creds() mach_credentials = self.get_dc_creds() @@ -78,6 +88,24 @@ class SmbTests(KDCBaseTest): # Remove the cached credentials file. self.addCleanup(os.remove, cachefile.name) + # Retrieve the user account's SID. + ldb_res = samdb.search(scope=SCOPE_SUBTREE, + expression="(sAMAccountName=%s)" % user_name, + attrs=["objectSid"]) + self.assertEqual(1, len(ldb_res)) + sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) + + if rename: + # Rename the account. + + new_name = self.get_new_username() + + msg = ldb.Message(user_credentials.get_dn()) + msg['sAMAccountName'] = ldb.MessageElement(new_name, + ldb.FLAG_MOD_REPLACE, + 'sAMAccountName') + samdb.modify(msg) + # Set the Kerberos 5 credentials cache environment variable. This is # required because the codepath that gets run (gse_krb5) looks for it # in here and not in the credentials object. @@ -88,13 +116,6 @@ class SmbTests(KDCBaseTest): # Authenticate in-process to the machine account using the user's # cached credentials. - # Retrieve the user account's SID. - ldb_res = samdb.search(scope=SCOPE_SUBTREE, - expression="(sAMAccountName=%s)" % user_name, - attrs=["objectSid"]) - self.assertEqual(1, len(ldb_res)) - sid = ndr_unpack(security.dom_sid, ldb_res[0]["objectSid"][0]) - # Connect to a share and retrieve the user SID. s3_lp = s3param.get_context() s3_lp.load(self.get_lp().configfile) diff --git a/selftest/knownfail_mit_kdc b/selftest/knownfail_mit_kdc index b4e819c83dfb..8cd36fe2d967 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -417,6 +417,7 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_ ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_sid_mismatch_nonexisting ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_authdata_no_pac ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_no_pac +^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rename ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_allowed_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_denied ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_tgs_rodc_no_krbtgt_link -- 2.25.1 From 0a5a01a0a6210454928bf982d1093df7f3e63808 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:53:33 +1300 Subject: [PATCH 219/224] CVE-2020-25718 heimdal:kdc: Add comment about tests for tickets of users not revealed to an RODC BUG: https://bugzilla.samba.org/show_bug.cgi?id=14886 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 933b6c2af042..7eba899966e2 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -250,7 +250,9 @@ ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_a ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_no_client_pac_no_auth_data_required_b # -# KDC TGT tests +# https://bugzilla.samba.org/show_bug.cgi?id=14886: Tests for accounts not revealed to the RODC +# +# The KDC should not accept tickets from an RODC for accounts not in the msDS-RevealedUsers list. # ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_renew_rodc_not_revealed ^samba.tests.krb5.kdc_tgs_tests.samba.tests.krb5.kdc_tgs_tests.KdcTgsTests.test_s4u2self_rodc_not_revealed -- 2.25.1 From 8015dfc2a8e67d126e79b669badb01a1f2bc3bdd Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 2 Nov 2021 14:52:22 +1300 Subject: [PATCH 220/224] Revert "CVE-2020-25719 heimdal:kdc: Require authdata to be present" This reverts an earlier commit that was incorrect. It is not Samba practice to include a revert, but at this point in the patch preperation the ripple though the knownfail files is more trouble than can be justified. It is not correct to refuse to parse all tickets with no authorization data, only for the KDC to require that a PAC is found, which is done in "heimdal:kdc: Require PAC to be present" Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/heimdal/lib/krb5/pac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/heimdal/lib/krb5/pac.c b/source4/heimdal/lib/krb5/pac.c index 749d0fdb4ebb..05bcc5230800 100644 --- a/source4/heimdal/lib/krb5/pac.c +++ b/source4/heimdal/lib/krb5/pac.c @@ -1369,7 +1369,7 @@ _krb5_kdc_pac_ticket_parse(krb5_context context, *ppac = NULL; if (ad == NULL || ad->len == 0) - return KRB5KDC_ERR_BADOPTION; + return 0; for (i = 0; i < ad->len; i++) { AuthorizationData child; -- 2.25.1 From 818ac4fde5ce3b8070eb2ee883e33d96f685ffed Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 2 Nov 2021 14:02:14 +1300 Subject: [PATCH 221/224] CVE-2020-25719 selftest: Always expect a PAC in TGS replies with Heimdal This is tested in other places already, but this ensures a global check that a TGS-REP has a PAC, regardless. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14561 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/selftest/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 35186ad59c5a..f30491cf0953 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -811,7 +811,7 @@ planoldpythontestsuite("ad_dc:local", "samba.tests.dckeytab", extra_args=['-U"$U have_fast_support = int('SAMBA_USES_MITKDC' in config_hash) tkt_sig_support = int('SAMBA4_USES_HEIMDAL' in config_hash) -expect_pac = 0 +expect_pac = int('SAMBA4_USES_HEIMDAL' in config_hash) planoldpythontestsuite("none", "samba.tests.krb5.kcrypto") planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.simple_tests", environ={'SERVICE_USERNAME':'$SERVER', -- 2.25.1 From 8ffb70ec560c67afe8e370837b0ea2bc73ecedb1 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 2 Nov 2021 14:11:27 +0100 Subject: [PATCH 222/224] CVE-2020-25722 pytests: Give computer accounts unique (and valid) sAMAccountNames and SPNs BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Stefan Metzmacher --- python/samba/tests/samba_tool/computer.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/python/samba/tests/samba_tool/computer.py b/python/samba/tests/samba_tool/computer.py index 27815adbe0a8..a1f29993d01a 100644 --- a/python/samba/tests/samba_tool/computer.py +++ b/python/samba/tests/samba_tool/computer.py @@ -39,23 +39,29 @@ class ComputerCmdTestCase(SambaToolCmdTest): # ips used to test --ip-address option self.ipv4 = '10.10.10.10' self.ipv6 = '2001:0db8:0a0b:12f0:0000:0000:0000:0001' + computer_basename = self.randomName().lower() data = [ { - 'name': 'testcomputer1', + 'name': computer_basename + 'cmp1', 'ip_address_list': [self.ipv4] }, { - 'name': 'testcomputer2', + 'name': computer_basename + 'cmp2', 'ip_address_list': [self.ipv6], - 'service_principal_name_list': ['SPN0'] + 'service_principal_name_list': [ + 'host/' + computer_basename + 'SPN20', + ], }, { - 'name': 'testcomputer3$', + 'name': computer_basename + 'cmp3$', 'ip_address_list': [self.ipv4, self.ipv6], - 'service_principal_name_list': ['SPN0', 'SPN1'] + 'service_principal_name_list': [ + 'host/' + computer_basename + 'SPN30', + 'host/' + computer_basename + 'SPN31', + ], }, { - 'name': 'testcomputer4$', + 'name': computer_basename + 'cmp4$', }, ] self.computers = [self._randomComputer(base=item) for item in data] -- 2.25.1 From 51af99ac3bf98bbd503170baa524efdc7453a6b1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 2 Nov 2021 21:21:17 +1300 Subject: [PATCH 223/224] CVE-2020-25722 selftest: Add test for duplicate servicePrincipalNames on an add operation BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Joseph Sutton --- selftest/knownfail.d/spn_uniqueness | 2 ++ source4/dsdb/tests/python/sam.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 selftest/knownfail.d/spn_uniqueness diff --git a/selftest/knownfail.d/spn_uniqueness b/selftest/knownfail.d/spn_uniqueness new file mode 100644 index 000000000000..3f6c2f031912 --- /dev/null +++ b/selftest/knownfail.d/spn_uniqueness @@ -0,0 +1,2 @@ +^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_service_principal_name_uniqueness\(ad_dc_default\) +^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_service_principal_name_uniqueness\(fl2008r2dc\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index cff9b4345f4e..30954a37185b 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -90,6 +90,7 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) delete_force(self.ldb, "cn=ldaptest\,specialuser,cn=users," + self.base_dn) delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + delete_force(self.ldb, "cn=ldaptestcomputer2,cn=computers," + self.base_dn) delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn) delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn) @@ -3501,6 +3502,26 @@ class SamTests(samba.tests.TestCase): delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + def test_service_principal_name_uniqueness(self): + """Test the servicePrincipalName uniqueness behaviour""" + print("Testing servicePrincipalName uniqueness behaviour") + + ldb.add({ + "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, + "objectclass": "computer", + "servicePrincipalName": "HOST/testname.testdom"}) + + try: + ldb.add({ + "dn": "cn=ldaptestcomputer2,cn=computers," + self.base_dn, + "objectclass": "computer", + "servicePrincipalName": "HOST/testname.testdom"}) + except LdbError as e: + num, _ = e.args + self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) + else: + self.fail() + def test_sam_description_attribute(self): """Test SAM description attribute""" print("Test SAM description attribute") -- 2.25.1 From 60fdc7047cd8dfc56faa27bc09fea8ce58f1570d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 2 Nov 2021 21:00:00 +1300 Subject: [PATCH 224/224] CVE-2020-25722 selftest: Ensure check for duplicate servicePrincipalNames is not bypassed for an add operation BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 If one of the objectClass checks passed, samldb_add() could return through one of the samldb_fill_*() functions and skip the servicePrincipalName uniqueness checking. Signed-off-by: Joseph Sutton --- selftest/knownfail.d/spn_uniqueness | 2 -- source4/dsdb/samdb/ldb_modules/samldb.c | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 selftest/knownfail.d/spn_uniqueness diff --git a/selftest/knownfail.d/spn_uniqueness b/selftest/knownfail.d/spn_uniqueness deleted file mode 100644 index 3f6c2f031912..000000000000 --- a/selftest/knownfail.d/spn_uniqueness +++ /dev/null @@ -1,2 +0,0 @@ -^samba4.sam.python\(ad_dc_default\).__main__.SamTests.test_service_principal_name_uniqueness\(ad_dc_default\) -^samba4.sam.python\(fl2008r2dc\).__main__.SamTests.test_service_principal_name_uniqueness\(fl2008r2dc\) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 1ecd44b4b11a..911aea7ec9e3 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4840,6 +4840,18 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) } } + el = ldb_msg_find_element(ac->msg, "servicePrincipalName"); + if ((el != NULL)) { + /* + * We need to check whether the SPN collides with an existing + * one (anywhere) including via aliases. + */ + ret = samldb_spn_uniqueness_check(ac, el); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (samdb_find_attribute(ldb, ac->msg, "objectclass", "user") != NULL) { ac->type = SAMLDB_TYPE_USER; @@ -4938,19 +4950,6 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) return samldb_fill_object(ac); } - - el = ldb_msg_find_element(ac->msg, "servicePrincipalName"); - if ((el != NULL)) { - /* - * We need to check whether the SPN collides with an existing - * one (anywhere) including via aliases. - */ - ret = samldb_spn_uniqueness_check(ac, el); - if (ret != LDB_SUCCESS) { - return ret; - } - } - if (samdb_find_attribute(ldb, ac->msg, "objectclass", "subnet") != NULL) { ret = samldb_verify_subnet(ac, ac->msg->dn); -- 2.25.1