1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# This tests the restrictions on userAccountControl that apply even if write access is permitted 4# 5# Copyright Samuel Cabrero 2014 <samuelcabrero@kernevil.me> 6# Copyright Andrew Bartlett 2014 <abartlet@samba.org> 7# 8# Licenced under the GPLv3 9# 10 11from __future__ import print_function 12import optparse 13import sys 14import unittest 15import samba 16import samba.getopt as options 17import samba.tests 18import ldb 19import base64 20 21sys.path.insert(0, "bin/python") 22from samba.tests.subunitrun import TestProgram, SubunitOptions 23 24from samba.subunit.run import SubunitTestRunner 25from samba.auth import system_session 26from samba.samdb import SamDB 27from samba.dcerpc import samr, security, lsa 28from samba.credentials import Credentials 29from samba.ndr import ndr_unpack, ndr_pack 30from samba.tests import delete_force 31from samba import gensec, sd_utils 32from samba.credentials import DONT_USE_KERBEROS 33from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError 34from ldb import Message, MessageElement, Dn 35from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE 36from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, \ 37 UF_LOCKOUT, UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,\ 38 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, UF_INTERDOMAIN_TRUST_ACCOUNT, \ 39 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, \ 40 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, \ 41 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \ 42 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \ 43 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS 44 45 46parser = optparse.OptionParser("user_account_control.py [options] <host>") 47sambaopts = options.SambaOptions(parser) 48parser.add_option_group(sambaopts) 49parser.add_option_group(options.VersionOptions(parser)) 50 51# use command line creds if available 52credopts = options.CredentialsOptions(parser) 53parser.add_option_group(credopts) 54opts, args = parser.parse_args() 55 56if len(args) < 1: 57 parser.print_usage() 58 sys.exit(1) 59host = args[0] 60 61if "://" not in host: 62 ldaphost = "ldap://%s" % host 63else: 64 ldaphost = host 65 start = host.rindex("://") 66 host = host.lstrip(start + 3) 67 68lp = sambaopts.get_loadparm() 69creds = credopts.get_credentials(lp) 70creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) 71 72bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, 73 UF_LOCKOUT, UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, 74 UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, 75 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, 76 UF_INTERDOMAIN_TRUST_ACCOUNT, 77 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, 78 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, 79 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, 80 UF_DONT_REQUIRE_PREAUTH, 81 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, 82 UF_NO_AUTH_DATA_REQUIRED, 83 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS, 84 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)] 85 86account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT]) 87 88 89class UserAccountControlTests(samba.tests.TestCase): 90 def add_computer_ldap(self, computername, others=None, samdb=None): 91 if samdb is None: 92 samdb = self.samdb 93 dn = "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn) 94 domainname = ldb.Dn(self.samdb, self.samdb.domain_dn()).canonical_str().replace("/", "") 95 samaccountname = "%s$" % computername 96 dnshostname = "%s.%s" % (computername, domainname) 97 msg_dict = { 98 "dn": dn, 99 "objectclass": "computer"} 100 if others is not None: 101 msg_dict = dict(list(msg_dict.items()) + list(others.items())) 102 103 msg = ldb.Message.from_dict(self.samdb, msg_dict) 104 msg["sAMAccountName"] = samaccountname 105 106 print("Adding computer account %s" % computername) 107 samdb.add(msg) 108 109 def get_creds(self, target_username, target_password): 110 creds_tmp = Credentials() 111 creds_tmp.set_username(target_username) 112 creds_tmp.set_password(target_password) 113 creds_tmp.set_domain(creds.get_domain()) 114 creds_tmp.set_realm(creds.get_realm()) 115 creds_tmp.set_workstation(creds.get_workstation()) 116 creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() 117 | gensec.FEATURE_SEAL) 118 creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop 119 return creds_tmp 120 121 def setUp(self): 122 super(UserAccountControlTests, self).setUp() 123 self.admin_creds = creds 124 self.admin_samdb = SamDB(url=ldaphost, 125 session_info=system_session(), 126 credentials=self.admin_creds, lp=lp) 127 self.domain_sid = security.dom_sid(self.admin_samdb.get_domain_sid()) 128 self.base_dn = self.admin_samdb.domain_dn() 129 130 self.unpriv_user = "testuser1" 131 self.unpriv_user_pw = "samba123@" 132 self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw) 133 134 delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) 135 delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn)) 136 delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn)) 137 138 self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw) 139 res = self.admin_samdb.search("CN=%s,CN=Users,%s" % (self.unpriv_user, self.admin_samdb.domain_dn()), 140 scope=SCOPE_BASE, 141 attrs=["objectSid"]) 142 self.assertEqual(1, len(res)) 143 144 self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]) 145 self.unpriv_user_dn = res[0].dn 146 147 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp) 148 149 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, self.unpriv_creds) 150 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) 151 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) 152 153 self.sd_utils = sd_utils.SDUtils(self.admin_samdb) 154 155 self.admin_samdb.create_ou("OU=test_computer_ou1," + self.base_dn) 156 self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 157 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid) 158 159 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 160 161 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 162 163 self.add_computer_ldap("testcomputer-t") 164 165 self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd) 166 167 self.computernames = ["testcomputer-0"] 168 169 # Get the SD of the template account, then force it to match 170 # what we expect for SeMachineAccountPrivilege accounts, so we 171 # can confirm we created the accounts correctly 172 self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) 173 174 self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) 175 for ace in self.sd_reference_modify.dacl.aces: 176 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED and ace.trustee == self.unpriv_user_sid: 177 ace.access_mask = ace.access_mask | security.SEC_ADS_SELF_WRITE | security.SEC_ADS_WRITE_PROP 178 179 # Now reconnect without domain admin rights 180 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp) 181 182 def tearDown(self): 183 super(UserAccountControlTests, self).tearDown() 184 for computername in self.computernames: 185 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)) 186 delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn)) 187 delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn)) 188 delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn)) 189 190 def test_add_computer_sd_cc(self): 191 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 192 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 193 194 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 195 196 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 197 198 computername = self.computernames[0] 199 sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)), 200 ldb.FLAG_MOD_ADD, 201 "nTSecurityDescriptor") 202 self.add_computer_ldap(computername, 203 others={"nTSecurityDescriptor": sd}) 204 205 res = self.admin_samdb.search("%s" % self.base_dn, 206 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 207 scope=SCOPE_SUBTREE, 208 attrs=["ntSecurityDescriptor"]) 209 210 desc = res[0]["nTSecurityDescriptor"][0] 211 desc = ndr_unpack(security.descriptor, desc, allow_remaining=True) 212 213 sddl = desc.as_sddl(self.domain_sid) 214 self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl) 215 216 m = ldb.Message() 217 m.dn = res[0].dn 218 m["description"] = ldb.MessageElement( 219 ("A description"), ldb.FLAG_MOD_REPLACE, 220 "description") 221 self.samdb.modify(m) 222 223 m = ldb.Message() 224 m.dn = res[0].dn 225 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT), 226 ldb.FLAG_MOD_REPLACE, "userAccountControl") 227 try: 228 self.samdb.modify(m) 229 self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn) 230 except LdbError as e5: 231 (enum, estr) = e5.args 232 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 233 234 m = ldb.Message() 235 m.dn = res[0].dn 236 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT | 237 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT), 238 ldb.FLAG_MOD_REPLACE, "userAccountControl") 239 try: 240 self.samdb.modify(m) 241 self.fail("Unexpectedly able to set userAccountControl to be an RODC on %s" % m.dn) 242 except LdbError as e6: 243 (enum, estr) = e6.args 244 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 245 246 m = ldb.Message() 247 m.dn = res[0].dn 248 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT), 249 ldb.FLAG_MOD_REPLACE, "userAccountControl") 250 try: 251 self.samdb.modify(m) 252 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn) 253 except LdbError as e7: 254 (enum, estr) = e7.args 255 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 256 257 m = ldb.Message() 258 m.dn = res[0].dn 259 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), 260 ldb.FLAG_MOD_REPLACE, "userAccountControl") 261 self.samdb.modify(m) 262 263 m = ldb.Message() 264 m.dn = res[0].dn 265 m["primaryGroupID"] = ldb.MessageElement(str(security.DOMAIN_RID_ADMINS), 266 ldb.FLAG_MOD_REPLACE, "primaryGroupID") 267 try: 268 self.samdb.modify(m) 269 except LdbError as e8: 270 (enum, estr) = e8.args 271 self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum) 272 return 273 self.fail() 274 275 def test_mod_computer_cc(self): 276 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 277 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 278 279 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 280 281 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 282 283 computername = self.computernames[0] 284 self.add_computer_ldap(computername) 285 286 res = self.admin_samdb.search("%s" % self.base_dn, 287 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 288 scope=SCOPE_SUBTREE, 289 attrs=[]) 290 291 m = ldb.Message() 292 m.dn = res[0].dn 293 m["description"] = ldb.MessageElement( 294 ("A description"), ldb.FLAG_MOD_REPLACE, 295 "description") 296 self.samdb.modify(m) 297 298 m = ldb.Message() 299 m.dn = res[0].dn 300 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT | 301 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT), 302 ldb.FLAG_MOD_REPLACE, "userAccountControl") 303 try: 304 self.samdb.modify(m) 305 self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn) 306 except LdbError as e9: 307 (enum, estr) = e9.args 308 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 309 310 m = ldb.Message() 311 m.dn = res[0].dn 312 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT), 313 ldb.FLAG_MOD_REPLACE, "userAccountControl") 314 try: 315 self.samdb.modify(m) 316 self.fail() 317 except LdbError as e10: 318 (enum, estr) = e10.args 319 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 320 321 m = ldb.Message() 322 m.dn = res[0].dn 323 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT), 324 ldb.FLAG_MOD_REPLACE, "userAccountControl") 325 self.samdb.modify(m) 326 327 m = ldb.Message() 328 m.dn = res[0].dn 329 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT), 330 ldb.FLAG_MOD_REPLACE, "userAccountControl") 331 try: 332 self.samdb.modify(m) 333 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn) 334 except LdbError as e11: 335 (enum, estr) = e11.args 336 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 337 338 def test_admin_mod_uac(self): 339 computername = self.computernames[0] 340 self.add_computer_ldap(computername, samdb=self.admin_samdb) 341 342 res = self.admin_samdb.search("%s" % self.base_dn, 343 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 344 scope=SCOPE_SUBTREE, 345 attrs=["userAccountControl"]) 346 347 self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_NORMAL_ACCOUNT | 348 UF_ACCOUNTDISABLE | 349 UF_PASSWD_NOTREQD)) 350 351 m = ldb.Message() 352 m.dn = res[0].dn 353 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT | 354 UF_PARTIAL_SECRETS_ACCOUNT | 355 UF_TRUSTED_FOR_DELEGATION), 356 ldb.FLAG_MOD_REPLACE, "userAccountControl") 357 try: 358 self.admin_samdb.modify(m) 359 self.fail("Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s" % m.dn) 360 except LdbError as e12: 361 (enum, estr) = e12.args 362 self.assertEqual(ldb.ERR_OTHER, enum) 363 364 m = ldb.Message() 365 m.dn = res[0].dn 366 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT | 367 UF_PARTIAL_SECRETS_ACCOUNT), 368 ldb.FLAG_MOD_REPLACE, "userAccountControl") 369 self.admin_samdb.modify(m) 370 371 res = self.admin_samdb.search("%s" % self.base_dn, 372 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 373 scope=SCOPE_SUBTREE, 374 attrs=["userAccountControl"]) 375 376 self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_WORKSTATION_TRUST_ACCOUNT | 377 UF_PARTIAL_SECRETS_ACCOUNT)) 378 m = ldb.Message() 379 m.dn = res[0].dn 380 m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE), 381 ldb.FLAG_MOD_REPLACE, "userAccountControl") 382 self.admin_samdb.modify(m) 383 384 res = self.admin_samdb.search("%s" % self.base_dn, 385 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 386 scope=SCOPE_SUBTREE, 387 attrs=["userAccountControl"]) 388 389 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE) 390 391 def test_uac_bits_set(self): 392 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 393 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 394 395 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 396 397 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 398 399 computername = self.computernames[0] 400 self.add_computer_ldap(computername) 401 402 res = self.admin_samdb.search("%s" % self.base_dn, 403 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 404 scope=SCOPE_SUBTREE, 405 attrs=[]) 406 407 m = ldb.Message() 408 m.dn = res[0].dn 409 m["description"] = ldb.MessageElement( 410 ("A description"), ldb.FLAG_MOD_REPLACE, 411 "description") 412 self.samdb.modify(m) 413 414 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test 415 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, 416 UF_DONT_EXPIRE_PASSWD]) 417 418 # These bits really are privileged, or can't be changed from UF_NORMAL as a non-admin 419 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, 420 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, 421 UF_WORKSTATION_TRUST_ACCOUNT]) 422 423 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) 424 425 for bit in bits: 426 m = ldb.Message() 427 m.dn = res[0].dn 428 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD), 429 ldb.FLAG_MOD_REPLACE, "userAccountControl") 430 try: 431 self.samdb.modify(m) 432 if (bit in priv_bits): 433 self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn)) 434 except LdbError as e: 435 (enum, estr) = e.args 436 if bit in invalid_bits: 437 self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn)) 438 # No point going on, try the next bit 439 continue 440 elif (bit in priv_bits): 441 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum) 442 else: 443 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) 444 445 def uac_bits_unrelated_modify_helper(self, account_type): 446 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 447 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 448 449 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 450 451 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 452 453 computername = self.computernames[0] 454 self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]}) 455 456 res = self.admin_samdb.search("%s" % self.base_dn, 457 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 458 scope=SCOPE_SUBTREE, 459 attrs=["userAccountControl"]) 460 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type) 461 462 m = ldb.Message() 463 m.dn = res[0].dn 464 m["description"] = ldb.MessageElement( 465 ("A description"), ldb.FLAG_MOD_REPLACE, 466 "description") 467 self.samdb.modify(m) 468 469 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) 470 471 # UF_LOCKOUT isn't actually ignored, it changes other 472 # attributes but does not stick here. See MS-SAMR 2.2.1.13 473 # UF_FLAG Codes clarification that UF_SCRIPT and 474 # UF_PASSWD_CANT_CHANGE are simply ignored by both clients and 475 # servers. Other bits are ignored as they are undefined, or 476 # are not set into the attribute (instead triggering other 477 # events). 478 ignored_bits = set([UF_SCRIPT, UF_00000004, UF_LOCKOUT, UF_PASSWD_CANT_CHANGE, 479 UF_00000400, UF_00004000, UF_00008000, UF_PASSWORD_EXPIRED, 480 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)]) 481 super_priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT]) 482 483 priv_to_remove_bits = set([UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_WORKSTATION_TRUST_ACCOUNT]) 484 485 for bit in bits: 486 # Reset this to the initial position, just to be sure 487 m = ldb.Message() 488 m.dn = res[0].dn 489 m["userAccountControl"] = ldb.MessageElement(str(account_type), 490 ldb.FLAG_MOD_REPLACE, "userAccountControl") 491 self.admin_samdb.modify(m) 492 493 res = self.admin_samdb.search("%s" % self.base_dn, 494 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 495 scope=SCOPE_SUBTREE, 496 attrs=["userAccountControl"]) 497 498 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type) 499 500 m = ldb.Message() 501 m.dn = res[0].dn 502 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD), 503 ldb.FLAG_MOD_REPLACE, "userAccountControl") 504 try: 505 self.admin_samdb.modify(m) 506 if bit in invalid_bits: 507 self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn)) 508 509 except LdbError as e1: 510 (enum, estr) = e1.args 511 if bit in invalid_bits: 512 self.assertEqual(enum, ldb.ERR_OTHER) 513 # No point going on, try the next bit 514 continue 515 elif bit in super_priv_bits: 516 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) 517 # No point going on, try the next bit 518 continue 519 else: 520 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) 521 522 res = self.admin_samdb.search("%s" % self.base_dn, 523 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 524 scope=SCOPE_SUBTREE, 525 attrs=["userAccountControl"]) 526 527 if bit in ignored_bits: 528 self.assertEqual(int(res[0]["userAccountControl"][0]), 529 UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD, 530 "Bit 0x%08x shouldn't stick" % bit) 531 else: 532 if bit in account_types: 533 self.assertEqual(int(res[0]["userAccountControl"][0]), 534 bit | UF_PASSWD_NOTREQD, 535 "Bit 0x%08x didn't stick" % bit) 536 else: 537 self.assertEqual(int(res[0]["userAccountControl"][0]), 538 bit | UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD, 539 "Bit 0x%08x didn't stick" % bit) 540 541 try: 542 m = ldb.Message() 543 m.dn = res[0].dn 544 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE), 545 ldb.FLAG_MOD_REPLACE, "userAccountControl") 546 self.samdb.modify(m) 547 548 except LdbError as e2: 549 (enum, estr) = e2.args 550 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) 551 552 res = self.admin_samdb.search("%s" % self.base_dn, 553 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 554 scope=SCOPE_SUBTREE, 555 attrs=["userAccountControl"]) 556 557 if bit in account_types: 558 self.assertEqual(int(res[0]["userAccountControl"][0]), 559 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 560 "bit 0X%08x should have been added (0X%08x vs 0X%08x)" 561 % (bit, int(res[0]["userAccountControl"][0]), 562 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)) 563 elif bit in ignored_bits: 564 self.assertEqual(int(res[0]["userAccountControl"][0]), 565 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 566 "bit 0X%08x should have been added (0X%08x vs 0X%08x)" 567 % (bit, int(res[0]["userAccountControl"][0]), 568 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)) 569 570 else: 571 self.assertEqual(int(res[0]["userAccountControl"][0]), 572 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 573 "bit 0X%08x should have been added (0X%08x vs 0X%08x)" 574 % (bit, int(res[0]["userAccountControl"][0]), 575 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)) 576 577 try: 578 m = ldb.Message() 579 m.dn = res[0].dn 580 m["userAccountControl"] = ldb.MessageElement(str(UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE), 581 ldb.FLAG_MOD_REPLACE, "userAccountControl") 582 self.samdb.modify(m) 583 if bit in priv_to_remove_bits: 584 self.fail("Should have been unable to remove userAccountControl bit 0x%08X on %s" % (bit, m.dn)) 585 586 except LdbError as e3: 587 (enum, estr) = e3.args 588 if bit in priv_to_remove_bits: 589 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) 590 else: 591 self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr)) 592 593 res = self.admin_samdb.search("%s" % self.base_dn, 594 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 595 scope=SCOPE_SUBTREE, 596 attrs=["userAccountControl"]) 597 598 if bit in priv_to_remove_bits: 599 if bit in account_types: 600 self.assertEqual(int(res[0]["userAccountControl"][0]), 601 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 602 "bit 0X%08x should not have been removed" % bit) 603 else: 604 self.assertEqual(int(res[0]["userAccountControl"][0]), 605 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 606 "bit 0X%08x should not have been removed" % bit) 607 else: 608 self.assertEqual(int(res[0]["userAccountControl"][0]), 609 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD, 610 "bit 0X%08x should have been removed" % bit) 611 612 def test_uac_bits_unrelated_modify_normal(self): 613 self.uac_bits_unrelated_modify_helper(UF_NORMAL_ACCOUNT) 614 615 def test_uac_bits_unrelated_modify_workstation(self): 616 self.uac_bits_unrelated_modify_helper(UF_WORKSTATION_TRUST_ACCOUNT) 617 618 def test_uac_bits_add(self): 619 computername = self.computernames[0] 620 621 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 622 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 623 624 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 625 626 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 627 628 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT]) 629 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test 630 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, 631 UF_DONT_EXPIRE_PASSWD]) 632 633 # These bits really are privileged 634 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, 635 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION]) 636 637 for bit in bits: 638 try: 639 self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]}) 640 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)) 641 if bit in priv_bits: 642 self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername)) 643 644 except LdbError as e4: 645 (enum, estr) = e4.args 646 if bit in invalid_bits: 647 self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername)) 648 # No point going on, try the next bit 649 continue 650 elif bit in priv_bits: 651 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS) 652 continue 653 else: 654 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr)) 655 656 def test_primarygroupID_cc_add(self): 657 computername = self.computernames[0] 658 659 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn) 660 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid) 661 662 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn) 663 664 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod) 665 try: 666 # When creating a new object, you can not ever set the primaryGroupID 667 self.add_computer_ldap(computername, others={"primaryGroupID": [str(security.DOMAIN_RID_ADMINS)]}) 668 self.fail("Unexpectedly able to set primaryGruopID to be an admin on %s" % computername) 669 except LdbError as e13: 670 (enum, estr) = e13.args 671 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) 672 673 def test_primarygroupID_priv_DC_modify(self): 674 computername = self.computernames[0] 675 676 self.add_computer_ldap(computername, 677 others={"userAccountControl": [str(UF_SERVER_TRUST_ACCOUNT)]}, 678 samdb=self.admin_samdb) 679 res = self.admin_samdb.search("%s" % self.base_dn, 680 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 681 scope=SCOPE_SUBTREE, 682 attrs=[""]) 683 684 m = ldb.Message() 685 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid), 686 security.DOMAIN_RID_USERS)) 687 m["member"] = ldb.MessageElement( 688 [str(res[0].dn)], ldb.FLAG_MOD_ADD, 689 "member") 690 self.admin_samdb.modify(m) 691 692 m = ldb.Message() 693 m.dn = res[0].dn 694 m["primaryGroupID"] = ldb.MessageElement( 695 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE, 696 "primaryGroupID") 697 try: 698 self.admin_samdb.modify(m) 699 700 # When creating a new object, you can not ever set the primaryGroupID 701 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername) 702 except LdbError as e14: 703 (enum, estr) = e14.args 704 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) 705 706 def test_primarygroupID_priv_member_modify(self): 707 computername = self.computernames[0] 708 709 self.add_computer_ldap(computername, 710 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)]}, 711 samdb=self.admin_samdb) 712 res = self.admin_samdb.search("%s" % self.base_dn, 713 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 714 scope=SCOPE_SUBTREE, 715 attrs=[""]) 716 717 m = ldb.Message() 718 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid), 719 security.DOMAIN_RID_USERS)) 720 m["member"] = ldb.MessageElement( 721 [str(res[0].dn)], ldb.FLAG_MOD_ADD, 722 "member") 723 self.admin_samdb.modify(m) 724 725 m = ldb.Message() 726 m.dn = res[0].dn 727 m["primaryGroupID"] = ldb.MessageElement( 728 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE, 729 "primaryGroupID") 730 try: 731 self.admin_samdb.modify(m) 732 733 # When creating a new object, you can not ever set the primaryGroupID 734 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername) 735 except LdbError as e15: 736 (enum, estr) = e15.args 737 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM) 738 739 def test_primarygroupID_priv_user_modify(self): 740 computername = self.computernames[0] 741 742 self.add_computer_ldap(computername, 743 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT)]}, 744 samdb=self.admin_samdb) 745 res = self.admin_samdb.search("%s" % self.base_dn, 746 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername, 747 scope=SCOPE_SUBTREE, 748 attrs=[""]) 749 750 m = ldb.Message() 751 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid), 752 security.DOMAIN_RID_ADMINS)) 753 m["member"] = ldb.MessageElement( 754 [str(res[0].dn)], ldb.FLAG_MOD_ADD, 755 "member") 756 self.admin_samdb.modify(m) 757 758 m = ldb.Message() 759 m.dn = res[0].dn 760 m["primaryGroupID"] = ldb.MessageElement( 761 [str(security.DOMAIN_RID_ADMINS)], ldb.FLAG_MOD_REPLACE, 762 "primaryGroupID") 763 self.admin_samdb.modify(m) 764 765 766runner = SubunitTestRunner() 767rc = 0 768if not runner.run(unittest.makeSuite(UserAccountControlTests)).wasSuccessful(): 769 rc = 1 770sys.exit(rc) 771