From 35e6c1d7050b28a742190ebddb18c618666d5347 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 10 Aug 2021 22:31:02 +1200 Subject: [PATCH 001/190] 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 --- 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 00000000000..31b9cb23b44 --- /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 00000000000..ec2b13045e5 --- /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 21b1086a70a..21894812422 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1179,6 +1179,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 43f18b20fe6e6bb6c13ef51aa7bb043fc131a6c4 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 12 Aug 2021 11:10:09 +1200 Subject: [PATCH 002/190] 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 --- 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 31b9cb23b44..ab6db192aae 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 7ecf8c43040..47af8c8733c 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -2496,6 +2496,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) { @@ -3673,59 +3726,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 cdb14f498a3829bc49c119d0d32c6abd3727fb41 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 13 Aug 2021 17:42:23 +1200 Subject: [PATCH 003/190] 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 --- 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 ab6db192aae..c3a779010d9 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 e3081cd13dc..a4b4783955c 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 e1f6194cd79c47cee1c652e3dcaffa4e4ab19671 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 20:34:54 +1200 Subject: [PATCH 004/190] 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 --- 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 c3a779010d9..4b85a869089 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 ec2b13045e5..aa35dcc1317 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 3c2fde375f33f44b3347addb7e19fa744afe5b43 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 10:21:03 +1200 Subject: [PATCH 005/190] 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 --- .../user_account_control-uac_mod_lock | 11 ++ .../dsdb/tests/python/user_account_control.py | 169 ++++++++++++++++++ 2 files changed, 180 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 00000000000..a70534506f3 --- /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 4602b3e97d4..30ff259f8b7 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -90,6 +90,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", @@ -843,6 +878,140 @@ 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}") + + 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) + + 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) + + 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 AssertionError(f"{how} was not a valid argument") + + try: + self.admin_samdb.modify(m) + if account_type == UF_NORMAL_ACCOUNT \ + and account_type2 in [UF_SERVER_TRUST_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT]: + account_type2_str \ + = dsdb.user_account_control_flag_bit_to_string(account_type2) + self.fail("Able to change {account_type_str} to {account_type2_str") + if account_type2 == UF_NORMAL_ACCOUNT \ + and account_type in [UF_SERVER_TRUST_ACCOUNT, + UF_WORKSTATION_TRUST_ACCOUNT]: + account_type2_str \ + = dsdb.user_account_control_flag_bit_to_string(account_type2) + self.fail("Able to change {account_type_str} to {account_type2_str") + except LdbError as e: + (enum, estr) = e.args + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + + 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") + try: + self.admin_samdb.modify(m) + self.fail("Able to change objectclass of a {objectclass}") + except LdbError as e: + (enum, estr) = e.args + self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) runner = SubunitTestRunner() rc = 0 -- 2.25.1 From 0cd4dbb299584b7720fc61c7289328c814f08e16 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Sep 2021 21:48:13 +1200 Subject: [PATCH 006/190] CVE-2020-25722 selftest: Move to using self.assertRaisesLdbError() in user_account_control.py This is easier to reason with regaridng which cases should work and which cases should fail, avoiding issues where more failures than expected would be OK. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett --- .../dsdb/tests/python/user_account_control.py | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py index 30ff259f8b7..d6698490af8 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -893,14 +893,15 @@ class UserAccountControlTests(samba.tests.TestCase): print(f"Adding account {name} as {account_type_str} with objectclass {objectclass}") - try: + if (objectclass == "user" \ + and account_type == UF_NORMAL_ACCOUNT): 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) + 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, @@ -919,7 +920,10 @@ class UserAccountControlTests(samba.tests.TestCase): "samAccountName": name, "userAccountControl": str(account_type | UF_PASSWD_NOTREQD)} - account_type_str = dsdb.user_account_control_flag_bit_to_string(account_type) + 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}") @@ -938,23 +942,17 @@ class UserAccountControlTests(samba.tests.TestCase): else: raise AssertionError(f"{how} was not a valid argument") - try: + 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) - if account_type == UF_NORMAL_ACCOUNT \ - and account_type2 in [UF_SERVER_TRUST_ACCOUNT, - UF_WORKSTATION_TRUST_ACCOUNT]: - account_type2_str \ - = dsdb.user_account_control_flag_bit_to_string(account_type2) - self.fail("Able to change {account_type_str} to {account_type2_str") - if account_type2 == UF_NORMAL_ACCOUNT \ - and account_type in [UF_SERVER_TRUST_ACCOUNT, - UF_WORKSTATION_TRUST_ACCOUNT]: - account_type2_str \ - = dsdb.user_account_control_flag_bit_to_string(account_type2) - self.fail("Able to change {account_type_str} to {account_type2_str") - except LdbError as e: - (enum, estr) = e.args - self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + 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, @@ -1006,12 +1004,10 @@ class UserAccountControlTests(samba.tests.TestCase): ldb.FLAG_MOD_DELETE, "objectclass") m["1objectclass"] = ldb.MessageElement(new_objectclass, ldb.FLAG_MOD_ADD, "objectclass") - try: - self.admin_samdb.modify(m) - self.fail("Able to change objectclass of a {objectclass}") - except LdbError as e: - (enum, estr) = e.args - self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) + + 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 460ee8e119370305d0c48978b8f2f343ef56e17d Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 20 Sep 2021 12:35:51 +1200 Subject: [PATCH 007/190] 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 --- 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 a38021b5ded..ce81b736e86 100755 --- a/auth/credentials/tests/bind.py +++ b/auth/credentials/tests/bind.py @@ -92,7 +92,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 @@ -105,13 +106,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 dab919cdcb7be8dff0997780396936c4d5979e82 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 20 Sep 2021 14:54:03 +1200 Subject: [PATCH 008/190] 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 --- .../dsdb/tests/python/password_settings.py | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/source4/dsdb/tests/python/password_settings.py b/source4/dsdb/tests/python/password_settings.py index fcb671690c3..678822f7352 100644 --- a/source4/dsdb/tests/python/password_settings.py +++ b/source4/dsdb/tests/python/password_settings.py @@ -594,19 +594,31 @@ 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: + # 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) + # 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 177ecfedb518837f2c61038b099b95358209d279 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 17 Sep 2021 13:41:40 +1200 Subject: [PATCH 009/190] 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 --- .../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 d6698490af8..2a3dd34c53a 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -305,7 +305,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 @@ -360,7 +364,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 @@ -457,7 +465,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, @@ -578,7 +590,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, @@ -897,7 +913,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 6ae98b9b8b2cae7e527d71a74f28c94aca31d306 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 16 Sep 2021 08:46:42 +1200 Subject: [PATCH 010/190] 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 --- 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 00000000000..a076f9cfedb --- /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 a4b4783955c..da74462ec57 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 a5fe195b647746ee4314aa16c7aac3f310bd75b0 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 15:42:08 +1300 Subject: [PATCH 011/190] 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 --- 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 00000000000..8c70c859fa4 --- /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 a076f9cfedb..bb0787c1a48 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 a70534506f3..00000000000 --- 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 2a3dd34c53a..ecdb05b56e9 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -90,32 +90,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}" @@ -894,10 +905,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, @@ -909,25 +921,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: @@ -948,10 +992,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") @@ -963,17 +1022,36 @@ class UserAccountControlTests(samba.tests.TestCase): else: raise AssertionError(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 392a7d189f198f7f815f0e58754a0bc9c776be8f Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 16:07:46 +1300 Subject: [PATCH 012/190] 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 benifit. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett --- selftest/knownfail.d/priv_attr | 6 - selftest/knownfail.d/uac_objectclass_restrict | 35 ++-- source4/dsdb/samdb/ldb_modules/samldb.c | 153 ++++++++++++++---- 3 files changed, 145 insertions(+), 49 deletions(-) diff --git a/selftest/knownfail.d/priv_attr b/selftest/knownfail.d/priv_attr index 4b85a869089..e0d6104cec9 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_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index bb0787c1a48..040c4eb219d 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 da74462ec57..d7e0a33be96 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 72ca5491059a4c58f43bea0dc02c631b80009a57 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 28 Oct 2021 14:47:30 +1300 Subject: [PATCH 013/190] 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 --- 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 e0d6104cec9..5d3713eafe3 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 3cf8d636038d58331af2a6b556fa7feca9ef49f1 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 16:18:51 +1300 Subject: [PATCH 014/190] 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 --- 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 040c4eb219d..32d8a99f950 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 ecdb05b56e9..bda005e929b 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -178,6 +178,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) @@ -491,17 +508,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=[]) @@ -547,7 +568,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) @@ -555,22 +580,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 @@ -608,7 +630,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"]) @@ -655,7 +677,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"]) @@ -685,7 +707,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("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 72280d4ea850825c27660265278369db2f774bdd Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 22 Sep 2021 11:28:05 +1200 Subject: [PATCH 015/190] 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 --- 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 d7e0a33be96..3eeab82126f 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 e48919468a6cafb199c73bc978af52d5bc96c2e2 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 22 Sep 2021 11:29:02 +1200 Subject: [PATCH 016/190] 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 --- selftest/knownfail.d/uac_dollar_lock | 1 - source4/dsdb/samdb/ldb_modules/samldb.c | 171 +++++++++++++++++++++--- 2 files changed, 154 insertions(+), 18 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 8c70c859fa4..00000000000 --- 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/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 3eeab82126f..e947f788810 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 61be58614ca603064f0ca3129e84452a0325152c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 11:57:22 +1300 Subject: [PATCH 017/190] 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 --- .../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 00000000000..a6351a81907 --- /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 32d8a99f950..d093c631bd3 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 1417505c4f5..5dd091fe475 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2925,6 +2925,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") @@ -2939,7 +2972,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 51f17b1cb17905ffe14bb71d68a26f60c97924b2 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 13:02:42 +1300 Subject: [PATCH 018/190] 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 --- 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 a6351a81907..00000000000 --- 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 e947f788810..140cc22cc53 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 a3e2745e73003b9d4e0b5073c9ee2741d8c0c99e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 14:03:05 +1300 Subject: [PATCH 019/190] 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 --- 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 d093c631bd3..510dbbf96fa 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_trust +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify +^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_add_0_uac +^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 5dd091fe475..425e51cf72c 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -1884,7 +1884,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") @@ -1912,12 +1912,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", @@ -1932,6 +1935,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", @@ -1951,6 +1955,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, @@ -1962,6 +1967,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, @@ -1973,6 +1979,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, @@ -1983,6 +1990,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, @@ -1993,6 +2001,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, @@ -2006,6 +2015,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"}) @@ -2176,6 +2186,7 @@ class SamTests(samba.tests.TestCase): (num, _) = e69.args self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + def test_userAccountControl_user_add_0_uac(self): # With a computer object # Add operation @@ -2200,12 +2211,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", @@ -2220,6 +2233,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", @@ -2239,6 +2253,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, @@ -2250,6 +2265,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", @@ -2262,6 +2278,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, @@ -2272,6 +2289,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, @@ -2283,6 +2301,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 04d4bcf2e632df74849638d07b156f089c701287 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:06:14 +1300 Subject: [PATCH 020/190] 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 --- 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 510dbbf96fa..f697bc2b6e8 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_trust ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_computer_modify ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_add_0_uac ^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_modify diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 425e51cf72c..330885167bf 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2298,7 +2298,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 4cf554b5a8fa7a0ef45575c6be0d3c2eb24122c4 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:14:28 +1300 Subject: [PATCH 021/190] 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 --- 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 f697bc2b6e8..295818d6a1b 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_modify -^samba4.sam.python\(.*\).__main__.SamTests.test_userAccountControl_user_add_0_uac ^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\) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 330885167bf..d7f5ed2c7f9 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2206,7 +2206,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) @@ -2314,7 +2314,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 1229db7cb0a5f441fe2930b87004c0a5e904fd76 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:19:19 +1300 Subject: [PATCH 022/190] 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 --- 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 295818d6a1b..0971c13c2f0 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 d7f5ed2c7f9..dcb3f4f769d 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2134,7 +2134,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) @@ -2153,7 +2153,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"]) @@ -2501,7 +2501,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 51cb0071913248ae4af884de93ea2c9d5398cfa9 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 21 Oct 2021 15:42:46 +1300 Subject: [PATCH 023/190] 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 --- 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 0971c13c2f0..7328ca17d80 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 0c90a0bbd79..ce02d887792 100755 --- a/source4/dsdb/tests/python/ldap.py +++ b/source4/dsdb/tests/python/ldap.py @@ -47,6 +47,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 @@ -2017,9 +2018,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) @@ -2498,9 +2499,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 dcb3f4f769d..ca01e2c87cf 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -290,7 +290,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 cc67b09227130acc1429ee6f8c842f6060c5554e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 22:40:06 +1300 Subject: [PATCH 024/190] 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 --- 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 1b1fd984251..9ed8db7d337 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -20,6 +20,7 @@ import os import tempfile import warnings +import collections import ldb import samba from samba import param @@ -195,23 +196,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 7328ca17d80..ac6f4857bf4 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 bda005e929b..6eb864bb21f 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -553,6 +553,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: @@ -560,6 +563,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 57f8e7967d06918ec3add8650fc767a35eacc47f Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 22:54:52 +1300 Subject: [PATCH 025/190] 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 --- 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 ac6f4857bf4..1d72442f8a8 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 ad3af678708..00000000000 --- 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 6eb864bb21f..4da7c160957 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -443,7 +443,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}", @@ -1053,12 +1054,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 \ @@ -1131,7 +1134,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 8e58fe7db94bb0309eedfc2b70e7aa3048235191 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 22 Oct 2021 23:41:23 +1300 Subject: [PATCH 026/190] 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 --- 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 1d72442f8a8..c4d4507c833 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 4da7c160957..02ade60b5f8 100755 --- a/source4/dsdb/tests/python/user_account_control.py +++ b/source4/dsdb/tests/python/user_account_control.py @@ -392,11 +392,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 @@ -460,7 +459,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)) @@ -640,11 +639,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 @@ -663,14 +660,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) \ @@ -748,7 +747,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)) @@ -767,7 +769,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) @@ -818,9 +820,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 c7fafac40d506e4063e7e464012c35cc41c361e6 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 27 Sep 2021 11:20:19 +1300 Subject: [PATCH 027/190] 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 --- 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 515150ab9cd..ed488dee425 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 a9ae2c4a789..57b28df9e52 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 83d4fc9d7b2454da1ac0a8906c034c210d9e91cf Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 20 Oct 2021 15:48:35 +1300 Subject: [PATCH 028/190] 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 --- 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 302ae865cf1..0e252d90262 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 392c39c202985aaae2b89256af9227c98d0c6257 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 14:59:01 +1300 Subject: [PATCH 029/190] 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 --- 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 8ae9c24b0fc..c129883e7cd 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 cda8221bbe1eb94b90c0fc69d6cf7e9bac2257e4 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:00:38 +1300 Subject: [PATCH 030/190] 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 --- 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 c129883e7cd..813af767dbd 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 6b677a8393ce7cfe2381ca816ebd7913be351985 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 8 Oct 2021 15:40:09 +1300 Subject: [PATCH 031/190] CVE-2020-25722 tests/krb5: Allow creating server accounts BUG: https://bugzilla.samba.org/show_bug.cgi?id=14776 Signed-off-by: Joseph Sutton --- 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 813af767dbd..a0da89041c4 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 25f2815cd3508867509a8b5a9e5b8d4d7a998b1e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 30 Sep 2021 16:53:22 +1300 Subject: [PATCH 032/190] 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 --- 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 fdf078ea788..055209fd09d 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 3301fd2b2dcc52d179b8b4318722b52f591049aa Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 15:02:10 +1300 Subject: [PATCH 033/190] 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 --- 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 a0da89041c4..e85574c51cb 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 42486d5fbad36376b712f121c37589c96be4a9a7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 20 Oct 2021 15:48:20 +1300 Subject: [PATCH 034/190] 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 --- 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 055209fd09d..62e1b9867dd 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 8bf67d1273f81c7d6a27f9629f2b8c4f2d846e2e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 16:46:23 +1300 Subject: [PATCH 035/190] 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 --- 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 62e1b9867dd..8e55790272a 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 ebb068c264a2886a102decc019730abdc544b6a1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 22 Oct 2021 11:37:31 +1300 Subject: [PATCH 036/190] 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 --- 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 e85574c51cb..e77a940f411 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 6a2b78398ac..040ae5cc9a1 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 95b2d24221a..7d9ffebe298 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 40ac6df7a35..ef8dd4dcbf5 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 eebc9a9d4fe..1e70ed322bf 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 9f7155b3d89aacf732d09cb3fe6dfa6e76d80d05 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 22 Oct 2021 11:37:37 +1300 Subject: [PATCH 037/190] 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 --- 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 e77a940f411..aed4c427ab0 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 a1e90086170525e7e04dc47209fc2e8b6d0714f8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 15:45:00 +1300 Subject: [PATCH 038/190] 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 --- 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 aed4c427ab0..cc23484ba2c 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 00000000000..62d2ea081bc --- /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 570391d67ac..a7fb4631d0b 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 5e0ee77ff6a..3cd8cfde948 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -93,3 +93,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 7aa95cbb1c7..1576e7244ac 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -352,3 +352,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 21894812422..f74e87345ee 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1620,6 +1620,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 8970b346a0515d67bb00b4f89235d4a1b7ec45b1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 13 Oct 2021 16:07:09 +1300 Subject: [PATCH 039/190] 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 --- 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 b6c61d7e7a4..ace56d3edf6 100644 --- a/python/samba/tests/blackbox/ndrdump.py +++ b/python/samba/tests/blackbox/ndrdump.py @@ -89,6 +89,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 00000000000..02b570624bc --- /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 00000000000..9747d1b6d3a --- /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 00000000000..cd99b9d0b0a --- /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 00000000000..d29832ede49 --- /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 344022a9677967310c008dc9a7c939fc8c93f003 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:02:39 +1300 Subject: [PATCH 040/190] 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 --- 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 f36704f998c..fbeb5fe61fb 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 3cd8cfde948..86d1733926f 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -99,3 +99,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 1576e7244ac..42a746a5177 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -256,7 +256,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 2be0e2c8c3d64f161f85f510f3b86e0e71554d29 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 14:07:41 +1300 Subject: [PATCH 041/190] CVE-2020-25722 Add test for SPN deletion followed by addition BUG: https://bugzilla.samba.org/show_bug.cgi?id=14876 --- selftest/knownfail.d/acl-spn | 1 + source4/dsdb/tests/python/acl.py | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 55 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 00000000000..e68add920a6 --- /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 2cb6303dce3..dcb4017699d 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -2189,6 +2189,60 @@ 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') + self.ldb_user1.transaction_start() + 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}') + finally: + self.ldb_user1.transaction_cancel() + + # 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') + self.ldb_user1.transaction_start() + 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}') + finally: + self.ldb_user1.transaction_cancel() + + # tests SEC_ADS_LIST vs. SEC_ADS_LIST_OBJECT @DynamicTestCase class AclVisibiltyTests(AclTests): -- 2.25.1 From 6f313677da5ff914bbd85d47ced80c6b2ffcd376 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 14:39:36 +1300 Subject: [PATCH 042/190] 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 --- 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 593ef94c910..a80a7b3427e 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 86d1733926f..76322fc187e 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -81,6 +81,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 44b973a772a1af258e7de18f6db7bb11e9a13854 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 19 Oct 2021 20:02:45 +1300 Subject: [PATCH 043/190] CVE-2020-25719 tests/krb5: Add principal aliasing test BUG: https://bugzilla.samba.org/show_bug.cgi?id=14686 Signed-off-by: Joseph Sutton --- 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 00000000000..60213845a44 --- /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 39bb2db8e32..b643185f767 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 a7fb4631d0b..aed012cf767 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 76322fc187e..8a205f4ac14 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -109,3 +109,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 42a746a5177..16d30bc9685 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -364,3 +364,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 f74e87345ee..41ae1e6219f 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1630,6 +1630,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 fb1985ffa3040d5dbacde99bb8b5d2364c027d8f Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 11:45:23 +1300 Subject: [PATCH 044/190] 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 --- 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 cc23484ba2c..4fe7485c492 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 fbeb5fe61fb..74f1032163e 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 b643185f767..490cd255ec3 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 8a205f4ac14..a4ec57ea12c 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -116,3 +116,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 16d30bc9685..cdf35585aea 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -371,3 +371,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 fed6761ad6691a1cf75b44d2165aef3f0d18554b Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 16:20:07 +1300 Subject: [PATCH 045/190] 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 --- 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 74f1032163e..5de79c30e1b 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 8e55790272a..b5ac393ea67 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 a4ec57ea12c..69c7241b971 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -119,6 +119,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 @@ -130,6 +131,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 @@ -141,6 +143,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 @@ -152,6 +155,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 @@ -169,6 +173,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 cdf35585aea..b5337da996e 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -374,6 +374,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 @@ -386,6 +387,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 @@ -399,6 +401,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 @@ -411,6 +414,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 @@ -426,6 +430,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 9652053c30298043909e6db82ee93d48328643f7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 21 Oct 2021 16:46:56 +1300 Subject: [PATCH 046/190] 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 --- 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 4fe7485c492..9be6cbab30b 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 5de79c30e1b..5313dbc6045 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 b5ac393ea67..18ee8738eaa 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 a80a7b3427e..5005affd6b3 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 69c7241b971..342d69a6a03 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -155,6 +155,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 b5337da996e..ead0902b2d4 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -414,6 +414,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 d13dbff1f256314d13217dc6f4118e1078f8186a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 22 Oct 2021 16:20:36 +0200 Subject: [PATCH 047/190] CVE-2020-25719 CVE-2020-25717: selftest: remove "gensec:require_pac" settings 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 --- selftest/selftest.pl | 2 -- selftest/target/Samba4.pm | 2 -- 2 files changed, 4 deletions(-) diff --git a/selftest/selftest.pl b/selftest/selftest.pl index 9d4462323f5..75763ef3838 100755 --- a/selftest/selftest.pl +++ b/selftest/selftest.pl @@ -586,8 +586,6 @@ sub write_clientconf($$$) client min protocol = CORE log level = $client_loglevel 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 4b302aa19de..aafe183dced 100755 --- a/selftest/target/Samba4.pm +++ b/selftest/target/Samba4.pm @@ -785,8 +785,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 b8362aec700a01cc452026c699203d8a32b94e94 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 24 Aug 2021 17:11:24 +0200 Subject: [PATCH 048/190] 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. 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 --- python/samba/tests/krb5/test_ccache.py | 32 +++++++++--- python/samba/tests/krb5/test_ldap.py | 70 ++++++++++++++++++++++---- python/samba/tests/krb5/test_rpc.py | 46 ++++++++++++++--- python/samba/tests/krb5/test_smb.py | 31 +++++++++--- selftest/knownfail.d/no-pac | 4 ++ source4/selftest/tests.py | 17 ++++--- 6 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 selftest/knownfail.d/no-pac diff --git a/python/samba/tests/krb5/test_ccache.py b/python/samba/tests/krb5/test_ccache.py index 040ae5cc9a1..4b016ed5816 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_NO_IMPERSONATION_TOKEN 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,12 +75,16 @@ 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. lp = self.get_lp() + lp.set('server role', 'active directory domain controller') settings = {} settings["lp_ctx"] = lp @@ -117,7 +129,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, estr = e.args + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, enum) + return + token = session.security_token token_sids = token.sids self.assertGreater(len(token_sids), 0) @@ -125,9 +146,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 7d9ffebe298..0205bdf6fb7 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_NO_IMPERSONATION_TOKEN', 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 ef8dd4dcbf5..e775257552e 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_NO_IMPERSONATION_TOKEN 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, estr = e.args + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, 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 1e70ed322bf..60f9a2da733 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_NO_IMPERSONATION_TOKEN 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, estr = e.args + self.assertEqual(NT_STATUS_NO_IMPERSONATION_TOKEN, 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/selftest/knownfail.d/no-pac b/selftest/knownfail.d/no-pac new file mode 100644 index 00000000000..9723d581c2a --- /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/source4/selftest/tests.py b/source4/selftest/tests.py index 41ae1e6219f..855f90e690b 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -955,14 +955,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 3c9f3e8779c3350a81cda812f09511b014371660 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 17:29:34 +0200 Subject: [PATCH 049/190] 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 --- 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 32d11e1fa57..0be5ae5554b 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 e419736010b..918393c0827 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 ea315aecf6d..dfbaf52d482 100644 --- a/source3/winbindd/winbindd_pam.c +++ b/source3/winbindd/winbindd_pam.c @@ -1866,7 +1866,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; @@ -2435,6 +2435,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", @@ -2649,7 +2656,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; @@ -2722,7 +2729,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; } @@ -2754,7 +2760,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; } @@ -2776,6 +2781,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 dacb6566be6..a6f13806df9 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 90fb4fde8f0..6e3739f68c1 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -2155,6 +2155,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 d80d31df5befa3a69ba235e1dbb87031818aa366 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 17:29:34 +0200 Subject: [PATCH 050/190] 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 --- 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 e54eb7719f5..4c66f2c23cb 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 b831a45252902939b11f367bc06eb9223e23f277 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 051/190] 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 --- 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 8c2a97cf205..911407b16c5 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 6784e56991c..85807e6aeab 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 3b965ecba5a4b37fc0c6560aab1947f9aec2452a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 052/190] 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 --- 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 68cdd70feff..8428ca3fabb 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 3bef6f99b750b25ed475816275d862b3c24de37a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 053/190] 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 --- 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 8df160cefc3..8301aec519c 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 34489dca910a5a0f146c77c2f6ecb26fc7f60c43 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 054/190] 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 --- 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 f887d6814d0..df1a87c8b55 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -1931,7 +1931,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()); @@ -2447,7 +2447,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 41591a8de33..fc0fc19bacb 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 a86bd7b1ad49d4e848f5ad266280a0e7d325b194 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 055/190] 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 --- 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 146320ff453..67a1aa0b2d5 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 177c4fc1966cd963540ac94f1864c6663c266d2f Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 056/190] 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 --- 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 18e1862ebaa..bb5b9dafed4 100644 --- a/source3/rpcclient/cmd_netlogon.c +++ b/source3/rpcclient/cmd_netlogon.c @@ -497,7 +497,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 914018c54dbc1cd0b279d4bf4bbe97427615193f Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 057/190] 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 --- 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 0e9245fc23d..0bd81b25cd4 100644 --- a/source3/auth/auth_generic.c +++ b/source3/auth/auth_generic.c @@ -418,7 +418,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 770e6a33190..ff8dc94d296 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 0663bb79beba81bb9c443fc427468f65bdd740d5 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 26 Oct 2021 17:42:41 +0200 Subject: [PATCH 058/190] 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 --- 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 001238278d7..939aa0ef4aa 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 6da6847b8a1dcd74e92f776185c53b4207b1f7a2 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 28 Sep 2021 10:43:40 +0200 Subject: [PATCH 059/190] 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 --- 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 00000000000..46ae795d730 --- /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 1374040fb29..f70f11df757 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 2eac1ba7c38..95373497695 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -2994,6 +2994,10 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) "server smb3 encryption algorithms", DEFAULT_SMB3_ENCRYPTION_ALGORITHMS); + 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 6c9830563c3..e98d1738bb4 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -976,6 +976,8 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.server_smb3_encryption_algorithms = str_list_make_v3_const(NULL, DEFAULT_SMB3_ENCRYPTION_ALGORITHMS, NULL); + 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 5f0e62fa1e9460bad8824c19a9eeff8354502247 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 5 Oct 2021 12:31:29 +0200 Subject: [PATCH 060/190] 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 --- selftest/target/Samba.pm | 1 + selftest/target/Samba3.pm | 63 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/selftest/target/Samba.pm b/selftest/target/Samba.pm index 10353008b88..69e6dcee591 100644 --- a/selftest/target/Samba.pm +++ b/selftest/target/Samba.pm @@ -610,6 +610,7 @@ sub get_interface($) fipsadmember => 57, offlineadmem => 58, s2kmember => 59, + admemnonsswb => 60, rootdnsforwarder => 64, diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index fdbba8411bc..fb84e4de5b9 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -240,6 +240,7 @@ sub check_env($$) ad_member_fips => ["ad_dc_fips"], ad_member_offlogon => ["ad_dc"], ad_member_oneway => ["fl2000dc"], + ad_member_no_nss_wb => ["ad_dc"], clusteredmember => ["nt4_dc"], ); @@ -653,8 +654,15 @@ sub provision_ad_member $dcvars, $trustvars_f, $trustvars_e, + $extra_member_options, $force_fips_mode, - $offline_logon) = @_; + $offline_logon, + $no_nss_winbind) = @_; + + if (defined($offline_logon) && defined($no_nss_winbind)) { + warn ("Offline logon incompatible with no nss winbind\n"); + return undef; + } my $prefix_abs = abs_path($prefix); my @dirs = (); @@ -696,6 +704,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} @@ -719,6 +731,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 @@ -920,6 +936,11 @@ sub provision_ad_member $ENV{SOCKET_WRAPPER_DIR} = $swrap_env; } else { + 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", @@ -1398,6 +1419,7 @@ sub setup_ad_member_fips $dcvars, $trustvars_f, $trustvars_e, + undef, 1); } @@ -1422,9 +1444,48 @@ sub setup_ad_member_offlogon $trustvars_f, $trustvars_e, undef, + 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, + 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 8060df5a8774d1cbf76b3ee32b66a88239e45865 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 5 Oct 2021 16:56:06 +0200 Subject: [PATCH 061/190] 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 --- .../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 00000000000..77414b239f0 --- /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 aed012cf767..42aa1afc998 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 00000000000..982bb26a439 --- /dev/null +++ b/selftest/knownfail.d/min_domain_uid @@ -0,0 +1 @@ +^samba.tests.krb5.test_smb_no_nss_wb.samba.*.SmbNoNssWbTests.test_min_uid\(ad_member_no_nss_wb:local\) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 855f90e690b..2e9603e8f34 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -972,6 +972,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 cbbdc0410e24de056686837a1dd711a40e53a82c Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 19:57:18 +0200 Subject: [PATCH 062/190] 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 --- 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 0bd81b25cd4..61abbab2a02 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 4079efa67d951148f0662404c0dfaad0eab88a7d Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 28 Sep 2021 10:45:11 +0200 Subject: [PATCH 063/190] 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 --- source3/auth/auth_util.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c index 0a1cf4803e4..9ff7256bbed 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -2117,6 +2117,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 dc704aa9590d31c68ec24bb5ac83ca34be4cbba2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 17:40:30 +0200 Subject: [PATCH 064/190] 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 --- 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 8998f9c8f8a..074e8c7eb71 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 5e5c3b4804eee37d23418ec4798a68e8220f6ddf Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 18:08:20 +0200 Subject: [PATCH 065/190] 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 --- 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 9ff7256bbed..be2f466526f 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -1912,7 +1912,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 a51f2f9f58f437b6c76eef6fb5b1b9ae48b07da1 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 8 Oct 2021 12:33:16 +0200 Subject: [PATCH 066/190] 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 --- 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 00000000000..809612ba0b9 --- /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 be2f466526f..26fa227e9b0 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -1947,7 +1947,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 */ @@ -1966,48 +1966,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 ff24958db064a1a1698a52adebe7939812935e28 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 13:13:52 +0200 Subject: [PATCH 067/190] 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 --- 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 f0e9f699f29..b62ddb302c6 100644 --- a/source3/lib/util_names.c +++ b/source3/lib/util_names.c @@ -69,5 +69,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 b67c2374c78542008e018557a3dcd9b7653ca46e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 18:03:55 +0200 Subject: [PATCH 068/190] 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 --- 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 26fa227e9b0..dec854d85c3 100644 --- a/source3/auth/auth_util.c +++ b/source3/auth/auth_util.c @@ -570,13 +570,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 0265afb17f0854c50336beeb13fcd3f285547818 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 11 Nov 2020 18:50:45 +0200 Subject: [PATCH 069/190] 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 --- 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 9511c61c96d..b8b83a127b5 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 7a6bc770723..a78d1ab9cf3 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 d9301152d94..9fac73ef113 100644 --- a/lib/param/param_table.c +++ b/lib/param/param_table.c @@ -109,6 +109,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 cd8e74b9d8f..9a0fc102de8 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 239503e85b6..59af460dc4e 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 4772c8d7d3f..03ba1915b21 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 ce6bf6c5621..fec19c76dbb 100644 --- a/source3/auth/auth.c +++ b/source3/auth/auth.c @@ -544,6 +544,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; @@ -575,6 +576,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; @@ -596,6 +598,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 e8e0d543f8c..a2ce1013975 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 39a7c0d6911..ba2c76764d1 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 da40a887172..4fe5a3b8eef 100644 --- a/source3/lib/netapi/joindomain.c +++ b/source3/lib/netapi/joindomain.c @@ -378,6 +378,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 e98d1738bb4..e37c7ec8b03 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -4421,6 +4421,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: @@ -4446,7 +4447,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 0e01467b3cb..a551bcfd24a 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 1964eb5a448..f98f0c98674 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 655c587ac40..7bd3f324c37 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 64569382695..932452bc13b 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 b9fb7e855b8..d7f5b4b73c0 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -1930,7 +1930,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 451ad6aee14..db7e1c87dee 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 6e3739f68c1..fe68adec534 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -1322,15 +1322,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 4c66f2c23cb..ea9ff70ce80 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 c695004cafc..ce32d3cb1b3 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 cda887d45ee..29c509522be 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 7a907bd6dbcec6f3fcb5ee3d6f6c19e57348a332 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 18:11:57 +0200 Subject: [PATCH 070/190] 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 --- 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 e411751c3af..1075b9fde87 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 @@ -48,10 +50,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 9723d581c2a..00000000000 --- 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 11d9dd7f14ced502ad21416025cdf1d8a806fee2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 11 Oct 2021 23:17:19 +0200 Subject: [PATCH 071/190] 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 --- 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 3f9fb1ae3cb..6b7db99cbe2 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 ea9ff70ce80..3dd2ffc9276 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 a521bc94bc4..dbbf97665db 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 f2c836b78576c32ebd823f341122c0eacff5200f Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 12:27:28 +0200 Subject: [PATCH 072/190] 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 --- 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 df1a87c8b55..b94a5268ea7 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 37ee0bb6fa82bf16eb4e1a960f84c2033d662012 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Sep 2021 12:44:01 +0200 Subject: [PATCH 073/190] 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 --- 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 b94a5268ea7..ad58d96100d 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 9a0dccfda2634d1ffd8099d40e99bf0b9a7d97bb Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 4 Oct 2021 19:42:20 +0200 Subject: [PATCH 074/190] 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 --- 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 61abbab2a02..9c99d4119ea 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 9c7a7140185a892af663fc4a834047a8f2046896 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 17:14:01 +0200 Subject: [PATCH 075/190] 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 --- 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 809612ba0b9..00000000000 --- 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 fb84e4de5b9..6fd8ab77650 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -1954,7 +1954,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 @@ -1962,6 +1961,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( @@ -1987,12 +1990,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 @@ -2044,6 +2041,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 53a2f18805902e2c3ca870dc7e625963c33b069e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 5 Oct 2021 18:12:49 +0200 Subject: [PATCH 076/190] 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 --- 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 9c99d4119ea..5fb16213d28 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 9bc5cb7e9a5e242cf8df13b5ea6f1f589e57ba95 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 17:59:59 +0200 Subject: [PATCH 077/190] 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 --- 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 5fb16213d28..2ddb9c6bf04 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 036d4004c38..596cbd68a7f 100644 --- a/source3/auth/proto.h +++ b/source3/auth/proto.h @@ -417,7 +417,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 074e8c7eb71..7b69ca6c222 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 1cf0ac642058c56dd66e51d1074f1aa7c9179ca9 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 8 Oct 2021 18:03:04 +0200 Subject: [PATCH 078/190] 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 --- 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 2ddb9c6bf04..fc7a7549e8e 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 596cbd68a7f..9bffce7a808 100644 --- a/source3/auth/proto.h +++ b/source3/auth/proto.h @@ -428,9 +428,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 7b69ca6c222..b8f37cbeee0 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 5f673f5765198e77e6d0d3b9af935dcf460e8c5c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 8 Oct 2021 15:49:31 +1300 Subject: [PATCH 079/190] 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 dcb4017699d..c844eb7359f 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -1646,6 +1646,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)""" @@ -1707,6 +1709,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): @@ -2023,6 +2027,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) @@ -2061,29 +2067,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 @@ -2129,6 +2145,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) @@ -2147,41 +2165,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 2a844e51ea375a2328944ad9e2828b792a2f6470 Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Mon, 25 Oct 2021 14:54:56 +0300 Subject: [PATCH 080/190] 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 --- 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 00000000000..059a1778e65 --- /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 c844eb7359f..b341f1b3332 100755 --- a/source4/dsdb/tests/python/acl.py +++ b/source4/dsdb/tests/python/acl.py @@ -1925,6 +1925,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, @@ -1946,6 +1948,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 @@ -2221,6 +2224,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 f6e13bdd12cc416f1b8bb604868ae865df543d8f Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Mon, 18 Oct 2021 14:27:59 +0300 Subject: [PATCH 081/190] 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 --- 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 059a1778e65..00000000000 --- 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 769e589e265..9b4afa45215 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -1182,6 +1182,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 802969d50d5..723c5ae07ca 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 f917d99517a..08a95c1c310 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 0aa3edac3dc..fa57af49e8f 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 140cc22cc53..edf153d9fb9 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 a19979d63932d4d217c7a6621c94143459021bc5 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Mon, 4 Oct 2021 12:56:42 +1300 Subject: [PATCH 082/190] 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 --- 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 9ed8db7d337..1b43359e3e8 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -61,6 +61,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 904099c527da0ed43e900aabb1dd089a0670e857 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Sun, 24 Oct 2021 15:18:05 +1300 Subject: [PATCH 083/190] 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 --- 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 1b43359e3e8..ab366c7ed30 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -208,6 +208,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 4ed3a31a2a036c9a0cd982993118fed85dc1fe59 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 11 Aug 2021 16:56:07 +1200 Subject: [PATCH 084/190] 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 f298ef3df6f..0aefaa1e58e 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 72f284d48b245e2545d26f7c35397f22149def93 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Tue, 10 Aug 2021 23:02:36 +0000 Subject: [PATCH 085/190] 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 0aefaa1e58e..7313a3247c7 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 bf3aa8755a6a0df46582a372159f1f3eac342654 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 28 Jul 2021 05:38:50 +0000 Subject: [PATCH 086/190] 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 f0069460e3e..46e9c59272a 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 db6ba7ed372d21c5df3630d1bebb4bf54cc6ff08 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 27 Aug 2021 11:36:42 +1200 Subject: [PATCH 087/190] 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 --- 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 46e9c59272a..2676ff34fac 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 429ace9494f..764ded4c88b 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 adb28ff91a4ed5e0e7025ed2c60fc6b8e7e7fa16 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 1 Sep 2021 18:35:02 +1200 Subject: [PATCH 088/190] 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 --- 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 764ded4c88b..8f0258d0db8 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 401956d478d416edcd5516fb9b4a8d487b8e227a Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 28 Oct 2021 09:45:36 +1300 Subject: [PATCH 089/190] 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 --- 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 00000000000..7fd4b4b3763 --- /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 f77ac5710ec..92bf4d9cf8f 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 09627acd26d2870c3ee18634c10362d754037dcc Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 28 Oct 2021 13:07:01 +1300 Subject: [PATCH 090/190] 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 --- 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 7fd4b4b3763..00000000000 --- 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 e53e7031cd2..58f8af7672e 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 0866627c42b..1c558202bc5 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 a5ac4b8bc7f..e9ab0854cff 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 b02aef9f91f..c6251796878 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 8b56ea48faa902f5d2a442113845fa2bc5d94d93 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Mon, 13 Sep 2021 14:15:09 +1200 Subject: [PATCH 091/190] 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 --- 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 00000000000..cc1cce9b6c3 --- /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 00000000000..c4d494968b2 --- /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 2e9603e8f34..5dd61828899 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1181,6 +1181,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 d9d87e090c09c91d23c95a8150e28dd09854f26f Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 6 Aug 2021 12:03:18 +1200 Subject: [PATCH 092/190] CVE-2020-25722 pytest: test setting servicePrincipalName over ldap BUG: https://bugzilla.samba.org/show_bug.cgi?id=14564 Signed-off-by: Douglas Bagnall --- 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 00000000000..8a398ffaa49 --- /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 00000000000..dc768728658 --- /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 5dd61828899..deba427f3b9 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1181,7 +1181,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 89c570006787fbc14b150726de1273c19f947684 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 12 Aug 2021 21:53:16 +1200 Subject: [PATCH 093/190] 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 --- 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 7313a3247c7..4852a0ef9bd 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 9a46d11638c23a52af23964d33669a90afaf7639 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 14:12:25 +1300 Subject: [PATCH 094/190] 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 --- 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 edf153d9fb9..690719ce423 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 4185b800fd821fc43334d5fcb648e5a8e80f323f Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:16:30 +1300 Subject: [PATCH 095/190] 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 --- 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 690719ce423..8f22fb45fd1 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 5d3914eec5247e5208ebae6f72f1d9ba520cfc43 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:17:34 +1300 Subject: [PATCH 096/190] 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 --- 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 c4d494968b2..e53d566816e 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 8f22fb45fd1..af32e942c82 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 abbb478237d27e5af6ee581003250a7315956bea Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 15:27:25 +1300 Subject: [PATCH 097/190] 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 --- 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 e53d566816e..00000000000 --- 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 af32e942c82..b0628df1131 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 19527958663896cecaf4a3d04f55b3e155dfa42f Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 13:14:32 +1300 Subject: [PATCH 098/190] 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 --- selftest/knownfail.d/ldap_spn | 23 - source4/dsdb/samdb/ldb_modules/samldb.c | 584 +++++++++++++++++++++++- 2 files changed, 581 insertions(+), 26 deletions(-) diff --git a/selftest/knownfail.d/ldap_spn b/selftest/knownfail.d/ldap_spn index dc768728658..b7eb6f30e7a 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 b0628df1131..02a74bf8710 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3388,6 +3388,542 @@ 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); + return ldb_error( + ldb, + LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS, + "SPN conflict"); + } + } 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); + talloc_free(tmp_ctx); + return ldb_error(ldb, + LDB_ERR_CONSTRAINT_VIOLATION, + "SPN conflict"); + } + + 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 +4039,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 +4068,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 +4802,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 +5065,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 a29c6625ab2fe9751ae04aac9d2e420fb817b012 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 16:03:18 +1300 Subject: [PATCH 099/190] 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 --- selftest/knownfail.d/ldap_spn | 2 -- source4/dsdb/samdb/ldb_modules/samldb.c | 38 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/selftest/knownfail.d/ldap_spn b/selftest/knownfail.d/ldap_spn index b7eb6f30e7a..63f9fe02ef7 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 02a74bf8710..b56ddf49537 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3869,6 +3869,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, @@ -3884,8 +3915,15 @@ 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) { + talloc_free(tmp_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + ret = check_spn_direct_collision(ldb, tmp_ctx, spn, -- 2.25.1 From b70d1b5be68396df0e1f0129279d60ab3a3f3819 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:09:21 +1300 Subject: [PATCH 100/190] 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 --- 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 0e8b72233f4..b10177775a8 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 f6e298a5b1d416aefb82682a6737991de6dba75b Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:10:44 +1300 Subject: [PATCH 101/190] 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 --- 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 b56ddf49537..f1d176820f9 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 2a85a20f79ece5a7f06ca4ffb6160619d1028f90 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 22 Oct 2021 14:52:49 +1300 Subject: [PATCH 102/190] 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 --- 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 f1d176820f9..b5b72968f0f 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 d3d0177a68ea29bc4380bc133cb70b0b4705feaf Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:12:49 +1300 Subject: [PATCH 103/190] 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 --- 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 b5b72968f0f..98bc69f5489 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 e8e8612f0289f1eef4f14ebfd4a9d56c5a69dad3 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:13:35 +1300 Subject: [PATCH 104/190] 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 --- 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 98bc69f5489..08d1e57df10 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 facb5d7dae28305fd3bf14662f2da44bdee96e10 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:14:05 +1300 Subject: [PATCH 105/190] 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 --- 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 08d1e57df10..819af114e08 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 5f524fab321581ef8daed65efd9181c52bbfcf0e Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:15:00 +1300 Subject: [PATCH 106/190] 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 --- 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 819af114e08..55a12de586c 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 27b52295eb78aa6010b6bf8525757790890a3728 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:15:43 +1300 Subject: [PATCH 107/190] 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 --- 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 55a12de586c..7a60aa3f908 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 3b6b8e861bd45e7bee92594455c74fe1fea6f998 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:16:34 +1300 Subject: [PATCH 108/190] 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 --- 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 7a60aa3f908..06385dfcebe 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 84c59072af8cb4d401f14e120718e14be9e2eb90 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:17:31 +1300 Subject: [PATCH 109/190] 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 --- 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 06385dfcebe..5b9a1d5864c 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 750824c9edae6a7b474e335f41140dc92d21d0ec Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:17:50 +1300 Subject: [PATCH 110/190] 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 --- 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 5b9a1d5864c..6a5eb473990 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 1680010be1c86d63e85f76458ed2ebb5f53e8854 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:18:10 +1300 Subject: [PATCH 111/190] 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 --- 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 6a5eb473990..2b4770fc18f 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4045,10 +4045,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 109f53c7bb2d8160d7c3ff6617c76b2b61508dba Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:18:21 +1300 Subject: [PATCH 112/190] 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 --- 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 2b4770fc18f..620b165abfe 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4300,9 +4300,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 d5d6f5928ff91a69c9cf654f72e416c37a403a45 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 21 Oct 2021 12:52:07 +1300 Subject: [PATCH 113/190] 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 --- 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 620b165abfe..c1c996d4c34 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -4313,6 +4313,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); @@ -4329,11 +4332,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 */ @@ -4353,6 +4352,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 2a984cf38218650510cac4e14b05cca23646b128 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:19:42 +1300 Subject: [PATCH 114/190] 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 --- .../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 47af8c8733c..c285e2d0457 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 bd04e76f03eecd8463dc5f60345ce68c3d637258 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 20 Oct 2021 17:20:54 +1300 Subject: [PATCH 115/190] 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 --- .../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 c285e2d0457..348696a895c 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -2247,23 +2247,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 9cf0560d99c5afe1de09b4752094f7abdf4e6fc7 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 21 Oct 2021 13:49:28 +1300 Subject: [PATCH 116/190] 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 --- 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 b10177775a8..405febf0b3d 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 edc01437a4b4b1f2f6a03015d6fca44f58965c66 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 12:20:49 +1300 Subject: [PATCH 117/190] 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 --- 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 0331d3687d4..545dc93db8e 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 ce02d887792..a50a5f7b8d6 100755 --- a/source4/dsdb/tests/python/ldap.py +++ b/source4/dsdb/tests/python/ldap.py @@ -435,33 +435,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 e4c748d067ac47963c0b79abc0a1fc0ccb59e2cf Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:56:10 +1300 Subject: [PATCH 118/190] tests/krb5: Fix indentation Signed-off-by: Joseph Sutton --- 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 5313dbc6045..480e3d8264d 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 76e3384213c204cac55cfb7f59ac1ae0fcc37d87 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:33:38 +1300 Subject: [PATCH 119/190] krb5pac.idl: Add PAC_ATTRIBUTES_INFO PAC buffer type Signed-off-by: Joseph Sutton --- 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 ed488dee425..11e227026f6 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 86205a8a721edea6d7f3a5424279aa9488d0e3f0 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:33:49 +1300 Subject: [PATCH 120/190] krb5pac.idl: Add PAC_REQUESTER_SID PAC buffer type Signed-off-by: Joseph Sutton --- 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 11e227026f6..bbe4a253e3a 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 88dc9e4ed17f6927a701a8d6b6d776aa01d910e3 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:44:45 +1300 Subject: [PATCH 121/190] tests/krb5: Provide expected parameters for both AS-REQs in get_tgt() Signed-off-by: Joseph Sutton --- 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 9be6cbab30b..6d6dcc21607 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 8851f3c64eba001633825c92cf5c099b4d549fca Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:47:53 +1300 Subject: [PATCH 122/190] tests/krb5: Allow update_pac_checksums=True if the PAC is not present Signed-off-by: Joseph Sutton --- 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 18ee8738eaa..39ca4a69e1c 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 7d4becdfc8abfc66a35135138a292e53b82d421c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:34 +1300 Subject: [PATCH 123/190] tests/krb5: Don't expect a kvno for user-to-user Signed-off-by: Joseph Sutton --- 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 39ca4a69e1c..f39e57c8189 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 1f2ba1e8cf540e79c0866c3c3a1d3802dcc5579e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:46 +1300 Subject: [PATCH 124/190] tests/krb5: Expect 'renew-till' element when renewing a TGT Signed-off-by: Joseph Sutton --- 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 f39e57c8189..79fe9ec4620 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 a22e7ce26611ae562564820fa9bf13a180adf31b Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:05:08 +1300 Subject: [PATCH 125/190] tests/krb5: Return ticket from _tgs_req() Signed-off-by: Joseph Sutton --- 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 480e3d8264d..11bf38766ae 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 273565862fad03b0d6c448cbb257364632c7aede Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:14:45 +1300 Subject: [PATCH 126/190] tests/krb5: Use correct credentials for user-to-user tests Signed-off-by: Joseph Sutton --- 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 11bf38766ae..2787185f04a 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 342d69a6a03..90632f1e4b9 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -161,7 +161,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 ead0902b2d4..97269987d01 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -419,7 +419,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 dee9e9e24d1ced98d97dd5ad2678cc0b3698f9ec Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:15:53 +1300 Subject: [PATCH 127/190] tests/krb5: Adjust PAC tests to prepare for new PAC_ATTRIBUTES_INFO buffer Signed-off-by: Joseph Sutton --- 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 2787185f04a..10a146a5e59 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 90632f1e4b9..1d344298037 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -103,7 +103,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 97269987d01..e6d6af30d7a 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -258,7 +258,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 dc103d98416d12444bbaf445af0e8ee551006365 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:20:51 +1300 Subject: [PATCH 128/190] tests/krb5: Adjust expected error codes for user-to-user tests Signed-off-by: Joseph Sutton --- 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 10a146a5e59..3a60b9cafb9 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 b21b8c7a5983b8655c53ff337f2aab7a0fb3d976 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:08:34 +1300 Subject: [PATCH 129/190] tests/krb5: tests/krb5: Adjust expected error code for S4U2Self no-PAC tests Signed-off-by: Joseph Sutton --- 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 3a60b9cafb9..3fd6fc1a7b1 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 35c41d222a55e63de82b3fbff03daea031548662 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:12:12 +1300 Subject: [PATCH 130/190] tests/krb5: Extend _get_tgt() method to allow more modifications to tickets Signed-off-by: Joseph Sutton --- 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 3fd6fc1a7b1..4cb32c96250 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 4ae8f46efde896a97afcf785f7d32bc0e30eb414 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 10:25:08 +1300 Subject: [PATCH 131/190] tests/krb5: Add _modify_tgt() method for modifying already obtained tickets Signed-off-by: Joseph Sutton --- 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 4cb32c96250..52a347b9ed4 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 6a99bcf0c498d4693bdd59ab7a66abea0f34dba9 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:50:09 +1300 Subject: [PATCH 132/190] tests/krb5: Add testing for PAC_TYPE_ATTRIBUTES_INFO PAC buffer Signed-off-by: Joseph Sutton --- 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 6d6dcc21607..dc1ba629b41 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 79fe9ec4620..d63366318be 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 5a1048ede79b7b30ee1d953a8d424b6e07e4448c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:51:13 +1300 Subject: [PATCH 133/190] tests/krb5: Add testing for PAC_TYPE_REQUESTER_SID PAC buffer Signed-off-by: Joseph Sutton --- 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 dc1ba629b41..61eeb2333f9 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 d63366318be..8779d0f7869 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 e14acc5aa02b0dbdf74ef69a0f9acf26bd110e10 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:47:24 +1300 Subject: [PATCH 134/190] tests/krb5: Add EXPECT_PAC environment variable to expect pac from all TGS tickets Signed-off-by: Joseph Sutton --- 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 8779d0f7869..42f2e94f5aa 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 deba427f3b9..a0de8ab661c 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -914,30 +914,35 @@ for env in ['fileserver_smb1', 'nt4_member', 'clusteredmember', 'ktest', 'nt4_dc 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("ad_dc_default", "samba.tests.dsdb_dns") 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={ @@ -945,7 +950,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={ @@ -953,7 +959,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", @@ -962,7 +969,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={ @@ -970,7 +978,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", @@ -1571,7 +1580,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', @@ -1580,7 +1590,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"]: @@ -1602,7 +1613,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={ @@ -1610,11 +1622,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", @@ -1623,7 +1637,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", @@ -1633,7 +1648,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", @@ -1643,7 +1659,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", @@ -1653,7 +1670,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", @@ -1663,7 +1681,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 e677d5a84cf355108eb045ae37efb7e51b1dd7ac Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 11:18:36 +1300 Subject: [PATCH 135/190] 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. Signed-off-by: Joseph Sutton --- 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 61eeb2333f9..4b4f1486f60 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 7f21dac267e4c4756498a83d7099e2aa8b36895e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:02:08 +1300 Subject: [PATCH 136/190] tests/krb5: Add tests for PAC attributes buffer Signed-off-by: Joseph Sutton --- 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 52a347b9ed4..40291677819 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 1d344298037..c31a738d482 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -187,3 +187,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 e6d6af30d7a..ef05e1d4782 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -445,3 +445,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 6fe497a67c040a13dc1504361f1bc57712367ebb Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:19:44 +1300 Subject: [PATCH 137/190] tests/krb5: Add tests for PAC-REQUEST padata Signed-off-by: Joseph Sutton --- 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 40291677819..53d7dd4effb 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 c31a738d482..7119dbe7a40 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -208,3 +208,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 ef05e1d4782..546316413b9 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -467,3 +467,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 58287f5a2e43ffd057ab877d3faca5168e15369e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:04:25 +1300 Subject: [PATCH 138/190] tests/krb5: Add tests for requester SID PAC buffer Signed-off-by: Joseph Sutton --- 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 53d7dd4effb..2005d71fa81 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 7119dbe7a40..749a8892ab0 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -217,3 +217,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 546316413b9..176c0747b48 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -485,3 +485,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 6191ef18fd7fadbedcf47e105765f836e10649cc Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:06:58 +1300 Subject: [PATCH 139/190] tests/krb5: Add test for user-to-user with no sname Signed-off-by: Joseph Sutton --- 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 2005d71fa81..b0f60c0a8ce 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 749a8892ab0..005d348484e 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -161,6 +161,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 176c0747b48..e77d01fecea 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -419,6 +419,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 5f8936a26a03462dc26c58c8fae1e68f46b35b24 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 21:09:32 +1300 Subject: [PATCH 140/190] tests/krb5: Add tests for mismatched names with user-to-user Signed-off-by: Joseph Sutton --- 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 b0f60c0a8ce..cfe1ad42d61 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 490cd255ec3..5251e291fde 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 005d348484e..a52e6c741fc 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -163,6 +163,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 @@ -175,7 +176,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 e77d01fecea..d74d55fb01c 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -420,6 +420,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 @@ -432,7 +433,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 570001a9c81458f905a5af1c6877b8f1371370d7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 11:00:38 +1300 Subject: [PATCH 141/190] s4/torture: Expect additional PAC buffers Signed-off-by: Joseph Sutton --- 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 a52e6c741fc..f8a5d8bf7da 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -244,3 +244,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 16249799e36..5a1567f1bde 100644 --- a/source4/torture/rpc/remote_pac.c +++ b/source4/torture/rpc/remote_pac.c @@ -308,7 +308,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; } @@ -365,6 +365,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); @@ -1128,7 +1140,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"); @@ -1168,6 +1180,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 e308a224d38b994fb4603fda8412964776a0b17f Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 19:18:20 +1300 Subject: [PATCH 142/190] pytest: Raise an error when adding a dynamic test that would overwrite an existing test Signed-off-by: Joseph Sutton --- 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 ab366c7ed30..6d4993ac255 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -99,7 +99,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 7a2497a9fb8cee9ee3ebe0b94a3fb3a248226a1f Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 12:32:12 +0200 Subject: [PATCH 143/190] mit-samba: Make ks_get_principal() internally public Signed-off-by: Andreas Schneider --- 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 8a29334bcea..d666216b0c1 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 a8c99b025c9..b543600a360 100644 --- a/source4/kdc/mit-kdb/kdb_samba_principals.c +++ b/source4/kdc/mit-kdb/kdb_samba_principals.c @@ -36,10 +36,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 66e6f8f73b62ca8b6eb8fb6123bd16aee177c881 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Wed, 14 Jul 2021 14:51:34 +0200 Subject: [PATCH 144/190] mit-samba: Add ks_free_principal() --- 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 d666216b0c1..e9613e2fc7e 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 b543600a360..3917b9824c6 100644 --- a/source4/kdc/mit-kdb/kdb_samba_principals.c +++ b/source4/kdc/mit-kdb/kdb_samba_principals.c @@ -62,6 +62,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 0ea96e935617b7cf7e96d66773988632c5b0c662 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 11:20:29 +0200 Subject: [PATCH 145/190] mit-samba: If we use client_princ, always lookup the db entry Signed-off-by: Andreas Schneider --- 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 ac9865aac60..7a404f90e5e 100644 --- a/source4/kdc/mit-kdb/kdb_samba_policies.c +++ b/source4/kdc/mit-kdb/kdb_samba_policies.c @@ -309,6 +309,8 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, krb5_data ***auth_indicators, krb5_authdata ***signed_auth_data) { + 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; @@ -325,8 +327,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; } @@ -335,8 +401,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, @@ -349,9 +415,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; } @@ -362,7 +428,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); @@ -396,6 +462,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 5cd7de5e89b5d26611fff641a6b9449eda7501a1 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 13:12:00 +0200 Subject: [PATCH 146/190] mit-samba: Add mit_samba_princ_needs_pac() Signed-off-by: Andreas Schneider --- 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 22f9a54a05b..3f8b0b9fb2b 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -1176,3 +1176,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 ba824557bd5..636c77ec97c 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 a108522825ababa2e7f338e7d9c057569a125121 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 13:58:57 +0200 Subject: [PATCH 147/190] mit-samba: Handle no DB entry in mit_samba_get_pac() Signed-off-by: Andreas Schneider --- 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 3f8b0b9fb2b..e7fe67241ef 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -463,6 +463,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 e450bf8ce5a52d210c44ce09f931f4ff73242e94 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 12 Jul 2021 14:00:19 +0200 Subject: [PATCH 148/190] mit-samba: Rework PAC handling in kdb_samba_db_sign_auth_data() Signed-off-by: Andreas Schneider --- 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 d74d55fb01c..10c41c51fd0 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -258,12 +258,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. # @@ -479,11 +480,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 @@ -493,7 +492,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 7a404f90e5e..f35210669c2 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 @@ -311,11 +311,16 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, { 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; + krbtgt = krbtgt == NULL ? local_krbtgt : krbtgt; krbtgt_key = krbtgt_key == NULL ? local_krbtgt_key : krbtgt_key; @@ -358,8 +363,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); @@ -391,43 +394,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) { @@ -465,8 +530,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 491d34d0c8887a99be1799b4cf42aca7706f8b6e Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:22:52 +0200 Subject: [PATCH 149/190] 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. Signed-off-by: Andreas Schneider --- 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 e7fe67241ef..8651fc2979e 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -512,6 +512,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; @@ -525,6 +526,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 = @@ -536,6 +538,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 bd5b7ae49ce5a46735067e8804bb81b3dab0d5ec Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:25:53 +0200 Subject: [PATCH 150/190] mit_samba: Create the talloc context earlier Signed-off-by: Andreas Schneider --- 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 8651fc2979e..eee364fb617 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -528,6 +528,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, @@ -535,7 +541,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 = @@ -545,21 +552,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 58ade7688323e8548730194f24bd447a3c93a70a Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 6 Aug 2021 12:03:49 +0200 Subject: [PATCH 151/190] s4:kdc: Remove trailing spaces in pac-glue.c 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 688103d8477..4066389e717 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 8fc9ee546b1287084c91a2a2943d11417f4e1225 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:19:45 +0200 Subject: [PATCH 152/190] s4:kdc: Add samba_kdc_validate_pac_blob() Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- source4/kdc/pac-glue.c | 55 ++++++++++++++++++++++++++++++++++++++++++ source4/kdc/pac-glue.h | 5 ++++ 2 files changed, 60 insertions(+) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 4066389e717..1c6117ad6cb 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -918,3 +918,58 @@ NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, talloc_free(tmp_ctx); return nt_status; } + +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 7b51b0389f5..e83446647b3 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 913b6a8f38ed1874ae68e5511ce90c937f1460ba Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:30:40 +1300 Subject: [PATCH 153/190] fix --- source4/kdc/pac-glue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 1c6117ad6cb..8a3ec22190c 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -919,6 +919,7 @@ NTSTATUS samba_kdc_check_client_access(struct samba_kdc_entry *kdc_entry, 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, -- 2.25.1 From 5a1bae820947a89af83996c425cc820de5f8e134 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 9 Aug 2021 17:20:31 +0200 Subject: [PATCH 154/190] s4:kdc: Check if the pac is valid before updating it Signed-off-by: Andreas Schneider Reviewed-by: Andrew Bartlett --- selftest/knownfail_heimdal_kdc | 31 ++++--------------------------- selftest/knownfail_mit_kdc | 10 ++-------- source4/kdc/mit_samba.c | 8 ++++++++ source4/kdc/wdc-samba4.c | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index f8a5d8bf7da..d0333880012 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -109,13 +109,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 @@ -126,10 +119,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 @@ -138,10 +127,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 @@ -150,10 +135,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 @@ -192,10 +173,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 # @@ -230,8 +207,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 @@ -240,8 +217,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 10c41c51fd0..f67f6bfe187 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -385,8 +385,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 @@ -412,8 +410,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 @@ -453,8 +449,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 # @@ -496,8 +490,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 eee364fb617..be894f60aa7 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -538,6 +538,14 @@ 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. + */ + 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 ac9d7d51733..4e60044997c 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -137,6 +137,22 @@ 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. + */ + 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 ade08022287f7a175ac18652aa40a9a0cdf43de1 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:31:38 +1300 Subject: [PATCH 155/190] fix --- source4/kdc/mit_samba.c | 1 + source4/kdc/wdc-samba4.c | 1 + 2 files changed, 2 insertions(+) diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c index be894f60aa7..a0c5015b655 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -541,6 +541,7 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx, /* * 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) { diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c index 4e60044997c..ed6e9fb9b63 100644 --- a/source4/kdc/wdc-samba4.c +++ b/source4/kdc/wdc-samba4.c @@ -145,6 +145,7 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context, /* * 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) { -- 2.25.1 From b79e3902baae1b929555d6a08ea84676ef7061f8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:41:31 +1300 Subject: [PATCH 156/190] s4:kdc: Add KDC support for PAC_ATTRIBUTES_INFO PAC buffer Signed-off-by: Joseph Sutton --- 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 d0333880012..6e9f8ff8b94 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -103,11 +103,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 # @@ -174,27 +172,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 ec0c5ade153..b8fec62333d 100644 --- a/source4/heimdal/kdc/kerberos5.c +++ b/source4/heimdal/kdc/kerberos5.c @@ -914,27 +914,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; } @@ -1758,13 +1761,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 7e9379db64a..301ca92091a 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 43dc89e2bc0..93b973f576b 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 dda258da3d1..c7f2bcb5ed9 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 a0c5015b655..1668d11fc7f 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -460,7 +460,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, @@ -488,6 +489,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); @@ -590,7 +592,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 8a3ec22190c..06019e579eb 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 e83446647b3..1b6264cb2c3 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 ed6e9fb9b63..11d9ff84f04 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 d427a8acfd8366ac3faf9dfa4343ffb029753a54 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 18 Oct 2021 15:07:58 +1300 Subject: [PATCH 157/190] 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 --- 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 6e9f8ff8b94..1c0c41912fa 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -81,7 +81,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 @@ -101,16 +100,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 @@ -118,7 +110,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 @@ -126,7 +117,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 @@ -164,7 +154,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 05bcc523080..749d0fdb4eb 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 9451b54c49868b75a8670cffd720683af882ba3b Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 30 Sep 2021 14:55:06 +1300 Subject: [PATCH 158/190] 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 --- 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 06019e579eb..7d45391dba4 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 1b6264cb2c3..2a7cb68f274 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 70b7361d2440d8bdb7dd8360677494b019dc72d3 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 10:47:29 +1300 Subject: [PATCH 159/190] 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 --- source4/rpc_server/common/sid_helper.c | 40 ++++++++++--------- source4/rpc_server/drsuapi/getncchanges.c | 33 ++++++++++----- source4/rpc_server/netlogon/dcerpc_netlogon.c | 33 ++++++++++----- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 698249391ef..c32f5cd8d6f 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,18 +69,15 @@ 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; + 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], &sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_INTERNAL_DB_CORRUPTION; @@ -88,7 +89,7 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, (*sids)[i++] = additional_sids[j]; } - (*sids)[i] = NULL; + *num_sids = i; return WERR_OK; } @@ -101,7 +102,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,7 +114,7 @@ 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++) { @@ -126,9 +128,9 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx, if (!NT_STATUS_IS_OK(status)) { return WERR_INTERNAL_DB_CORRUPTION; } - (*sids)[i] = sid; + (*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 e458b2a9931..c7d2addd104 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 1f60f9cc79c..b135d114716 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2856,10 +2856,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)); @@ -2874,17 +2874,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; } @@ -2895,19 +2900,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 240cd280249e5126796cd647d6359bd478bd6ba5 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:09:48 +1300 Subject: [PATCH 160/190] 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 --- 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 c7d2addd104..bc30e73e06b 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 b135d114716..cccd215dc0b 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2878,6 +2878,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, @@ -2894,20 +2908,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 19cee32748ed69c11a3e788e213b9a35bc769e5d Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:38:16 +1300 Subject: [PATCH 161/190] 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 --- 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 c32f5cd8d6f..9f0b19b5104 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -134,3 +134,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 bc30e73e06b..3b1d674573f 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 cccd215dc0b..4d8fd0c4b8e 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2858,8 +2858,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)); @@ -2892,38 +2892,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 555e5f349b2239a71ef114269f25055db0976bd7 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 11:55:11 +1300 Subject: [PATCH 162/190] 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 --- 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 9f0b19b5104..0b2584782be 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -137,16 +137,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 3b1d674573f..a9d305fc9a0 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 4d8fd0c4b8e..0b8330b9f21 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2894,6 +2894,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 f2535b001f8662336b8b2264c33ad19d3abd8bf6 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:01:12 +1300 Subject: [PATCH 163/190] 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 --- 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 0b2584782be..91835f21ec3 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -145,6 +145,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 @@ -164,6 +165,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", + 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 a9d305fc9a0..2fbd178cedc 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 0b8330b9f21..e3422d8b19f 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2851,7 +2851,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 60753d2c6e64a55108c86e13e5b8eb1e3a3e88c6 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:23:39 +1300 Subject: [PATCH 164/190] fix --- source4/rpc_server/common/sid_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 91835f21ec3..695f6ce0081 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -172,7 +172,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct 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", + 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; } -- 2.25.1 From 1a84ce097ae51dfd61c0f7ca79e83be0e35e7af1 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:25:30 +1300 Subject: [PATCH 165/190] 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 --- source4/rpc_server/common/sid_helper.c | 46 +++++++++++++++++++ source4/rpc_server/drsuapi/getncchanges.c | 24 ++-------- source4/rpc_server/netlogon/dcerpc_netlogon.c | 30 ++---------- 3 files changed, 52 insertions(+), 48 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index 695f6ce0081..3947016bc26 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -217,3 +217,49 @@ 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; + + /* if the object SID is equal to the user_sid, allow */ + 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 2fbd178cedc..11a6c93d4cd 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 e3422d8b19f..5838eebc6cc 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2862,9 +2862,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)); @@ -2878,30 +2875,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 9040a5df3016baca7ccaeb4b4d636e6f2e9ba071 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 12:29:49 +1300 Subject: [PATCH 166/190] 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 --- 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 11a6c93d4cd..3ec5acb5353 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 5838eebc6cc..904f430079a 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2851,10 +2851,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 d8167384d2313c486f18f0bd56f0d66828865093 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 14:31:00 +1300 Subject: [PATCH 167/190] 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 --- 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 3ec5acb5353..8a5243aba52 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 904f430079a..a70180f34de 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2865,7 +2865,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 61c59fe182c46bf191e94db7b60858c32294ad23 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 15:57:41 +1300 Subject: [PATCH 168/190] 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 --- 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 3947016bc26..f55609cedf7 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -155,12 +155,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", + 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", + ldb_dn_get_linearized(obj_msg->dn), + ldb_dn_get_linearized(rodc_msg->dn)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -171,9 +177,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; } @@ -182,6 +188,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", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -191,6 +200,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", + ldb_dn_get_linearized(rodc_msg->dn), + win_errstr(werr)); TALLOC_FREE(frame); return WERR_DS_DRA_SECRETS_DENIED; } @@ -252,6 +264,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", + 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 aa13627fae73fcdefafb58cb85c075346d1402db Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:24:10 +1300 Subject: [PATCH 169/190] fix --- source4/rpc_server/common/sid_helper.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source4/rpc_server/common/sid_helper.c b/source4/rpc_server/common/sid_helper.c index f55609cedf7..f4b06fc6264 100644 --- a/source4/rpc_server/common/sid_helper.c +++ b/source4/rpc_server/common/sid_helper.c @@ -155,7 +155,7 @@ 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", + 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; @@ -164,7 +164,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct 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", + 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); @@ -188,7 +188,7 @@ 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", + DBG_ERR("Failed to parse msDS-NeverRevealGroup on %s: %s\n", ldb_dn_get_linearized(rodc_msg->dn), win_errstr(werr)); TALLOC_FREE(frame); @@ -200,7 +200,7 @@ 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", + DBG_ERR("Failed to parse msDS-RevealOnDemandGroup on %s: %s\n", ldb_dn_get_linearized(rodc_msg->dn), win_errstr(werr)); TALLOC_FREE(frame); @@ -264,7 +264,7 @@ 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", + 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)); -- 2.25.1 From a3dadb17644d09dd0c637549ecb1e6c319713aeb Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 15:59:28 +1300 Subject: [PATCH 170/190] 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 --- .../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 f4b06fc6264..d54f921c93e 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 0238ea2e2d7..7f9b8fe7874 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 8a5243aba52..28223104c94 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 a70180f34de..0cc26afaeb1 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" #include "lib/param/loadparm.h" diff --git a/source4/rpc_server/wscript_build b/source4/rpc_server/wscript_build index c9c1978f223..8c756721232 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 0c0a9310ed48ee2f09ccd23c7209f89277352900 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 1 Oct 2021 16:14:37 +1300 Subject: [PATCH 171/190] 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 --- source4/auth/sam.c | 5 ++- source4/kdc/pac-glue.c | 95 +++++++++++++++++++++++++++++++++++++++- source4/kdc/pac-glue.h | 13 +++++- source4/kdc/wdc-samba4.c | 40 ++++++++++++++--- 4 files changed, 142 insertions(+), 11 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 39e48c26b52..93b41be3b21 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/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 7d45391dba4..2d74375ba08 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 + */ 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,79 @@ 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", + 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); + + if (rodc_machine_account_dn == NULL) { + DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list", + 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", + 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)", + 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; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb, + 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 2a7cb68f274..89aa8da63c3 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 11d9ff84f04..71507018120 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); -- 2.25.1 From a7e0b1e4e1426656726e2dd320d3833f08d01737 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 13 Oct 2021 12:25:58 +1300 Subject: [PATCH 172/190] fix --- selftest/knownfail_heimdal_kdc | 12 ----- source4/dsdb/common/rodc_helper.c | 46 +++++++++++-------- source4/kdc/mit_samba.c | 6 ++- source4/kdc/pac-glue.c | 19 ++++++-- source4/rpc_server/drsuapi/getncchanges.c | 11 +---- source4/rpc_server/netlogon/dcerpc_netlogon.c | 1 + 6 files changed, 47 insertions(+), 48 deletions(-) diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index 1c0c41912fa..0af75d48a3c 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -103,25 +103,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 @@ -154,11 +145,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/dsdb/common/rodc_helper.c b/source4/dsdb/common/rodc_helper.c index d54f921c93e..9cc6aa18403 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,10 +68,12 @@ 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; @@ -81,14 +82,11 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx, if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_INTERNAL_DB_CORRUPTION; } - (*sids)[i] = sid; + /* 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; } @@ -135,6 +133,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, @@ -206,6 +205,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, @@ -234,6 +239,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) { @@ -243,7 +249,6 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx, struct dom_sid *token_sids; const struct dom_sid *object_sid = NULL; - /* if the object SID is equal to the user_sid, allow */ object_sid = samdb_result_dom_sid(frame, obj_msg, "objectSid"); @@ -261,7 +266,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), @@ -271,6 +276,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 1668d11fc7f..7dc62280dd9 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -461,7 +461,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, @@ -593,7 +594,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 2d74375ba08..d27d3a18e71 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -748,7 +748,7 @@ int samba_krbtgt_is_in_db(struct samba_kdc_entry *p, /* * 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 + * permitted to be ? */ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx, struct samba_kdc_entry *p, @@ -1127,6 +1127,7 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, "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, @@ -1134,9 +1135,10 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, 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", + 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; @@ -1159,7 +1161,7 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, 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", + 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)); @@ -1168,7 +1170,7 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, } 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)", + 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); @@ -1176,7 +1178,16 @@ WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids, 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, diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 28223104c94..c3330a622af 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 0cc26afaeb1..30af978c85b 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -2877,6 +2877,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 370a3ae760dbcaa3c5957a53e8ccdf171fa4c5b5 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 4 Oct 2021 12:43:13 +1300 Subject: [PATCH 173/190] 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 --- 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 0af75d48a3c..c02a7e2064f 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -103,16 +103,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 @@ -145,8 +139,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 9cc6aa18403..29755c0e09e 100644 --- a/source4/dsdb/common/rodc_helper.c +++ b/source4/dsdb/common/rodc_helper.c @@ -178,7 +178,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 d27d3a18e71..9bb966c4406 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 71507018120..c9bf5dd9cf5 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 1ac06b8f4cf8508d663609aed4bdbbbd9eb2fb3f Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 8 Oct 2021 08:29:51 +1300 Subject: [PATCH 174/190] 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 --- source4/heimdal/kdc/krb5tgs.c | 26 +++++++++++----- source4/heimdal/lib/hdb/hdb.h | 2 +- source4/kdc/db-glue.c | 56 +++++++++++++---------------------- source4/kdc/db-glue.h | 5 ++-- source4/kdc/hdb-samba4.c | 43 ++++++++------------------- 5 files changed, 53 insertions(+), 79 deletions(-) diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 301ca92091a..d4a1c78e153 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 6a09ecb6fe1..5ef9d9565f3 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 a560a1cd84b..8670c2e8adb 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; - } - - orig_sid = samdb_result_dom_sid(mem_ctx, skdc_entry->msg, "objectSid"); - target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + TALLOC_CTX *frame = talloc_stackframe(); + + 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 aa630f5d349..cadfac1deb8 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 38ce9807c02..f0939193ad7 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 6e140433326af0f8c874c629dad50eb660ffc23c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:43:40 +1300 Subject: [PATCH 175/190] fix --- source4/kdc/db-glue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c index 8670c2e8adb..8fe4f1ea3e9 100644 --- a/source4/kdc/db-glue.c +++ b/source4/kdc/db-glue.c @@ -2521,7 +2521,7 @@ samba_kdc_check_s4u2self(krb5_context context, struct dom_sid *orig_sid; struct dom_sid *target_sid; TALLOC_CTX *frame = talloc_stackframe(); - + orig_sid = samdb_result_dom_sid(frame, skdc_entry_client->msg, "objectSid"); -- 2.25.1 From 8edd3aad51bb77c0146c4eb76170956fc30ced90 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 27 Sep 2021 12:10:02 +1300 Subject: [PATCH 176/190] 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 --- source4/kdc/pac-glue.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 9bb966c4406..bd374e2b62d 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 83e68495a42b871361eff3fa4c115dd4910817cb Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:45:45 +1300 Subject: [PATCH 177/190] fix --- python/samba/tests/krb5/s4u_tests.py | 2 -- selftest/knownfail_heimdal_kdc | 10 ---------- selftest/knownfail_mit_kdc | 4 ---- 3 files changed, 16 deletions(-) diff --git a/python/samba/tests/krb5/s4u_tests.py b/python/samba/tests/krb5/s4u_tests.py index 5005affd6b3..a80a7b3427e 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 c02a7e2064f..43edebd782c 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -83,12 +83,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 @@ -108,10 +102,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 f67f6bfe187..7a15d9059c9 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -410,10 +410,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 -- 2.25.1 From b2d8fd8491967ddddfa960d981c6dd4089d8d842 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 20 Oct 2021 11:36:58 +1300 Subject: [PATCH 178/190] 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. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14753 Signed-off-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/objectclass.c | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c index 17cb34f4bef..3175e99afca 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 e031abf485fab25e089e7a2a9061e4f7e8e0ccf7 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Oct 2021 11:52:29 +1300 Subject: [PATCH 179/190] fix --- selftest/knownfail.d/ldap | 1 - selftest/knownfail.d/modify-order | 2 +- selftest/knownfail.d/uac_objectclass_restrict | 4 ---- source4/dsdb/samdb/ldb_modules/objectclass.c | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/selftest/knownfail.d/ldap b/selftest/knownfail.d/ldap index 545dc93db8e..0331d3687d4 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 e14cd1eb356..76d538e1f9c 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_objectclass_restrict b/selftest/knownfail.d/uac_objectclass_restrict index c4d4507c833..a9ed5e888cc 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 3175e99afca..5b97c9dec02 100644 --- a/source4/dsdb/samdb/ldb_modules/objectclass.c +++ b/source4/dsdb/samdb/ldb_modules/objectclass.c @@ -976,7 +976,7 @@ static int objectclass_do_mod(struct oc_context *ac) 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 ddc7a43eb5a6b899658886e92d1a16b0e4c37a75 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:42:41 +1300 Subject: [PATCH 180/190] s4:kdc: Add KDC support for PAC_REQUESTER_SID PAC buffer Signed-off-by: Joseph Sutton --- 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 43edebd782c..d396ea54a6d 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -139,60 +139,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 7dc62280dd9..4239332f0d9 100644 --- a/source4/kdc/mit_samba.c +++ b/source4/kdc/mit_samba.c @@ -461,7 +461,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); @@ -491,6 +491,7 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx, upn_dns_info_blob, NULL, NULL, + NULL, pac); talloc_free(tmp_ctx); @@ -595,6 +596,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 bd374e2b62d..0bab12d0da0 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 89aa8da63c3..266e000f9cd 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 c9bf5dd9cf5..ecd182702c3 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 8ac47ca44cfe4f9d5dfc2bccf5602dce3d94d0bc Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 13:53:25 +1300 Subject: [PATCH 181/190] heimdal:kdc: Check return code Signed-off-by: Joseph Sutton --- 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 d4a1c78e153..5cc45826cbe 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 6e036e8e46ad08d9b57917ac215a940a64ac31da Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 26 Oct 2021 20:34:44 +1300 Subject: [PATCH 182/190] heimdal:kdc: Move fetching krbtgt entry to before enctype selection This allows us to use it when validating user-to-user. Signed-off-by: Joseph Sutton --- 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 5cc45826cbe..9cad3ac7a76 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 9bad0e6a4d08886b156eec0862a0b60d842c69fa Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 13:50:03 +1300 Subject: [PATCH 183/190] 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 --- 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 d396ea54a6d..43224fbbb81 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -103,39 +103,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 9cad3ac7a76..d6ca1fe601c 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 465936b2c039dbdaaacd4b5ea5f4dd4384a05ebc Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 15:51:58 +1300 Subject: [PATCH 184/190] 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 --- 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 43224fbbb81..a0a4af4aa07 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -114,8 +114,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 d6ca1fe601c..f59f99f369f 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 433ba067e871e984cd0b7624f993de61895b48bc Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 27 Oct 2021 15:52:06 +1300 Subject: [PATCH 185/190] 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 --- 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 a0a4af4aa07..e59316c59cd 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -102,17 +102,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 f59f99f369f..ed1fd420a3a 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 a72a8a226b8b9308a3891d868a4345cef089383c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 4 Oct 2021 15:18:34 +1300 Subject: [PATCH 186/190] 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 --- 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 e59316c59cd..e26d6b10ec1 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -88,12 +88,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 7a15d9059c9..22cd0b50ad7 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -359,12 +359,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 8fe4f1ea3e9..aff74f2ee71 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 335ea5a257f88e9190fd2900d001e4414951cb4d Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 14:35:52 +1300 Subject: [PATCH 187/190] 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 --- 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 e26d6b10ec1..38c383450a7 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -90,12 +90,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 ed1fd420a3a..fb2ef8230c9 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 1ad93b1ba517dd0c48d20f9c036e1293517facaf Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:43:28 +1300 Subject: [PATCH 188/190] tests/krb5: Only fetch RODC account credentials when necessary Signed-off-by: Joseph Sutton --- 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 4b4f1486f60..f64bd0b206e 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 3c3ce8eabac0f7875d77022c32d9424997c8c84e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:07:07 +1300 Subject: [PATCH 189/190] tests/krb5: Add tests for using a ticket with a renamed account Signed-off-by: Joseph Sutton --- 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 cfe1ad42d61..abac5a47a56 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 4b016ed5816..9f340a0159d 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 0205bdf6fb7..c1375730e6f 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 e775257552e..6ab3d52aa56 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 60f9a2da733..286ecbaa5fe 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 22cd0b50ad7..cc7b501c6bf 100644 --- a/selftest/knownfail_mit_kdc +++ b/selftest/knownfail_mit_kdc @@ -395,6 +395,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 f3dc4f8a9d31e8523f82ce288be26a64430147a5 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 29 Oct 2021 15:53:33 +1300 Subject: [PATCH 190/190] 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 --- 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 38c383450a7..0f62627ae31 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -88,7 +88,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