Codebase list pywerview / b4f0ca8
Import upstream version 0.3.3 Kali Janitor 2 years ago
10 changed file(s) with 663 addition(s) and 349 deletion(s). Raw diff Collapse all Expand all
2424 def get_adobject(domain_controller, domain, user, password=str(),
2525 lmhash=str(), nthash=str(), queried_domain=str(), queried_sid=str(),
2626 queried_name=str(), queried_sam_account_name=str(), ads_path=str(),
27 custom_filter=str()):
27 attributes=list(), custom_filter=str()):
2828 requester = NetRequester(domain_controller, domain, user, password,
2929 lmhash, nthash)
3030 return requester.get_adobject(queried_domain=queried_domain,
3131 queried_sid=queried_sid, queried_name=queried_name,
3232 queried_sam_account_name=queried_sam_account_name,
33 ads_path=ads_path, custom_filter=custom_filter)
33 ads_path=ads_path, attributes=attributes, custom_filter=custom_filter)
34
35 def get_objectacl(domain_controller, domain, user, password=str(),
36 lmhash=str(), nthash=str(), queried_domain=str(), queried_sid=str(),
37 queried_name=str(), queried_sam_account_name=str(), ads_path=str(),
38 sacl=False, rights_filter=str(), resolve_sids=False,
39 resolve_guids=False, custom_filter=str()):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash)
42 return requester.get_objectacl(queried_domain=queried_domain,
43 queried_sid=queried_sid, queried_name=queried_name,
44 queried_sam_account_name=queried_sam_account_name,
45 ads_path=ads_path, sacl=sacl, rights_filter=rights_filter,
46 resolve_sids=resolve_sids, resolve_guids=resolve_guids,
47 custom_filter=custom_filter)
3448
3549 def get_netuser(domain_controller, domain, user, password=str(), lmhash=str(),
3650 nthash=str(), queried_username=str(), queried_domain=str(), ads_path=str(),
193207 requester = GPORequester(domain_controller, domain, user, password,
194208 lmhash, nthash)
195209 return requester.get_netgpo(queried_gponame=queried_gponame,
210 queried_displayname=queried_displayname,
211 queried_domain=queried_domain, ads_path=ads_path)
212
213 def get_netpso(domain_controller, domain, user, password=str(),
214 lmhash=str(), nthash=str(), queried_psoname='*',
215 queried_displayname=str(), queried_domain=str(), ads_path=str()):
216 requester = GPORequester(domain_controller, domain, user, password,
217 lmhash, nthash)
218 return requester.get_netpso(queried_psoname=queried_psoname,
196219 queried_displayname=queried_displayname,
197220 queried_domain=queried_domain, ads_path=ads_path)
198221
9090 help='Domain to query')
9191 get_adobject_parser.add_argument('-a', '--ads-path',
9292 help='Additional ADS path')
93 get_adobject_parser.add_argument('--attributes', nargs='+', dest='attributes',
94 default=[], help='Object attributes to return')
9395 get_adobject_parser.set_defaults(func=get_adobject)
96
97 # Parser for the get-objectacl command
98 get_objectacl_parser = subparsers.add_parser('get-objectacl', help='Takes a domain SID, '\
99 'samAccountName or name, and return the ACL of the associated object', parents=[ad_parser])
100 get_objectacl_parser.add_argument('--sid', dest='queried_sid',
101 help='SID to query (wildcards accepted)')
102 get_objectacl_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
103 help='samAccountName to query (wildcards accepted)')
104 get_objectacl_parser.add_argument('--name', dest='queried_name',
105 help='Name to query (wildcards accepted)')
106 get_objectacl_parser.add_argument('-d', '--domain', dest='queried_domain',
107 help='Domain to query')
108 get_objectacl_parser.add_argument('-a', '--ads-path',
109 help='Additional ADS path')
110 get_objectacl_parser.add_argument('--sacl', action='store_true',
111 help='Return the SACL instead of the DACL for the object (requires '\
112 'a privileged account)')
113 get_objectacl_parser.add_argument('--rights-filter', dest='rights_filter',
114 choices=['reset-password', 'write-members', 'all'], help='A specific set of rights to return '\
115 '(reset-password, write-members, all)')
116 get_objectacl_parser.add_argument('--resolve-sids', dest='resolve_sids',
117 action='store_true', help='Resolve SIDs when querying an ACL')
118 get_objectacl_parser.add_argument('--resolve-guids', action='store_true',
119 help='Resolve GUIDs to their display names')
120 get_objectacl_parser.set_defaults(func=get_objectacl)
94121
95122 # Parser for the get-netuser command
96123 get_netuser_parser = subparsers.add_parser('get-netuser', help='Queries information about '\
252279 get_netgpo_parser.add_argument('-a', '--ads-path',
253280 help='Additional ADS path')
254281 get_netgpo_parser.set_defaults(func=get_netgpo)
282
283 # Parser for the get-netpso command
284 get_netpso_parser = subparsers.add_parser('get-netpso', help='Get a list of all current '\
285 'PSOs in the domain', parents=[ad_parser])
286 get_netpso_parser.add_argument('--psoname', dest='queried_psoname',
287 default='*', help='pso name to query for (wildcards accepted)')
288 get_netpso_parser.add_argument('--displayname', dest='queried_displayname',
289 help='Display name to query for (wildcards accepted)')
290 get_netpso_parser.add_argument('-d', '--domain', dest='queried_domain',
291 help='Domain to query')
292 get_netpso_parser.add_argument('-a', '--ads-path',
293 help='Additional ADS path')
294 get_netpso_parser.set_defaults(func=get_netpso)
255295
256296 # Parser for the get-domainpolicy command
257297 get_domainpolicy_parser = subparsers.add_parser('get-domainpolicy', help='Returns the default domain or DC '\
468508 if results is not None:
469509 try:
470510 for x in results:
471 print(x)
511 print(x, '\n')
472512 # for example, invoke_checklocaladminaccess returns a bool
473513 except TypeError:
474514 print(results)
0 # This file is part of PywerView.
1
2 # PywerView is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
6
7 # PywerView is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11
12 # You should have received a copy of the GNU General Public License
13 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
14
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 __uac_flags = {0x0000001: 'SCRIPT',
18 0x0000002: 'ACCOUNTDISABLE',
19 0x0000008: 'HOMEDIR_REQUIRED',
20 0x0000010: 'LOCKOUT',
21 0x0000020: 'PASSWD_NOTREQD',
22 0x0000040: 'PASSWD_CANT_CHANGE',
23 0x0000080: 'ENCRYPTED_TEXT_PWD_ALLOWED',
24 0x0000100: 'TEMP_DUPLICATE_ACCOUNT',
25 0x0000200: 'NORMAL_ACCOUNT',
26 0x0000800: 'INTERDOMAIN_TRUST_ACCOUNT',
27 0x0001000: 'WORKSTATION_TRUST_ACCOUNT',
28 0x0002000: 'SERVER_TRUST_ACCOUNT',
29 0x0010000: 'DONT_EXPIRE_PASSWORD',
30 0x0020000: 'MNS_LOGON_ACCOUNT',
31 0x0040000: 'SMARTCARD_REQUIRED',
32 0x0080000: 'TRUSTED_FOR_DELEGATION',
33 0x0100000: 'NOT_DELEGATED',
34 0x0200000: 'USE_DES_KEY_ONLY',
35 0x0400000: 'DONT_REQ_PREAUTH',
36 0x0800000: 'PASSWORD_EXPIRED',
37 0x1000000: 'TRUSTED_TO_AUTH_FOR_DELEGATION',
38 0x4000000: 'PARTIAL_SECRETS_ACCOUNT'}
39
40 __ace_flags = {0x1: 'object_inherit', 0x2: 'container_inherit',
41 0x4: 'non_propagate_inherit', 0x8: 'inherit_only',
42 0x10: 'inherited_ace', 0x20: 'audit_successful_accesses',
43 0x40: 'audit_failed_access'}
44
45 __object_ace_flags = {0x1: 'object_ace_type_present', 0x2: 'inherited_object_ace_type_present'}
46
47 # Resources: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/990fb975-ab31-4bc1-8b75-5da132cd4584
48 __access_mask = {0x1: 'create_child', 0x2: 'delete_child',
49 0x4: 'list_children', 0x08: 'self',
50 0x10: 'read_property', 0x20: 'write_property',
51 0x40: 'delete_tree', 0x80: 'list_object',
52 0x100: 'extended_right', 0x10000: 'delete',
53 0x20000: 'read_control', 0x40000: 'write_dacl',
54 0x80000: 'write_owner'}
55
56 __access_mask_generic = {0xf01ff: 'generic_all', 0x20094: 'generic_read',
57 0x20028: 'generic_write', 0x20004: 'generic_execute'}
58
59 __trust_attrib = {0x1: 'non_transitive', 0x2: 'uplevel_only',
60 0x4: 'filter_sids', 0x8: 'forest_transitive',
61 0x10: 'cross_organization', 0x20: 'within_forest',
62 0x40: 'treat_as_external',
63 0x80: 'trust_uses_rc4_encryption',
64 0x100: 'trust_uses_aes_keys',
65 0X200: 'cross_organization_no_tgt_delegation',
66 0x400: 'pim_trust'}
67
68 __trust_direction = {0: 'disabled', 1: 'inbound',
69 2: 'outbound', 3: 'bidirectional'}
70
71 __trust_type = {1: 'windows_non_active_directory',
72 2: 'windows_active_directory', 3: 'mit'}
73
74 def __format_flag(raw_value, flag_dict):
75 try:
76 int_value = int(raw_value)
77 except ValueError:
78 return raw_value
79
80 parsed_flags = list()
81 for flag, flag_label in flag_dict.items():
82 if (int_value & flag) == flag:
83 parsed_flags.append(flag_label)
84 return parsed_flags
85
86 def __format_dict_lookup(raw_value, dictionary):
87 try:
88 return dictionary[int(raw_value)]
89 except (ValueError, KeyError):
90 return raw_value
91
92 def format_useraccountcontrol(raw_value):
93 return __format_flag(raw_value, __uac_flags)
94
95 def format_ace_access_mask(raw_value):
96 try:
97 int_value = int(raw_value)
98 except ValueError:
99 return raw_value
100
101 activedirectoryrights = list()
102 for flag, flag_label in __access_mask_generic.items():
103 if (int_value & flag) == flag:
104 activedirectoryrights.append(flag_label)
105 int_value ^= flag
106 activedirectoryrights += __format_flag(raw_value, __access_mask)
107
108 return activedirectoryrights
109
110 def format_ace_flags(raw_value):
111 return __format_flag(raw_value, __ace_flags)
112
113 def format_object_ace_flags(raw_value):
114 return __format_flag(raw_value, __object_ace_flags)
115
116 def format_trustdirection(raw_value):
117 return __format_dict_lookup(raw_value, __trust_direction)
118
119 def format_trusttype(raw_value):
120 return __format_dict_lookup(raw_value, __trust_type)
121
122 def format_trustattributes(raw_value):
123 return __format_flag(raw_value, __trust_attrib)
124
4141
4242 return self._ldap_search(gpo_search_filter, GPO)
4343
44 @LDAPRequester._ldap_connection_init
45 def get_netpso(self, queried_psoname='*', queried_displayname=str(),
46 queried_domain=str(), ads_path=str()):
47
48 pso_search_filter = '(objectClass=msDS-PasswordSettings)'
49
50 if queried_displayname:
51 pso_search_filter += '(displayname={})'.format(queried_displayname)
52 else:
53 pso_search_filter += '(name={})'.format(queried_psoname)
54
55 pso_search_filter = '(&{})'.format(pso_search_filter)
56
57 return self._ldap_search(pso_search_filter, PSO)
58
4459 def get_gpttmpl(self, gpttmpl_path):
4560 content_io = BytesIO()
4661
5772 smb_connection.connectTree(share)
5873 smb_connection.getFile(share, file_name, content_io.write)
5974 try:
60 content = codecs.decode(content_io.getvalue(), 'utf-16le')[1:].replace('\r', '')
75 content = content_io.getvalue().decode('utf-16le')[1:].replace('\r', '')
6176 except UnicodeDecodeError:
62 content = str(content_io.getvalue()).replace('\r', '')
77 content = content_io.getvalue().decode('utf-8').replace('\r', '')
6378
6479 gpttmpl_final = GptTmpl(list())
6580 for l in content.split('\n'):
6681 if l.startswith('['):
6782 section_name = l.strip('[]').replace(' ', '').lower()
68 setattr(gpttmpl_final, section_name, Policy(list()))
83 gpttmpl_final._attributes_dict[section_name] = Policy(list())
6984 elif '=' in l:
7085 property_name, property_values = [x.strip() for x in l.split('=')]
7186 if ',' in property_values:
7287 property_values = property_values.split(',')
73 try:
74 setattr(getattr(gpttmpl_final, section_name), property_name, property_values)
75 except UnicodeEncodeError:
76 property_name = property_name.encode('utf-8')
77 setattr(getattr(gpttmpl_final, section_name), property_name, property_values)
88 gpttmpl_final._attributes_dict[section_name]._attributes_dict[property_name] = property_values
7889
7990 return gpttmpl_final
8091
104115 members = inspect.getmembers(privilege_rights_policy, lambda x: not(inspect.isroutine(x)))
105116 with NetRequester(self._domain_controller, self._domain, self._user,
106117 self._password, self._lmhash, self._nthash) as net_requester:
107 for member in members:
108 if member[0].startswith('_'):
109 continue
110 if not isinstance(member[1], list):
111 sids = [member[1]]
118 for attr in privilege_rights_policy._attributes_dict:
119 attribute = privilege_rights_policy._attributes_dict[attr]
120 if not isinstance(attribute, list):
121 sids = [attribute]
112122 else:
113 sids = member[1]
123 sids = attribute
114124 resolved_sids = list()
115125 for sid in sids:
116126 if not sid:
117127 continue
128 sid = sid.replace('*', '')
118129 try:
119 resolved_sid = net_requester.get_adobject(queried_sid=sid, queried_domain=queried_domain)[0]
130 resolved_sid = net_requester.get_adobject(queried_sid=sid, queried_domain=self._queried_domain)[0]
120131 except IndexError:
121132 resolved_sid = sid
122133 else:
123134 resolved_sid = resolved_sid.distinguishedname.split(',')[:2]
124 resolved_sid = '{}\\{}'.format(resolved_sid[1], resolved_sid[0])
135 resolved_sid = resolved_sid[1] + '\\' + resolved_sid[0]
125136 resolved_sid = resolved_sid.replace('CN=', '')
126137 resolved_sids.append(resolved_sid)
127138 if len(resolved_sids) == 1:
128139 resolved_sids = resolved_sids[0]
129 setattr(privilege_rights_policy, member[0], resolved_sids)
140 privilege_rights_policy._attributes_dict[attr] = resolved_sids
130141
131142 gpttmpl.privilegerights = privilege_rights_policy
132143
155166 return list()
156167
157168 content = content_io.getvalue().replace(b'\r', b'')
158 groupsxml_soup = BeautifulSoup(content, 'xml')
159
169 groupsxml_soup = BeautifulSoup(content.decode('utf-8'), 'xml')
160170 for group in groupsxml_soup.find_all('Group'):
161171 members = list()
162172 memberof = list()
173
174 raw_xml_member = group.Properties.find_all('Member')
175 if not raw_xml_member:
176 continue
177
163178 local_sid = group.Properties.get('groupSid', str())
179
164180 if not local_sid:
165181 if 'administrators' in group.Properties['groupName'].lower():
166182 local_sid = 'S-1-5-32-544'
170186 local_sid = group.Properties['groupName']
171187 memberof.append(local_sid)
172188
173 for member in group.Properties.find_all('Member'):
189 for member in raw_xml_member:
174190 if not member['action'].lower() == 'add':
175191 continue
176192 if member['sid']:
184200 # have the barest support for filters, so ¯\_(ツ)_/¯
185201
186202 gpo_group = GPOGroup(list())
187 setattr(gpo_group, 'gpodisplayname', gpo_display_name)
188 setattr(gpo_group, 'gponame', gpo_name)
189 setattr(gpo_group, 'gpopath', groupsxml_path)
190 setattr(gpo_group, 'members', members)
191 setattr(gpo_group, 'memberof', memberof)
203 gpo_group._attributes_dict['gpodisplayname'] = gpo_display_name
204 gpo_group._attributes_dict['gponame'] = gpo_name
205 gpo_group._attributes_dict['gpopath'] = groupsxml_path
206 gpo_group._attributes_dict['members'] = members
207 gpo_group._attributes_dict['memberof'] = memberof
192208
193209 gpo_groups.append(gpo_group)
194210
195211 return gpo_groups
196212
197213 def _get_groupsgpttmpl(self, gpttmpl_path, gpo_display_name):
198 import inspect
199214 gpo_groups = list()
200215
201216 gpt_tmpl = self.get_gpttmpl(gpttmpl_path)
206221 except AttributeError:
207222 return list()
208223
209 membership = inspect.getmembers(group_membership, lambda x: not(inspect.isroutine(x)))
210 for m in membership:
211 if not m[1]:
224 membership = group_membership._attributes_dict
225
226 for ma,mv in membership.items():
227 if not mv:
212228 continue
213229 members = list()
214230 memberof = list()
215 if m[0].lower().endswith('__memberof'):
216 members.append(m[0].upper().lstrip('*').replace('__MEMBEROF', ''))
217 if not isinstance(m[1], list):
218 memberof_list = [m[1]]
219 else:
220 memberof_list = m[1]
231 if ma.lower().endswith('__memberof'):
232 members.append(ma.upper().lstrip('*').replace('__MEMBEROF', ''))
233 if not isinstance(mv, list):
234 memberof_list = [mv]
235 else:
236 memberof_list = mv
221237 memberof += [x.lstrip('*') for x in memberof_list]
222 elif m[0].lower().endswith('__members'):
223 memberof.append(m[0].upper().lstrip('*').replace('__MEMBERS', ''))
224 if not isinstance(m[1], list):
225 members_list = [m[1]]
226 else:
227 members_list = m[1]
238 elif ma.lower().endswith('__members'):
239 memberof.append(ma.upper().lstrip('*').replace('__MEMBERS', ''))
240 if not isinstance(mv, list):
241 members_list = [mv]
242 else:
243 members_list = mv
228244 members += [x.lstrip('*') for x in members_list]
229245
230246 if members and memberof:
231247 gpo_group = GPOGroup(list())
232 setattr(gpo_group, 'gpodisplayname', gpo_display_name)
233 setattr(gpo_group, 'gponame', gpo_name)
234 setattr(gpo_group, 'gpopath', gpttmpl_path)
235 setattr(gpo_group, 'members', members)
236 setattr(gpo_group, 'memberof', memberof)
248 gpo_group.add_attributes({'gpodisplayname' : gpo_display_name})
249 gpo_group.add_attributes({'gponame' : gpo_name})
250 gpo_group.add_attributes({'gpopath' : gpttmpl_path})
251 gpo_group.add_attributes({'members' : members})
252 gpo_group.add_attributes({'memberof' : memberof})
237253
238254 gpo_groups.append(gpo_group)
239255
271287 self._password, self._lmhash, self._nthash) as net_requester:
272288 for member in members:
273289 try:
274 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=queried_domain)[0]
275 resolved_member = resolved_member.distinguishedname.split(',')
276 resolved_member_domain = '.'.join(resolved_member[1:])
277 resolved_member = '{}\\{}'.format(resolved_member_domain, resolved_member[0])
278 resolved_member = resolved_member.replace('CN=', '').replace('DC=', '')
290 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
291 resolved_member = resolved_member.distinguishedname
279292 except IndexError:
280293 resolved_member = member
281294 finally:
282295 resolved_members.append(resolved_member)
283 gpo_group.members = resolved_members
296 gpo_group._attributes_dict['members'] = resolved_members
284297
285298 for member in memberof:
286299 try:
287 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=queried_domain)[0]
288 resolved_member = resolved_member.distinguishedname.split(',')[:2]
289 resolved_member = '{}\\{}'.format(resolved_member[1], resolved_member[0])
290 resolved_member = resolved_member.replace('CN=', '').replace('DC=', '')
300 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
301 resolved_member = resolved_member.distinguishedname
291302 except IndexError:
292303 resolved_member = member
293304 finally:
294305 resolved_memberof.append(resolved_member)
295 gpo_group.memberof = memberof = resolved_memberof
296
306 gpo_group._attributes_dict['memberof'] = memberof = resolved_memberof
297307 return results
298308
299309 def find_gpocomputeradmin(self, queried_computername=str(),
326336 for target_ou in target_ous:
327337 ous = net_requester.get_netou(ads_path=target_ou, queried_domain=queried_domain,
328338 full_data=True)
329
330339 for ou in ous:
331 for gplink in ou.gplink.strip('[]').split(']['):
340 try:
341 gplinks = ou.gplink.strip('[]').split('][')
342 except AttributeError:
343 continue
344 for gplink in gplinks:
332345 gplink = gplink.split(';')[0]
333346 gpo_groups = self.get_netgpogroup(queried_domain=queried_domain,
334347 ads_path=gplink)
335348 for gpo_group in gpo_groups:
336349 for member in gpo_group.members:
337350 obj = net_requester.get_adobject(queried_sid=member,
338 queried_domain=queried_domain)[0]
351 queried_domain=self._queried_domain)[0]
339352 gpo_computer_admin = GPOComputerAdmin(list())
340 setattr(gpo_computer_admin, 'computername', queried_computername)
341 setattr(gpo_computer_admin, 'ou', target_ou)
342 setattr(gpo_computer_admin, 'gpodisplayname', gpo_group.gpodisplayname)
343 setattr(gpo_computer_admin, 'gpopath', gpo_group.gpopath)
344 setattr(gpo_computer_admin, 'objectname', obj.name)
345 setattr(gpo_computer_admin, 'objectdn', obj.distinguishedname)
346 setattr(gpo_computer_admin, 'objectsid', member)
347 setattr(gpo_computer_admin, 'isgroup', (obj.samaccounttype != '805306368'))
353 gpo_computer_admin.add_attributes({'computername' : queried_computername})
354 gpo_computer_admin.add_attributes({'ou' : target_ou})
355 gpo_computer_admin.add_attributes({'gpodisplayname' : gpo_group.gpodisplayname})
356 gpo_computer_admin.add_attributes({'gpopath' : gpo_group.gpopath})
357 gpo_computer_admin.add_attributes({'objectname' : obj.name})
358 gpo_computer_admin.add_attributes({'objectdn' : obj.distinguishedname})
359 gpo_computer_admin.add_attributes({'objectsid' : obj.objectsid})
360 gpo_computer_admin.add_attributes({'isgroup' : (obj.samaccounttype != '805306368')})
348361
349362 results.append(gpo_computer_admin)
350363
352365 groups_to_resolve = [gpo_computer_admin.objectsid]
353366 while groups_to_resolve:
354367 group_to_resolve = groups_to_resolve.pop(0)
368
355369 group_members = net_requester.get_netgroupmember(queried_sid=group_to_resolve,
356 queried_domain=queried_domain,
370 queried_domain=self._queried_domain,
357371 full_data=True)
358372 for group_member in group_members:
359373 gpo_computer_admin = GPOComputerAdmin(list())
360 setattr(gpo_computer_admin, 'computername', queried_computername)
361 setattr(gpo_computer_admin, 'ou', target_ou)
362 setattr(gpo_computer_admin, 'gpodisplayname', gpo_group.gpodisplayname)
363 setattr(gpo_computer_admin, 'gpopath', gpo_group.gpopath)
364 setattr(gpo_computer_admin, 'objectname', group_member.samaccountname)
365 setattr(gpo_computer_admin, 'objectdn', group_member.distinguishedname)
366 setattr(gpo_computer_admin, 'objectsid', member)
367 setattr(gpo_computer_admin, 'isgroup', (group_member.samaccounttype != '805306368'))
374 gpo_computer_admin.add_attributes({'computername' : queried_computername})
375 gpo_computer_admin.add_attributes({'ou' : target_ou})
376 gpo_computer_admin.add_attributes({'gpodisplayname' : gpo_group.gpodisplayname})
377 gpo_computer_admin.add_attributes({'gpopath' : gpo_group.gpopath})
378 gpo_computer_admin.add_attributes({'objectname' : group_member.samaccountname})
379 gpo_computer_admin.add_attributes({'objectdn' : group_member.distinguishedname})
380 gpo_computer_admin.add_attributes({'objectsid' : group_member.objectsid})
381 gpo_computer_admin.add_attributes({'isgroup' : (group_member != '805306368')})
368382
369383 results.append(gpo_computer_admin)
370384
381395 if queried_username:
382396 try:
383397 user = net_requester.get_netuser(queried_username=queried_username,
384 queried_domain=queried_domain)[0]
398 queried_domain=self._queried_domain)[0]
385399 except IndexError:
386400 raise ValueError('Username \'{}\' was not found'.format(queried_username))
387401 else:
388 target_sid = [user.objectsid]
402 target_sid = [user.objectsid]
389403 object_sam_account_name = user.samaccountname
390404 object_distinguished_name = user.distinguishedname
391405 elif queried_groupname:
392406 try:
393407 group = net_requester.get_netgroup(queried_groupname=queried_groupname,
394 queried_domain=queried_domain,
408 queried_domain=self._queried_domain,
395409 full_data=True)[0]
396410 except IndexError:
397411 raise ValueError('Group name \'{}\' was not found'.format(queried_groupname))
417431 for object_group in object_groups:
418432 try:
419433 object_group_sid = net_requester.get_adobject(queried_sam_account_name=object_group.samaccountname,
420 queried_domain=queried_domain)[0].objectsid
434 queried_domain=self._queried_domain)[0].objectsid
421435 except IndexError:
422436 # We may have the name of the group, but not its sam account name
423437 try:
424438 object_group_sid = net_requester.get_adobject(queried_name=object_group.samaccountname,
425 queried_domain=queried_domain)[0].objectsid
439 queried_domain=self._queried_domain)[0].objectsid
426440 except IndexError:
427441 # Freak accident when someone is a member of a group, but
428442 # we can't find the group in the AD
434448 for gpo_group in self.get_netgpogroup(queried_domain=queried_domain):
435449 try:
436450 for member in gpo_group.members:
451 member = member
437452 if not member.upper().startswith('S-1-5'):
438453 try:
439454 member = net_requester.get_adobject(queried_sam_account_name=member,
440 queried_domain=queried_domain)[0].objectsid
455 queried_domain=self._queried_domain)[0].objectsid
441456 except (IndexError, AttributeError):
442457 continue
443458 if (member.upper() in target_sid) or (member.lower() in target_sid):
450465
451466 for gpo_group in gpo_groups:
452467 gpo_guid = gpo_group.gponame
453 ous = net_requester.get_netou(queried_domain=queried_domain,
468 ous = net_requester.get_netou(queried_domain=self._queried_domain,
454469 queried_guid=gpo_guid, full_data=True)
455470 for ou in ous:
471 ou_distinguishedname = 'LDAP://{}'.format(ou.distinguishedname)
456472 # TODO: support filters for GPO
457473 ou_computers = [x.dnshostname for x in \
458 net_requester.get_netcomputer(queried_domain=queried_domain,
459 ads_path=ou.distinguishedname)]
474 net_requester.get_netcomputer(queried_domain=self._queried_domain,
475 ads_path=ou_distinguishedname)]
460476 gpo_location = GPOLocation(list())
461 setattr(gpo_location, 'objectname', object_distinguished_name)
462 setattr(gpo_location, 'gponame', gpo_group.gpodisplayname)
463 setattr(gpo_location, 'gpoguid', gpo_guid)
464 setattr(gpo_location, 'containername', ou.distinguishedname)
465 setattr(gpo_location, 'computers', ou_computers)
477 gpo_location.add_attributes({'objectname' : object_distinguished_name})
478 gpo_location.add_attributes({'gponame' : gpo_group.gpodisplayname})
479 gpo_location.add_attributes({'gpoguid' : gpo_guid})
480 gpo_location.add_attributes({'containername' : ou.distinguishedname})
481 gpo_location.add_attributes({'computers' : ou_computers})
466482
467483 results.append(gpo_location)
468484
1919
2020 from pywerview.requester import LDAPRPCRequester
2121 import pywerview.functions.net
22
23 import struct
2224
2325 class Misc(LDAPRPCRequester):
2426 @LDAPRPCRequester._rpc_connection_init(r'\drsuapi')
5456
5557 if domain_controllers:
5658 primary_dc = domain_controllers[0]
57 domain_sid = '-'.join(primary_dc.objectsid.split('-')[:-1])
59 domain_sid = primary_dc.objectsid
60
61 # we need to retrieve the domain sid from the controller sid
62 domain_sid = '-'.join(domain_sid.split('-')[:-1])
5863 else:
5964 domain_sid = None
6065
7479
7580 return True
7681
82 class Utils():
83 @staticmethod
84 def convert_sidtostr(raw_sid):
85 str_sid = 'S-{0}-{1}'.format(raw_sid[0], raw_sid[1])
86 for i in range(8, len(raw_sid), 4):
87 str_sid += '-{}'.format(str(struct.unpack('<I', raw_sid[i:i+4])[0]))
88 return str_sid
89
90 @staticmethod
91 def convert_guidtostr(raw_guid):
92 str_guid = str()
93 str_guid += '{}-'.format(hex(struct.unpack('<I', raw_guid[0:4])[0])[2:].zfill(8))
94 str_guid += '{}-'.format(hex(struct.unpack('<H', raw_guid[4:6])[0])[2:].zfill(4))
95 str_guid += '{}-'.format(hex(struct.unpack('<H', raw_guid[6:8])[0])[2:].zfill(4))
96 str_guid += '{}-'.format(raw_guid.hex()[16:20])
97 str_guid += raw_guid.hex()[20:]
98 return str_guid
1515 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1616
1717 import socket
18 import datetime
18 from datetime import datetime, timedelta
1919 from impacket.dcerpc.v5.ndr import NULL
2020 from impacket.dcerpc.v5 import wkst, srvs, samr
2121 from impacket.dcerpc.v5.samr import DCERPCSessionError
2323 from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY
2424 from bs4 import BeautifulSoup
2525 from ldap3.utils.conv import escape_filter_chars
26 from ldap3.protocol.microsoft import security_descriptor_control
27 from ldap3.protocol.formatters.formatters import *
28 from impacket.ldap.ldaptypes import ACE, ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, LDAP_SID, SR_SECURITY_DESCRIPTOR
2629
2730 from pywerview.requester import LDAPRPCRequester
2831 import pywerview.objects.adobjects as adobj
2932 import pywerview.objects.rpcobjects as rpcobj
3033 import pywerview.functions.misc
34 import pywerview.formatters as fmt
3135
3236 class NetRequester(LDAPRPCRequester):
3337 @LDAPRPCRequester._ldap_connection_init
3438 def get_adobject(self, queried_domain=str(), queried_sid=str(),
3539 queried_name=str(), queried_sam_account_name=str(),
36 ads_path=str(), custom_filter=str()):
37
38 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', queried_name),
39 ('samAccountName', queried_sam_account_name)):
40 ads_path=str(), attributes=list(), custom_filter=str()):
41 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
42 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
4043 if attr_value:
4144 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter)
4245 break
43
44 return self._ldap_search(object_filter, adobj.ADObject)
46 else:
47 object_filter = '(&(name=*){})'.format(custom_filter)
48
49 return self._ldap_search(object_filter, adobj.ADObject, attributes=attributes)
50
51 @LDAPRPCRequester._ldap_connection_init
52 def get_objectacl(self, queried_domain=str(), queried_sid=str(),
53 queried_name=str(), queried_sam_account_name=str(),
54 ads_path=str(), sacl=False, rights_filter=str(),
55 resolve_sids=False, resolve_guids=False, custom_filter=str()):
56 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
57 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
58 if attr_value:
59 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter)
60 break
61 else:
62 object_filter = '(&(name=*){})'.format(custom_filter)
63
64 guid_map = dict()
65 # This works on a mono-domain forest, must be tested on a more complex one
66 if resolve_guids:
67 # Dirty fix to get base DN even if custom ADS path was given
68 base_dn = ','.join(self._base_dn.split(',')[-2:])
69 guid_map = {'{00000000-0000-0000-0000-000000000000}': 'All'}
70 with NetRequester(self._domain_controller, self._domain, self._user, self._password,
71 self._lmhash, self._nthash) as net_requester:
72 for o in net_requester.get_adobject(ads_path='CN=Schema,CN=Configuration,{}'.format(base_dn),
73 attributes=['name', 'schemaIDGUID'], custom_filter='(schemaIDGUID=*)'):
74 guid_map['{{{}}}'.format(o.schemaidguid)] = o.name
75
76 for o in net_requester.get_adobject(ads_path='CN=Extended-Rights,CN=Configuration,{}'.format(base_dn),
77 attributes=['name', 'rightsGuid'], custom_filter='(objectClass=controlAccessRight)'):
78 guid_map['{{{}}}'.format(o.rightsguid.lower())] = o.name
79
80 attributes = ['distinguishedname', 'objectsid', 'ntsecuritydescriptor']
81 if sacl:
82 controls = list()
83 acl_type = 'Sacl'
84 else:
85 # The control is used to get access to ntSecurityDescriptor with an
86 # unprivileged user, see https://stackoverflow.com/questions/40771503/selecting-the-ad-ntsecuritydescriptor-attribute-as-a-non-admin/40773088
87 # /!\ May break pagination from what I've read (see Stack Overflow answer)
88 controls = security_descriptor_control(criticality=True, sdflags=0x07)
89 acl_type = 'Dacl'
90
91 security_descriptors = self._ldap_search(object_filter, adobj.ADObject,
92 attributes=attributes, controls=controls)
93
94 acl = list()
95
96 rights_to_guid = {'reset-password': '{00299570-246d-11d0-a768-00aa006e0529}',
97 'write-members': '{bf9679c0-0de6-11d0-a285-00aa003049e2}',
98 'all': '{00000000-0000-0000-0000-000000000000}'}
99 guid_filter = rights_to_guid.get(rights_filter, None)
100
101 if resolve_sids:
102 sid_resolver = NetRequester(self._domain_controller, self._domain,
103 self._user, self._password, self._lmhash, self._nthash)
104 sid_mapping = adobj.ADObject._well_known_sids.copy()
105 else:
106 sid_resolver = None
107
108 for security_descriptor in security_descriptors:
109 sd = SR_SECURITY_DESCRIPTOR()
110 try:
111 sd.fromString(security_descriptor.ntsecuritydescriptor)
112 except TypeError:
113 continue
114 for ace in sd[acl_type]['Data']:
115 if guid_filter:
116 try:
117 object_type = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
118 except KeyError:
119 continue
120 if object_type != guid_filter:
121 continue
122 attributes = dict()
123 attributes['objectdn'] = security_descriptor.distinguishedname
124 attributes['objectsid'] = security_descriptor.objectsid
125 attributes['acetype'] = ace['TypeName']
126 attributes['binarysize'] = ace['AceSize']
127 attributes['aceflags'] = fmt.format_ace_flags(ace['AceFlags'])
128 attributes['accessmask'] = ace['Ace']['Mask']['Mask']
129 attributes['activedirectoryrights'] = fmt.format_ace_access_mask(ace['Ace']['Mask']['Mask'])
130 attributes['isinherited'] = bool(ace['AceFlags'] & 0x10)
131 attributes['securityidentifier'] = format_sid(ace['Ace']['Sid'].getData())
132 if sid_resolver:
133 converted_sid = attributes['securityidentifier']
134 try:
135 resolved_sid = sid_mapping[converted_sid]
136 except KeyError:
137 try:
138 resolved_sid = sid_resolver.get_adobject(queried_sid=converted_sid,
139 queried_domain=self._queried_domain, attributes=['distinguishedname'])[0]
140 resolved_sid = resolved_sid.distinguishedname
141 except IndexError:
142 resolved_sid = attributes['securityidentifier']
143 finally:
144 sid_mapping[converted_sid] = resolved_sid
145 attributes['securityidentifier'] = resolved_sid
146 try:
147 attributes['objectaceflags'] = fmt.format_object_ace_flags(ace['Ace']['Flags'])
148 except KeyError:
149 pass
150 try:
151 attributes['objectacetype'] = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
152 attributes['objectacetype'] = guid_map[attributes['objectacetype']]
153 except KeyError:
154 pass
155 try:
156 attributes['inheritedobjectacetype'] = format_uuid_le(ace['Ace']['InheritedObjectType']) if ace['Ace']['InheritedObjectType'] else '{00000000-0000-0000-0000-000000000000}'
157 attributes['inheritedobjectacetype'] = guid_map[attributes['inheritedobjectacetype']]
158 except KeyError:
159 pass
160
161 acl.append(adobj.ACE(attributes))
162
163 return acl
45164
46165 @LDAPRPCRequester._ldap_connection_init
47166 def get_netuser(self, queried_username=str(), queried_domain=str(),
79198
80199 # RFC 4515, section 3
81200 # However if we escape *, we can no longer use wildcard within `--groupname`
82 # Maybe we can raise a warning here ?
201 # Maybe we can raise a warning here ?
83202 if not '*' in queried_groupname:
84203 queried_groupname = escape_filter_chars(queried_groupname)
85204
119238 final_results = list()
120239 for group_sam_account_name in results:
121240 obj_member_of = adobj.Group(list())
122 setattr(obj_member_of, 'samaccountname', group_sam_account_name)
241 obj_member_of._attributes_dict['samaccountname'] = group_sam_account_name
123242 final_results.append(obj_member_of)
124243 return final_results
125244 else:
208327
209328 final_results = list()
210329 for file_server_name in results:
211 attributes = list()
212 attributes.append({'type': 'dnshostname', 'vals': [file_server_name]})
330 attributes = dict()
331 attributes['dnshostname'] = file_server_name
213332 final_results.append(adobj.FileServer(attributes))
214333
215334 return final_results
227346 for remote_server in dfs.remoteservername:
228347 remote_server = str(remote_server)
229348 if '\\' in remote_server:
230 attributes = {'name': [dfs.name.encode('utf-8')],
231 'remoteservername': [remote_server.split('\\')[2].encode('utf-8')]}
349 attributes = {'name': dfs.name,
350 'remoteservername': remote_server.split('\\')[2]}
232351 results.append(adobj.DFS(attributes))
233352
234353 return results
249368 for target in soup_target_list.targets.contents:
250369 if '\\' in target.string:
251370 server_name, dfs_root = target.string.split('\\')[2:4]
252 attributes = {'name': ['{}{}'.format(dfs_root, share_name).encode('utf-8')],
253 'remoteservername': [server_name.encode('utf-8')]}
371 attributes = {'name': '{}{}'.format(dfs_root, share_name),
372 'remoteservername': server_name}
254373
255374 results.append(adobj.DFS(attributes))
256375
338457 # `--groupname` option is supplied
339458 if _groupname:
340459 groups = self.get_netgroup(queried_groupname=_groupname,
341 queried_domain=queried_domain,
460 queried_domain=self._queried_domain,
342461 full_data=True)
343462
344463 # `--groupname` option is missing, falling back to the "Domain Admins"
352471 self._nthash) as misc_requester:
353472 queried_sid = misc_requester.get_domainsid(queried_domain) + '-512'
354473 groups = self.get_netgroup(queried_sid=queried_sid,
355 queried_domain=queried_domain,
474 queried_domain=self._queried_domain,
356475 full_data=True)
357476 except IndexError:
358477 raise ValueError('The group {} was not found'.format(_groupname))
365484 group_memberof_filter = '(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:={}){})'.format(group.distinguishedname, custom_filter)
366485
367486 members = self.get_netuser(custom_filter=group_memberof_filter,
368 queried_domain=queried_domain)
487 queried_domain=self._queried_domain)
369488 else:
370489 # TODO: range cycling
371490 try:
373492 # RFC 4515, section 3
374493 member = escape_filter_chars(member, encoding='utf-8')
375494 dn_filter = '(distinguishedname={}){}'.format(member, custom_filter)
376 members += self.get_netuser(custom_filter=dn_filter, queried_domain=queried_domain)
377 members += self.get_netgroup(custom_filter=dn_filter, queried_domain=queried_domain, full_data=True)
495 members += self.get_netuser(custom_filter=dn_filter, queried_domain=self._queried_domain)
496 members += self.get_netgroup(custom_filter=dn_filter, queried_domain=self._queried_domain, full_data=True)
378497 # The group doesn't have any members
379498 except AttributeError:
380499 continue
390509 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
391510 except IndexError:
392511 member_domain = str()
393 is_group = (member.samaccounttype != '805306368')
512 is_group = (member.samaccounttype != 805306368)
394513
395514 attributes = dict()
396515 if queried_domain:
397516 attributes['groupdomain'] = queried_domain
398517 else:
399 attributes['groupdomain'] = self._domain
518 attributes['groupdomain'] = self._queried_domain
400519 attributes['groupname'] = group.name
401520 attributes['membername'] = member.samaccountname
402521 attributes['memberdomain'] = member_domain
403522 attributes['isgroup'] = is_group
404523 attributes['memberdn'] = member_dn
405 attributes['membersid'] = member.objectsid
524 attributes['objectsid'] = member.objectsid
406525
407526 final_member.add_attributes(attributes)
408527
603722 member_handle = resp['UserHandle']
604723 attributes['isgroup'] = False
605724 resp = samr.hSamrQueryInformationUser(self._rpc_connection, member_handle)
606 attributes['name'] = '{}/{}'.format(member_domain, resp['Buffer']['General']['UserName'])
725 attributes['name'] = '{}\\{}'.format(member_domain, resp['Buffer']['General']['UserName'])
607726 except DCERPCSessionError:
608727 resp = samr.hSamrOpenAlias(self._rpc_connection, domain_handle, aliasId=member_rid)
609728 member_handle = resp['AliasHandle']
610729 attributes['isgroup'] = True
611730 resp = samr.hSamrQueryInformationAlias(self._rpc_connection, member_handle)
612 attributes['name'] = '{}/{}'.format(member_domain, resp['Buffer']['General']['Name'])
613 attributes['lastlogin'] = str()
731 attributes['name'] = '{}\\{}'.format(member_domain, resp['Buffer']['General']['Name'])
732 attributes['lastlogon'] = str()
614733 break
615734 # It's a domain member
616735 else:
621740 member_dn = ad_object.distinguishedname
622741 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
623742 try:
624 attributes['name'] = '{}/{}'.format(member_domain, ad_object.samaccountname)
743 attributes['name'] = '{}\\{}'.format(member_domain, ad_object.samaccountname)
625744 except AttributeError:
626745 # Here, the member is a foreign security principal
627746 # TODO: resolve it properly
628 attributes['name'] = '{}/{}'.format(member_domain, ad_object.objectsid)
629 attributes['isgroup'] = ad_object.isgroup
747 attributes['name'] = '{}\\{}'.format(member_domain, ad_object.objectsid)
748 attributes['isgroup'] = 'group' in ad_object.objectclass
630749 try:
631 attributes['lastlogin'] = ad_object.lastlogon
750 # TODO: Now, lastlogon is raw, convert here or within rpc __str__ ?
751 attributes['lastlogon'] = ad_object.lastlogon
632752 except AttributeError:
633 attributes['lastlogin'] = str()
753 attributes['lastlogon'] = str()
634754 except IndexError:
635755 # We did not manage to resolve this SID against the DC
636756 attributes['isdomain'] = False
637757 attributes['isgroup'] = False
638758 attributes['name'] = attributes['sid']
639 attributes['lastlogin'] = str()
759 attributes['lastlogon'] = str()
640760 else:
641761 attributes['isgroup'] = False
642762 attributes['name'] = str()
643 attributes['lastlogin'] = str()
763 attributes['lastlogon'] = str()
644764
645765 results.append(rpcobj.RPCObject(attributes))
646766
652772 domain_member_attributes['isdomain'] = True
653773 member_dn = domain_member.distinguishedname
654774 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
655 domain_member_attributes['name'] = '{}/{}'.format(member_domain, domain_member.samaccountname)
775 domain_member_attributes['name'] = '{}\\{}'.format(member_domain, domain_member.samaccountname)
656776 domain_member_attributes['isgroup'] = domain_member.isgroup
657777 domain_member_attributes['isdomain'] = True
778 # TODO: Nope, maybe here we can call get-netdomaincontroller ?
779 # Need to check in powerview
658780 domain_member_attributes['server'] = attributes['name']
659781 domain_member_attributes['sid'] = domain_member.objectsid
660782 try:
783 # TODO : Same here, must convert the timestamp
661784 domain_member_attributes['lastlogin'] = ad_object.lastlogon
662785 except AttributeError:
663786 domain_member_attributes['lastlogin'] = str()
690813
691814 @LDAPRPCRequester._wmi_connection_init()
692815 def get_userevent(self, event_type=['logon', 'tgt'], date_start=5):
693 limit_date = (datetime.datetime.today() - datetime.timedelta(days=date_start)).strftime('%Y%m%d%H%M%S.%f-000')
816 limit_date = (datetime.today() - timedelta(days=date_start)).strftime('%Y%m%d%H%M%S.%f-000')
694817 if event_type == ['logon']:
695818 where_clause = 'EventCode=4624'
696819 elif event_type == ['tgt']:
707830 wmi_event = wmi_enum_event.Next(0xffffffff, 1)[0]
708831 wmi_event_type = wmi_event.EventIdentifier
709832 wmi_event_info = wmi_event.InsertionStrings
710 time = datetime.datetime.strptime(wmi_event.TimeGenerated, '%Y%m%d%H%M%S.%f-000')
833 time = datetime.strptime(wmi_event.TimeGenerated, '%Y%m%d%H%M%S.%f-000')
711834 if wmi_event_type == 4624:
712835 logon_type = int(wmi_event_info[8])
713836 user = wmi_event_info[5]
1414
1515 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1616
17 from datetime import datetime, timedelta
1817 import inspect
1918 import struct
2019 import pyasn1
21 import codecs
20 from impacket.ldap.ldaptypes import ACE, ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, LDAP_SID, SR_SECURITY_DESCRIPTOR
21
22 import pywerview.functions.misc as misc
2223
2324 class ADObject:
24 __uac_flags = {0x0000001: 'SCRIPT',
25 0x0000002: 'ACCOUNTDISABLE',
26 0x0000008: 'HOMEDIR_REQUIRED',
27 0x0000010: 'LOCKOUT',
28 0x0000020: 'PASSWD_NOTREQD',
29 0x0000040: 'PASSWD_CANT_CHANGE',
30 0x0000080: 'ENCRYPTED_TEXT_PWD_ALLOWED',
31 0x0000100: 'TEMP_DUPLICATE_ACCOUNT',
32 0x0000200: 'NORMAL_ACCOUNT',
33 0x0000800: 'INTERDOMAIN_TRUST_ACCOUNT',
34 0x0001000: 'WORKSTATION_TRUST_ACCOUNT',
35 0x0002000: 'SERVER_TRUST_ACCOUNT',
36 0x0010000: 'DONT_EXPIRE_PASSWORD',
37 0x0020000: 'MNS_LOGON_ACCOUNT',
38 0x0040000: 'SMARTCARD_REQUIRED',
39 0x0080000: 'TRUSTED_FOR_DELEGATION',
40 0x0100000: 'NOT_DELEGATED',
41 0x0200000: 'USE_DES_KEY_ONLY',
42 0x0400000: 'DONT_REQ_PREAUTH',
43 0x0800000: 'PASSWORD_EXPIRED',
44 0x1000000: 'TRUSTED_TO_AUTH_FOR_DELEGATION',
45 0x4000000: 'PARTIAL_SECRETS_ACCOUNT'}
25 _well_known_sids = {'S-1-0-0': 'Nobody', 'S-1-0': 'Null Authority', 'S-1-1-0': 'Everyone',
26 'S-1-1': 'World Authority', 'S-1-2-0': 'Local', 'S-1-2-1': 'Console Logon',
27 'S-1-2': 'Local Authority', 'S-1-3-0': 'Creator Owner', 'S-1-3-1': 'Creator Group',
28 'S-1-3-2': 'Creator Owner Server', 'S-1-3-3': 'Creator Group Server', 'S-1-3-4': 'Owner Rights',
29 'S-1-3': 'Creator Authority', 'S-1-4': 'Non-unique Authority', 'S-1-5-10': 'Principal Self',
30 'S-1-5-11': 'Authenticated Users', 'S-1-5-12': 'Restricted Code', 'S-1-5-13': 'Terminal Server Users',
31 'S-1-5-14': 'Remote Interactive Logon', 'S-1-5-17': 'This Organization', 'S-1-5-18': 'Local System',
32 'S-1-5-19': 'NT Authority', 'S-1-5-1': 'Dialup', 'S-1-5-20': 'NT Authority',
33 'S-1-5-2': 'Network', 'S-1-5-32-546': 'Guests', 'S-1-5-32-547': 'Power Users',
34 'S-1-5-32-551': 'Backup Operators', 'S-1-5-32-555': 'Builtin\\Remote Desktop Users',
35 'S-1-5-32-556': 'Builtin\\Network Configuration Operators',
36 'S-1-5-32-557': 'Builtin\\Incoming Forest Trust Builders',
37 'S-1-5-32-558': 'Builtin\\Performance Monitor Users',
38 'S-1-5-32-559': 'Builtin\\Performance Log Users',
39 'S-1-5-32-560': 'Builtin\\Windows Authorization Access Group',
40 'S-1-5-32-561': 'Builtin\\Terminal Server License Servers',
41 'S-1-5-32-562': 'Builtin\\Distributed COM Users',
42 'S-1-5-32-569': 'Builtin\\Cryptographic Operators',
43 'S-1-5-32-573': 'Builtin\\Event Log Readers',
44 'S-1-5-32-574': 'Builtin\\Certificate Service DCOM Access',
45 'S-1-5-32-575': 'Builtin\\RDS Remote Access Servers',
46 'S-1-5-32-576': 'Builtin\\RDS Endpoint Servers',
47 'S-1-5-32-577': 'Builtin\\RDS Management Servers',
48 'S-1-5-32-578': 'Builtin\\Hyper-V Administrators',
49 'S-1-5-32-579': 'Builtin\\Access Control Assistance Operators',
50 'S-1-5-32-580': 'Builtin\\Remote Management Users',
51 'S-1-5-32-582': 'Storage Replica Administrators',
52 'S-1-5-3': 'Batch', 'S-1-5-4': 'Interactive', 'S-1-5-64-10': 'NTLM Authentication',
53 'S-1-5-64-14': 'SChannel Authentication', 'S-1-5-64-21': 'Digest Authentication',
54 'S-1-5-6': 'Service', 'S-1-5-7': 'Anonymous', 'S-1-5-80-0': 'NT Services\\All Services',
55 'S-1-5-80': 'NT Service', 'S-1-5-8': 'Proxy', 'S-1-5-9': 'Enterprise Domain Controllers',
56 'S-1-5': 'NT Authority'}
4657
4758 def __init__(self, attributes):
59 self._attributes_dict = dict()
4860 self.add_attributes(attributes)
4961
5062 def add_attributes(self, attributes):
5163 for attr in attributes:
52 #print(attr)
53 #print(attributes[attr], attr)
54 t = str(attr).lower()
55 if t in ('logonhours', 'msds-generationid'):
56 value = bytes(attributes[attr][0])
57 value = [x for x in value]
58 elif t in ('trustattributes', 'trustdirection', 'trusttype'):
59 value = int(attributes[attr][0])
60 elif t in ('objectsid', 'ms-ds-creatorsid'):
61 value = codecs.encode(bytes(attributes[attr][0]),'hex')
62 init_value = bytes(attributes[attr][0])
63 value = 'S-{0}-{1}'.format(init_value[0], init_value[1])
64 for i in range(8, len(init_value), 4):
65 value += '-{}'.format(str(struct.unpack('<I', init_value[i:i+4])[0]))
66 elif t == 'objectguid':
67 init_value = bytes(attributes[attr][0])
68 value = str()
69 value += '{}-'.format(hex(struct.unpack('<I', init_value[0:4])[0])[2:].zfill(8))
70 value += '{}-'.format(hex(struct.unpack('<H', init_value[4:6])[0])[2:].zfill(4))
71 value += '{}-'.format(hex(struct.unpack('<H', init_value[6:8])[0])[2:].zfill(4))
72 value += '{}-'.format((codecs.encode(init_value,'hex')[16:20]).decode('utf-8'))
73 value += init_value.hex()[20:]
74 elif t in ('dscorepropagationdata', 'whenchanged', 'whencreated'):
75 value = list()
76 for val in attributes[attr]:
77 value.append(str(datetime.strptime(str(val.decode('utf-8')), '%Y%m%d%H%M%S.0Z')))
78 elif t in ('accountexpires', 'pwdlastset', 'badpasswordtime', 'lastlogontimestamp', 'lastlogon', 'lastlogoff'):
64 self._attributes_dict[attr.lower()] = attributes[attr]
65
66 def __getattr__(self, attr):
67 try:
68 return self._attributes_dict[attr]
69 except KeyError:
70 if attr == 'isgroup':
7971 try:
80 filetimestamp = int(attributes[attr][0].decode('utf-8'))
81 if filetimestamp != 9223372036854775807:
82 timestamp = (filetimestamp - 116444736000000000)/10000000
83 value = datetime.fromtimestamp(0) + timedelta(seconds=timestamp)
84 else:
85 value = 'never'
86 except IndexError:
87 value = 'empty'
88 elif t == 'isgroup':
89 value = attributes[attr]
90 elif t == 'objectclass':
91 value = [x.decode('utf-8') for x in attributes[attr]]
92 setattr(self, 'isgroup', ('group' in value))
93 elif len(attributes[attr]) > 1:
94 try:
95 value = [x.decode('utf-8') for x in attributes[attr]]
96 except (UnicodeDecodeError):
97 value = [x for x in attributes[attr]]
98 except (AttributeError):
99 value = attributes[attr]
100 else:
101 try:
102 value = attributes[attr][0].decode('utf-8')
103 except (IndexError):
104 value = str()
105 except (UnicodeDecodeError):
106 value = attributes[attr][0]
107
108 setattr(self, t, value)
109
72 return 'group' in self._attributes_dict['objectclass']
73 except KeyError:
74 return False
75 raise AttributeError
76
77 # In this method, we try to pretty print common AD attributes
11078 def __str__(self):
11179 s = str()
112 members = inspect.getmembers(self, lambda x: not(inspect.isroutine(x)))
11380 max_length = 0
114 for member in members:
115 if not member[0].startswith('_'):
116 if len(member[0]) > max_length:
117 max_length = len(member[0])
118 for member in members:
119 if not member[0].startswith('_'):
120 if member[0] == 'msmqdigests':
121 member_value = (',\n' + ' ' * (max_length + 2)).join(x.hex() for x in member[1])
122 elif member[0] == 'useraccountcontrol':
123 member_value = list()
124 for uac_flag, uac_label in ADObject.__uac_flags.items():
125 if int(member[1]) & uac_flag == uac_flag:
126 member_value.append(uac_label)
127 elif isinstance(member[1], list):
128 if member[0] in ('logonhours',):
129 member_value = member[1]
130 elif member[0] in ('usercertificate',
131 'protocom-sso-entries', 'protocom-sso-security-prefs',):
132 member_value = (',\n' + ' ' * (max_length + 2)).join(
133 '{}...'.format(x.hex()[:100]) for x in member[1])
134 else:
135 member_value = (',\n' + ' ' * (max_length + 2)).join(str(x) for x in member[1])
136 elif member[0] in('msmqsigncertificates', 'userparameters',
137 'jpegphoto', 'thumbnailphoto', 'usercertificate',
138 'msexchmailboxguid', 'msexchmailboxsecuritydescriptor',
139 'msrtcsip-userroutinggroupid', 'msexchumpinchecksum',
140 'protocom-sso-auth-data', 'protocom-sso-entries-checksum',
141 'protocom-sso-security-prefs-checksum', ):
142 # Attribut exists but it is empty
143 try:
144 member_value = '{}...'.format(member[1].hex()[:100])
145 except AttributeError:
146 member_value = ''
147 else:
148 member_value = member[1]
149 s += '{}: {}{}\n'.format(member[0], ' ' * (max_length - len(member[0])), member_value)
150
81 for attr in self._attributes_dict:
82 if len(attr) > max_length:
83 max_length = len(attr)
84 for attr in self._attributes_dict:
85 attribute = self._attributes_dict[attr]
86 if isinstance(attribute, list):
87 if any(isinstance(x, bytes) for x in attribute):
88 attribute = ['{}...'.format(x.hex()[:97]) for x in attribute]
89 attribute_temp = ', '.join(str(x) for x in attribute)
90 if len(attribute_temp) > 100:
91 attribute_temp = str()
92 line = str()
93 for x in attribute:
94 if len(line) + len(str(x)) <= 100:
95 line += '{}, '.format(x)
96 else:
97 attribute_temp += line + '\n' + ' ' * (max_length + 2)
98 line = str()
99 line += '{}, '.format(x)
100 attribute_temp += line + '\n' + ' ' * (max_length + 2)
101 attribute = attribute_temp.rstrip().rstrip(',')
102 elif isinstance(attribute, bytes):
103 attribute = '{}...'.format(attribute.hex()[:100])
104 elif isinstance(attribute, ADObject):
105 attribute = ('\n' + str(attribute)).replace('\n', '\n\t')
106
107 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
108 #if not member.startswith('_'):
109 ##print(len(member[1]))
110 ## print(member)
111 ## ??
112 #if member in ('logonhours', 'msds-generationid'):
113 #value = member[1]
114 #member_value = [x for x in value]
115
116 ## Attribute is a SID
117 #elif member in ('objectsid', 'ms-ds-creatorsid', 'securityidentifier'):
118 #init_value = member[1]
119 #member_value = misc.Utils.convert_sidtostr(init_value)
120
151121 s = s[:-1]
152122 return s
153
123
154124 def __repr__(self):
155125 return str(self)
156126
157 class User(ADObject):
127 class ACE(ADObject):
128
158129 def __init__(self, attributes):
159130 ADObject.__init__(self, attributes)
160 for attr in filter(lambda _: _ in attributes, ('homedirectory',
161 'scriptpath',
162 'profilepath')):
163 if not hasattr(self, attr):
164 setattr(self, attr, str())
131
132 # We set iscallback, depending on the type of ACE
133 self._attributes_dict['iscallbak'] = ('CALLBACK' in self.acetype)
134
135 class User(ADObject):
136 pass
165137
166138 class Group(ADObject):
139 pass
140
141 class Computer(ADObject):
142 pass
143
144 class FileServer(ADObject):
145 pass
146
147 class DFS(ADObject):
148 pass
149
150 class OU(ADObject):
151 pass
152
153 class Site(ADObject):
154 pass
155
156 class Subnet(ADObject):
157 pass
158
159 class Trust(ADObject):
160
167161 def __init__(self, attributes):
168162 ADObject.__init__(self, attributes)
169 try:
170 if not isinstance(self.member, list):
171 self.member = [self.member]
172 except AttributeError:
173 pass
174
175 class Computer(ADObject):
176 pass
177
178 class FileServer(ADObject):
179 pass
180
181 class DFS(ADObject):
182 pass
183
184 class OU(ADObject):
185 def __init__(self, attributes):
186 ADObject.__init__(self, attributes)
187 self.distinguishedname = 'LDAP://{}'.format(self.distinguishedname)
188
189 class Site(ADObject):
190 pass
191
192 class Subnet(ADObject):
193 pass
194
195 class Trust(ADObject):
196 __trust_attrib = {0x1: 'non_transitive', 0x2: 'uplevel_only',
197 0x4: 'filter_sids', 0x8: 'forest_transitive',
198 0x10: 'cross_organization', 0x20: 'within_forest',
199 0x40: 'treat_as_external',
200 0x80: 'trust_uses_rc4_encryption',
201 0x100: 'trust_uses_aes_keys',
202 0X200: 'cross_organization_no_tgt_delegation',
203 0x400: 'pim_trust'}
204
205 __trust_direction = {0: 'disabled', 1: 'inbound',
206 2: 'outbound', 3: 'bidirectional'}
207
208 __trust_type = {1: 'windows_non_active_directory',
209 2: 'windows_active_directory', 3: 'mit'}
210
211 def __init__(self, attributes):
212 ad_obj = ADObject(attributes)
213 self.targetname = ad_obj.name
214
215 self.trustdirection = Trust.__trust_direction.get(ad_obj.trustdirection, 'unknown')
216 self.trusttype = Trust.__trust_type.get(ad_obj.trusttype, 'unknown')
217 self.whencreated = ad_obj.whencreated
218 self.whenchanged = ad_obj.whenchanged
219
220 self.trustattributes = list()
221 for attrib_flag, attrib_label in Trust.__trust_attrib.items():
222 if ad_obj.trustattributes & attrib_flag:
223 self.trustattributes.append(attrib_label)
224
163 trust_attributes = self.trustattributes
164 trust_direction = self.trustdirection
225165 # If the filter SIDs attribute is not manually set, we check if we're
226166 # not in a use case where SIDs are implicitly filtered
227167 # Based on https://github.com/vletoux/pingcastle/blob/master/Healthcheck/TrustAnalyzer.cs
228 if 'filter_sids' not in self.trustattributes:
229 if not (self.trustdirection == 'disabled' or \
230 self.trustdirection == 'inbound' or \
231 'within_forest' in self.trustattributes or \
232 'pim_trust' in self.trustattributes):
233 if 'forest_transitive' in self.trustattributes and 'treat_as_external' not in self.trustattributes:
234 self.trustattributes.append('filter_sids')
235
236 class GPO(ADObject):
237 pass
238
239 class GptTmpl(ADObject):
168 if 'filter_sids' not in trust_attributes:
169 if not (trust_direction == 'disabled' or \
170 trust_direction == 'inbound' or \
171 'within_forest' in trust_attributes or \
172 'pim_trust' in trust_attributes):
173 if 'forest_transitive' in trust_attributes and 'treat_as_external' not in trust_attributes:
174 self._attributes_dict['trustattributes'].append('filter_sids')
175
176 # Pretty printing Trust object, we don't want to print all the attributes
177 # so we only print useful ones (trustattributes, trustdirection, trustpartner
178 # trusttype, whenchanged, whencreated)
240179 def __str__(self):
241180 s = str()
242 members = inspect.getmembers(self, lambda x: not(inspect.isroutine(x)))
243 for member in members:
244 if not member[0].startswith('_'):
245 s += '{}:\n'.format(member[0])
246 member_value_str = str(member[1])
247 for line in member_value_str.split('\n'):
248 s += '\t{}\n'.format(line)
181 max_length = len('trustattributes')
182
183 for attr in self._attributes_dict:
184 if attr in ('trustpartner', 'trustdirection', 'trusttype', 'whenchanged', 'whencreated'):
185 attribute = self._attributes_dict[attr]
186 elif attr == 'trustattributes':
187 attribute = ', '.join(self._attributes_dict[attr])
188 else:
189 continue
190 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
249191
250192 s = s[:-1]
251193 return s
194 pass
195
196 class GPO(ADObject):
197 pass
198
199 class PSO(ADObject):
200 pass
201
202 class GptTmpl(ADObject):
203 pass
252204
253205 class GPOGroup(ADObject):
254206 pass
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
1515 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 from __future__ import unicode_literals
1816
1917 import inspect
2018
1717 import socket
1818 import ntpath
1919 import ldap3
20
21 from ldap3.protocol.formatters.formatters import *
2022
2123 from impacket.smbconnection import SMBConnection
2224 from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
2527 from impacket.dcerpc.v5.dtypes import NULL
2628 from impacket.dcerpc.v5.dcomrt import DCOMConnection
2729 from impacket.dcerpc.v5.rpcrt import DCERPCException
30
31 import pywerview.formatters as fmt
2832
2933 class LDAPRequester():
3034 def __init__(self, domain_controller, domain=str(), user=(), password=str(),
8589 # Format the username and the domain
8690 # ldap3 seems not compatible with USER@DOMAIN format
8791 user = '{}\\{}'.format(self._domain, self._user)
92
93 # Call custom formatters for several AD attributes
94 formatter = {'userAccountControl': fmt.format_useraccountcontrol,
95 'trustType': fmt.format_trusttype,
96 'trustDirection': fmt.format_trustdirection,
97 'trustAttributes': fmt.format_trustattributes,
98 'msDS-MaximumPasswordAge': format_ad_timedelta,
99 'msDS-MinimumPasswordAge': format_ad_timedelta,
100 'msDS-LockoutDuration': format_ad_timedelta,
101 'msDS-LockoutObservationWindow': format_ad_timedelta}
88102
89103 # Choose between password or pth
90104 if self._lmhash and self._nthash:
91105 lm_nt_hash = '{}:{}'.format(self._lmhash, self._nthash)
92106
93 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller))
107 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller),
108 formatter=formatter)
94109 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
95110 authentication=ldap3.NTLM, raise_exceptions=True)
96111
98113 ldap_connection.bind()
99114 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
100115 # We need to try SSL (pth version)
101 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller))
116 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller),
117 formatter=formatter)
102118 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
103119 authentication=ldap3.NTLM, raise_exceptions=True)
104120
105121 ldap_connection.bind()
106122
107123 else:
108 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller))
124 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller),
125 formatter=formatter)
109126 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
110127 authentication=ldap3.NTLM, raise_exceptions=True)
111128
113130 ldap_connection.bind()
114131 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
115132 # We nedd to try SSL (password version)
116 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller))
133 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller),
134 formatter=formatter)
117135 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
118136 authentication=ldap3.NTLM, raise_exceptions=True)
119137
121139
122140 self._ldap_connection = ldap_connection
123141
124 def _ldap_search(self, search_filter, class_result, attributes=list()):
142 def _ldap_search(self, search_filter, class_result, attributes=list(), controls=list()):
125143 results = list()
126144
127145 # if no attribute name specified, we return all attributes
132150 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
133151 search_results=self._ldap_connection.extend.standard.paged_search(search_base=self._base_dn,
134152 search_filter=search_filter, attributes=attributes,
135 paged_size=1000, generator=True)
153 controls=controls, paged_size=1000, generator=True)
136154 # TODO: for debug only
137155 except Exception as e:
138156 import sys
140158
141159 # Skip searchResRef
142160 for result in search_results:
143 if result['type'] is not 'searchResEntry':
161 if result['type'] != 'searchResEntry':
144162 continue
145 results.append(class_result(result['raw_attributes']))
163 results.append(class_result(result['attributes']))
146164
147165 return results
148166
11
22 from setuptools import setup, find_packages
33
4 try:
5 import pypandoc
6 long_description = pypandoc.convert_file('README.md', 'rst')
7 except(IOError, ImportError):
8 long_description = open('README.md').read()
4 long_description = open('README.md').read()
95
106 setup(name='pywerview',
11 version='0.3.2',
7 version='0.3.3',
128 description='A Python port of PowerSploit\'s PowerView',
139 long_description=long_description,
10 long_description_content_type='text/markdown',
1411 dependency_links = ['https://github.com/SecureAuthCorp/impacket/tarball/master#egg=impacket-0.9.22'],
1512 classifiers=[
1613 'Environment :: Console',