Codebase list pywerview / 1fd5bdc
New upstream version 0.3.2 Sophie Brun 2 years ago
14 changed file(s) with 232 addition(s) and 173 deletion(s). Raw diff Collapse all Expand all
1010
1111 Fork me on [GitHub](https://github.com/the-useless-one/pywerview).
1212
13 ![License](https://img.shields.io/github/license/the-useless-one/pywerview.svg?maxAge=2592000)
14 ![Python versions](https://img.shields.io/pypi/pyversions/pywerview.svg?maxAge=2592000)
15 [![GitHub release](https://img.shields.io/github/release/the-useless-one/pywerview.svg?maxAge=2592001&label=GitHub%20release)](https://github.com/the-useless-one/pywerview/releases/latest)
16 [![PyPI version](https://img.shields.io/pypi/v/pywerview.svg?maxAge=2592000)](https://pypi.python.org/pypi/pywerview)
13 [![License](https://img.shields.io/github/license/the-useless-one/pywerview)](https://github.com/the-useless-one/pywerview/blob/master/LICENSE)
14 ![Python versions](https://img.shields.io/pypi/pyversions/pywerview)
15 [![GitHub release](https://img.shields.io/github/v/release/the-useless-one/pywerview)](https://github.com/the-useless-one/pywerview/releases/latest)
16 [![PyPI version](https://img.shields.io/pypi/v/pywerview)](https://pypi.python.org/pypi/pywerview)
1717
1818 ## HISTORY
1919
2929 Linux.
3030
3131 That's why I decided to rewrite some of PowerView's functionalities in Python,
32 using the wonderful [impacket](https://github.com/CoreSecurity/impacket/)
32 using the wonderful [impacket](https://github.com/SecureAuthCorp/impacket)
3333 library.
3434
3535 *Update:* I haven't tested the last version of PowerView yet, which can run
5757
5858 ## REQUIREMENTS
5959
60 * Python 2.7
61 * impacket >= 0.9.16-dev
60 * Python 3.6
61 * impacket >= 0.9.22
62 * ldap3 >= 2.8.1
6263
6364 ## FUNCTIONALITIES
6465
178179 contributions.
179180 * Thanks to [@ThePirateWhoSmellsOfSunflowers](https://github.com/ThePirateWhoSmellsOfSunflowers)
180181 for his debugging, love you baby :heart:
182 * Thanks to [@mpgn](https://github.com/mpgn) for his python 3 contributions.
181183
182184 ## COPYRIGHT
183185
184186 PywerView - A Python rewriting of PowerSploit's PowerView
185187
186 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
188 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
187189
188190 This program is free software: you can redistribute it and/or modify it
189191 under the terms of the GNU General Public License as published by the
00 #!/usr/bin/env python
1 # -*- coding: utf8 -*-
21 #
32 # This file is part of PywerView.
43
1514 # You should have received a copy of the GNU General Public License
1615 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1716
18 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1918
2019 from pywerview.functions.net import NetRequester
2120 from pywerview.functions.gpo import GPORequester
3635 def get_netuser(domain_controller, domain, user, password=str(), lmhash=str(),
3736 nthash=str(), queried_username=str(), queried_domain=str(), ads_path=str(),
3837 admin_count=False, spn=False, unconstrained=False, allow_delegation=False,
39 preauth_notreq=False, custom_filter=str()):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash)
38 preauth_notreq=False, custom_filter=str(),
39 attributes=[]):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash)
4242 return requester.get_netuser(queried_username=queried_username,
4343 queried_domain=queried_domain, ads_path=ads_path, admin_count=admin_count,
4444 spn=spn, unconstrained=unconstrained, allow_delegation=allow_delegation,
45 preauth_notreq=preauth_notreq, custom_filter=custom_filter)
45 preauth_notreq=preauth_notreq, custom_filter=custom_filter,
46 attributes=attributes)
4647
4748 def get_netgroup(domain_controller, domain, user, password=str(),
4849 lmhash=str(), nthash=str(), queried_groupname='*', queried_sid=str(),
5960 lmhash=str(), nthash=str(), queried_computername='*', queried_spn=str(),
6061 queried_os=str(), queried_sp=str(), queried_domain=str(), ads_path=str(),
6162 printers=False, unconstrained=False, ping=False, full_data=False,
62 custom_filter=str()):
63 custom_filter=str(), attributes=[]):
6364 requester = NetRequester(domain_controller, domain, user, password,
6465 lmhash, nthash)
6566 return requester.get_netcomputer(queried_computername=queried_computername,
6667 queried_spn=queried_spn, queried_os=queried_os, queried_sp=queried_sp,
6768 queried_domain=queried_domain, ads_path=ads_path, printers=printers,
6869 unconstrained=unconstrained, ping=ping, full_data=full_data,
69 custom_filter=custom_filter)
70 custom_filter=custom_filter, attributes=attributes)
7071
7172 def get_netdomaincontroller(domain_controller, domain, user, password=str(),
7273 lmhash=str(), nthash=str(), queried_domain=str()):
99100
100101 def get_netsite(domain_controller, domain, user, password=str(), lmhash=str(),
101102 nthash=str(), queried_domain=str(), queried_sitename=str(),
102 queried_guid=str(), ads_path=str(), full_data=False):
103 queried_guid=str(), ads_path=str(), ads_prefix='CN=Sites,CN=Configuration',
104 full_data=False):
103105 requester = NetRequester(domain_controller, domain, user, password,
104106 lmhash, nthash)
105107 return requester.get_netsite(queried_domain=queried_domain,
106108 queried_sitename=queried_sitename, queried_guid=queried_guid,
107 ads_path=ads_path, full_data=full_data)
109 ads_path=ads_path, ads_prefix=ads_prefix, full_data=full_data)
108110
109111 def get_netsubnet(domain_controller, domain, user, password=str(),
110112 lmhash=str(), nthash=str(), queried_domain=str(), queried_sitename=str(),
111 ads_path=str(), full_data=False):
113 ads_path=str(), ads_prefix='CN=Sites,CN=Configuration', full_data=False):
112114 requester = NetRequester(domain_controller, domain, user, password,
113115 lmhash, nthash)
114116 return requester.get_netsubnet(queried_domain=queried_domain,
115 queried_sitename=queried_sitename, ads_path=ads_path, full_data=full_data)
117 queried_sitename=queried_sitename, ads_path=ads_path, ads_prefix=ads_prefix,
118 full_data=full_data)
116119
117120 def get_netdomaintrust(domain_controller, domain, user, password=str(),
118121 lmhash=str(), nthash=str(), queried_domain=str()):
0 #!/usr/bin/env python
1 # -*- coding: utf8 -*-
0 #!/usr/bin/env python3
21 #
32 # This file is part of PywerView.
43
1514 # You should have received a copy of the GNU General Public License
1615 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1716
18 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1918
2019 import argparse
2120 from pywerview.cli.helpers import *
2423 def main():
2524 # Main parser
2625 parser = argparse.ArgumentParser(description='Rewriting of some PowerView\'s functionalities in Python')
27 subparsers = parser.add_subparsers(title='Subcommands', description='Available subcommands')
26 subparsers = parser.add_subparsers(title='Subcommands', description='Available subcommands', dest='submodule')
27
28 # hack for python < 3.9 : https://stackoverflow.com/questions/23349349/argparse-with-required-subparser
29 subparsers.required = True
2830
2931 # TODO: support keberos authentication
3032 # Credentials parser
111113 help='Query only users with not-null Service Principal Names')
112114 get_netuser_parser.add_argument('--custom-filter', dest='custom_filter',
113115 default=str(), help='Custom filter')
116 get_netuser_parser.add_argument('--attributes', nargs='+', dest='attributes',
117 default=[], help='Object attributes to return')
114118 get_netuser_parser.set_defaults(func=get_netuser)
115119
116120 # Parser for the get-netgroup command
155159 help='Ping computers (will only return up computers)')
156160 get_netcomputer_parser.add_argument('--full-data', action='store_true',
157161 help='If set, returns full information on the groups, otherwise, just the dnsHostName')
162 get_netcomputer_parser.add_argument('--attributes', nargs='+', dest='attributes',
163 default=[], help='Object attributes to return')
158164 get_netcomputer_parser.set_defaults(func=get_netcomputer)
159165
160166 # Parser for the get-netdomaincontroller command
434440 invoke_eventhunter_parser.set_defaults(func=invoke_eventhunter)
435441
436442 args = parser.parse_args()
437 if hasattr(args,'queried_groupname'):
438 args.queried_groupname = args.queried_groupname.encode('utf-8').decode('latin1')
439443 if args.hashes:
440444 try:
441445 args.lmhash, args.nthash = args.hashes.split(':')
452456
453457 parsed_args = dict()
454458 for k, v in vars(args).items():
455 if k not in ('func', 'hashes'):
459 if k not in ('func', 'hashes', 'submodule'):
456460 parsed_args[k] = v
457461
458462 #try:
464468 if results is not None:
465469 try:
466470 for x in results:
467 x = str(x).encode('latin1').decode('utf-8')
468 print(x)
469 if '\n' in x:
470 print('')
471 print(x)
472 # for example, invoke_checklocaladminaccess returns a bool
471473 except TypeError:
472474 print(results)
473475
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 import codecs
2018 from bs4 import BeautifulSoup
7270 property_name, property_values = [x.strip() for x in l.split('=')]
7371 if ',' in property_values:
7472 property_values = property_values.split(',')
75 setattr(getattr(gpttmpl_final, section_name), property_name, property_values)
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)
7678
7779 return gpttmpl_final
7880
133135 def _get_groupsxml(self, groupsxml_path, gpo_display_name):
134136 gpo_groups = list()
135137
136 content_io = StringIO()
138 content_io = BytesIO()
137139
138140 groupsxml_path_split = groupsxml_path.split('\\')
139141 gpo_name = groupsxml_path_split[6]
152154 except SessionError:
153155 return list()
154156
155 content = content_io.getvalue().replace('\r', '')
157 content = content_io.getvalue().replace(b'\r', b'')
156158 groupsxml_soup = BeautifulSoup(content, 'xml')
157159
158160 for group in groupsxml_soup.find_all('Group'):
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 import random
2018 import multiprocessing
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 from impacket.dcerpc.v5.rpcrt import DCERPCException
2018 from impacket.dcerpc.v5 import scmr, drsuapi
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 import socket
2018 import datetime
2422 from impacket.dcerpc.v5.rpcrt import DCERPCException
2523 from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY
2624 from bs4 import BeautifulSoup
25 from ldap3.utils.conv import escape_filter_chars
2726
2827 from pywerview.requester import LDAPRPCRequester
2928 import pywerview.objects.adobjects as adobj
4948 ads_path=str(), admin_count=False, spn=False,
5049 unconstrained=False, allow_delegation=False,
5150 preauth_notreq=False,
52 custom_filter=str()):
51 custom_filter=str(), attributes=[]):
5352
5453 if unconstrained:
5554 custom_filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
7069
7170 user_search_filter = '(&{})'.format(user_search_filter)
7271
73 return self._ldap_search(user_search_filter, adobj.User)
72 return self._ldap_search(user_search_filter, adobj.User, attributes=attributes)
7473
7574 @LDAPRPCRequester._ldap_connection_init
7675 def get_netgroup(self, queried_groupname='*', queried_sid=str(),
7776 queried_username=str(), queried_domain=str(),
7877 ads_path=str(), admin_count=False, full_data=False,
7978 custom_filter=str()):
79
80 # RFC 4515, section 3
81 # However if we escape *, we can no longer use wildcard within `--groupname`
82 # Maybe we can raise a warning here ?
83 if not '*' in queried_groupname:
84 queried_groupname = escape_filter_chars(queried_groupname)
8085
8186 if queried_username:
8287 results = list()
8388 sam_account_name_to_resolve = [queried_username]
8489 first_run = True
8590 while sam_account_name_to_resolve:
86 sam_account_name = sam_account_name_to_resolve.pop(0)
91 sam_account_name = escape_filter_chars(sam_account_name_to_resolve.pop(0))
8792 if first_run:
8893 first_run = False
8994 if admin_count:
135140 attributes=['samaccountname']
136141
137142 group_search_filter = '(&{})'.format(group_search_filter)
138
139143 return self._ldap_search(group_search_filter, adobj.Group, attributes=attributes)
140144
141145 @LDAPRPCRequester._ldap_connection_init
142146 def get_netcomputer(self, queried_computername='*', queried_spn=str(),
143147 queried_os=str(), queried_sp=str(), queried_domain=str(),
144148 ads_path=str(), printers=False, unconstrained=False,
145 ping=False, full_data=False, custom_filter=str()):
149 ping=False, full_data=False, custom_filter=str(), attributes=[]):
146150
147151 if unconstrained:
148152 custom_filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
160164 if full_data:
161165 attributes=list()
162166 else:
163 attributes=['dnsHostName']
167 if not attributes:
168 attributes=['dnsHostName']
164169
165170 computer_search_filter = '(&{})'.format(computer_search_filter)
166171
182187 if len(split_path) >= 3:
183188 return split_path[2]
184189
190 file_server_attributes = ['homedirectory', 'scriptpath', 'profilepath']
185191 results = set()
186192 if target_users:
187193 users = list()
188194 for target_user in target_users:
189 users += self.get_netuser(target_user, queried_domain)
195 users += self.get_netuser(target_user, queried_domain,
196 attributes=file_server_attributes)
190197 else:
191 users = self.get_netuser(queried_domain=queried_domain)
198 users = self.get_netuser(queried_domain=queried_domain,
199 attributes=file_server_attributes)
192200
193201 for user in users:
194202 for full_path in (user.homedirectory, user.scriptpath, user.profilepath):
219227 for remote_server in dfs.remoteservername:
220228 remote_server = str(remote_server)
221229 if '\\' in remote_server:
222 attributes = list()
223 attributes.append({'type': 'name', 'vals': [dfs.name]})
224 attributes.append({'type': 'remoteservername', 'vals': [remote_server.split('\\')[2]]})
230 attributes = {'name': [dfs.name.encode('utf-8')],
231 'remoteservername': [remote_server.split('\\')[2].encode('utf-8')]}
225232 results.append(adobj.DFS(attributes))
226233
227234 return results
242249 for target in soup_target_list.targets.contents:
243250 if '\\' in target.string:
244251 server_name, dfs_root = target.string.split('\\')[2:4]
245 attributes.append({'type': 'remoteservername',
246 'vals': [server_name]})
247 attributes.append({'type': 'name',
248 'vals': ['{}{}'.format(dfs_root, share_name)]})
252 attributes = {'name': ['{}{}'.format(dfs_root, share_name).encode('utf-8')],
253 'remoteservername': [server_name.encode('utf-8')]}
249254
250255 results.append(adobj.DFS(attributes))
251256
282287
283288 @LDAPRPCRequester._ldap_connection_init
284289 def get_netsite(self, queried_domain=str(), queried_sitename=str(),
285 queried_guid=str(), ads_path=str(), full_data=False):
290 queried_guid=str(), ads_path=str(), ads_prefix=str(),
291 full_data=False):
286292
287293 site_search_filter = '(objectCategory=site)'
288294
303309
304310 @LDAPRPCRequester._ldap_connection_init
305311 def get_netsubnet(self, queried_domain=str(), queried_sitename=str(),
306 ads_path=str(), full_data=False):
312 ads_path=str(), ads_prefix=str(), full_data=False):
307313
308314 subnet_search_filter = '(objectCategory=subnet)'
309315
329335
330336 def _get_members(_groupname=str(), _sid=str()):
331337 try:
338 # `--groupname` option is supplied
332339 if _groupname:
333340 groups = self.get_netgroup(queried_groupname=_groupname,
334341 queried_domain=queried_domain,
335342 full_data=True)
343
344 # `--groupname` option is missing, falling back to the "Domain Admins"
336345 else:
337346 if _sid:
338347 queried_sid = _sid
361370 # TODO: range cycling
362371 try:
363372 for member in group.member:
373 # RFC 4515, section 3
374 member = escape_filter_chars(member, encoding='utf-8')
364375 dn_filter = '(distinguishedname={}){}'.format(member, custom_filter)
365376 members += self.get_netuser(custom_filter=dn_filter, queried_domain=queried_domain)
366377 members += self.get_netgroup(custom_filter=dn_filter, queried_domain=queried_domain, full_data=True)
381392 member_domain = str()
382393 is_group = (member.samaccounttype != '805306368')
383394
384 attributes = list()
395 attributes = dict()
385396 if queried_domain:
386 attributes.append({'type': 'groupdomain', 'vals': [queried_domain]})
397 attributes['groupdomain'] = queried_domain
387398 else:
388 attributes.append({'type': 'groupdomain', 'vals': [self._domain]})
389 attributes.append({'type': 'groupname', 'vals': [group.name]})
390 attributes.append({'type': 'membername', 'vals': [member.samaccountname]})
391 attributes.append({'type': 'memberdomain', 'vals': [member_domain]})
392 attributes.append({'type': 'isgroup', 'vals': [is_group]})
393 attributes.append({'type': 'memberdn', 'vals': [member_dn]})
394 attributes.append({'type': 'membersid', 'vals': [member.objectsid]})
399 attributes['groupdomain'] = self._domain
400 attributes['groupname'] = group.name
401 attributes['membername'] = member.samaccountname
402 attributes['memberdomain'] = member_domain
403 attributes['isgroup'] = is_group
404 attributes['memberdn'] = member_dn
405 attributes['membersid'] = member.objectsid
395406
396407 final_member.add_attributes(attributes)
397408
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
18
19 from datetime import datetime
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 from datetime import datetime, timedelta
2018 import inspect
2119 import struct
2220 import pyasn1
5149
5250 def add_attributes(self, attributes):
5351 for attr in attributes:
54 t = str(attr['type']).lower()
52 #print(attr)
53 #print(attributes[attr], attr)
54 t = str(attr).lower()
5555 if t in ('logonhours', 'msds-generationid'):
56 value = str(attr['vals'][0])
57 value = [ord(x) for x in value]
56 value = bytes(attributes[attr][0])
57 value = [x for x in value]
5858 elif t in ('trustattributes', 'trustdirection', 'trusttype'):
59 value = int(attr['vals'][0])
59 value = int(attributes[attr][0])
6060 elif t in ('objectsid', 'ms-ds-creatorsid'):
61 value = codecs.encode(bytes(attr['vals'][0]),'hex')
62 init_value = bytes(attr['vals'][0])
63 value = 'S-1-5'
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])
6464 for i in range(8, len(init_value), 4):
6565 value += '-{}'.format(str(struct.unpack('<I', init_value[i:i+4])[0]))
6666 elif t == 'objectguid':
67 init_value = bytes(attr['vals'][0])
67 init_value = bytes(attributes[attr][0])
6868 value = str()
6969 value += '{}-'.format(hex(struct.unpack('<I', init_value[0:4])[0])[2:].zfill(8))
7070 value += '{}-'.format(hex(struct.unpack('<H', init_value[4:6])[0])[2:].zfill(4))
7171 value += '{}-'.format(hex(struct.unpack('<H', init_value[6:8])[0])[2:].zfill(4))
72 value += '{}-'.format(codecs.encode(init_value,'hex')[16:20])
72 value += '{}-'.format((codecs.encode(init_value,'hex')[16:20]).decode('utf-8'))
7373 value += init_value.hex()[20:]
7474 elif t in ('dscorepropagationdata', 'whenchanged', 'whencreated'):
7575 value = list()
76 for val in attr['vals']:
77 value.append(str(datetime.strptime(str(val), '%Y%m%d%H%M%S.0Z')))
78 elif t in ('pwdlastset', 'badpasswordtime', 'lastlogon', 'lastlogoff'):
79 timestamp = (int(str(attr['vals'][0])) - 116444736000000000)/10000000
80 value = datetime.fromtimestamp(timestamp)
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'):
79 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'
8188 elif t == 'isgroup':
82 value = attr['vals'][0]
89 value = attributes[attr]
8390 elif t == 'objectclass':
84 value = [str(x) for x in attr['vals']]
91 value = [x.decode('utf-8') for x in attributes[attr]]
8592 setattr(self, 'isgroup', ('group' in value))
86 elif len(attr['vals']) > 1:
87 value = [str(x) for x in attr['vals']]
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]
88100 else:
89101 try:
90 value = str(attr['vals'][0])
91 except (IndexError, pyasn1.error.PyAsn1Error):
102 value = attributes[attr][0].decode('utf-8')
103 except (IndexError):
92104 value = str()
105 except (UnicodeDecodeError):
106 value = attributes[attr][0]
93107
94108 setattr(self, t, value)
95109
104118 for member in members:
105119 if not member[0].startswith('_'):
106120 if member[0] == 'msmqdigests':
107 member_value = (',\n' + ' ' * (max_length + 2)).join(x.encode('utf-8').hex() for x in member[1])
121 member_value = (',\n' + ' ' * (max_length + 2)).join(x.hex() for x in member[1])
108122 elif member[0] == 'useraccountcontrol':
109123 member_value = list()
110124 for uac_flag, uac_label in ADObject.__uac_flags.items():
116130 elif member[0] in ('usercertificate',
117131 'protocom-sso-entries', 'protocom-sso-security-prefs',):
118132 member_value = (',\n' + ' ' * (max_length + 2)).join(
119 '{}...'.format(x.encode('utf-8').hex()[:100]) for x in member[1])
133 '{}...'.format(x.hex()[:100]) for x in member[1])
120134 else:
121135 member_value = (',\n' + ' ' * (max_length + 2)).join(str(x) for x in member[1])
122136 elif member[0] in('msmqsigncertificates', 'userparameters',
125139 'msrtcsip-userroutinggroupid', 'msexchumpinchecksum',
126140 'protocom-sso-auth-data', 'protocom-sso-entries-checksum',
127141 'protocom-sso-security-prefs-checksum', ):
128 member_value = '{}...'.format(member[1].encode('utf-8').hex()[:100])
142 # Attribut exists but it is empty
143 try:
144 member_value = '{}...'.format(member[1].hex()[:100])
145 except AttributeError:
146 member_value = ''
129147 else:
130148 member_value = member[1]
131149 s += '{}: {}{}\n'.format(member[0], ' ' * (max_length - len(member[0])), member_value)
139157 class User(ADObject):
140158 def __init__(self, attributes):
141159 ADObject.__init__(self, attributes)
142 for attr in ('homedirectory', 'scriptpath', 'profilepath'):
160 for attr in filter(lambda _: _ in attributes, ('homedirectory',
161 'scriptpath',
162 'profilepath')):
143163 if not hasattr(self, attr):
144164 setattr(self, attr, str())
145165
192212 ad_obj = ADObject(attributes)
193213 self.targetname = ad_obj.name
194214
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
195220 self.trustattributes = list()
196221 for attrib_flag, attrib_label in Trust.__trust_attrib.items():
197222 if ad_obj.trustattributes & attrib_flag:
198223 self.trustattributes.append(attrib_label)
199224
200 self.trustdirection = Trust.__trust_direction.get(ad_obj.trustdirection, 'unknown')
201 self.trusttype = Trust.__trust_type.get(ad_obj.trusttype, 'unknown')
202 self.whencreated = ad_obj.whencreated
203 self.whenchanged = ad_obj.whenchanged
225 # If the filter SIDs attribute is not manually set, we check if we're
226 # not in a use case where SIDs are implicitly filtered
227 # 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')
204235
205236 class GPO(ADObject):
206237 pass
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 from __future__ import unicode_literals
2018
3735 'wkui1_oth_domains', 'wkui1_username',
3836 'sesi10_cname', 'sesi10_username'):
3937 value = value.rstrip('\x00')
40 if isinstance(value, str):
41 try:
42 value = value
43 except UnicodeDecodeError:
44 pass
45
38
4639 setattr(self, key.lower(), value)
4740
4841 def __str__(self):
5750 if not member[0].startswith('_'):
5851 s += '{}: {}{}\n'.format(member[0], ' ' * (max_length - len(member[0])), member[1])
5952
60 s = s[:-1].encode('utf-8')
53 s = s[:-1]
6154 return s
6255
6356 def __repr__(self):
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 import socket
2018 import ntpath
21 from impacket.ldap import ldap, ldapasn1
19 import ldap3
20
2221 from impacket.smbconnection import SMBConnection
2322 from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
2423 from impacket.dcerpc.v5 import transport, wkst, srvs, samr, scmr, drsuapi, epm
4039 self._ads_path = None
4140 self._ads_prefix = None
4241 self._ldap_connection = None
42 self._base_dn = None
4343
4444 def _get_netfqdn(self):
4545 try:
7878 else:
7979 base_dn += ','.join('dc={}'.format(x) for x in self._queried_domain.split('.'))
8080
81 try:
82 ldap_connection = ldap.LDAPConnection('ldap://{}'.format(self._domain_controller),
83 base_dn, self._domain_controller)
84 ldap_connection.login(self._user, self._password, self._domain,
85 self._lmhash, self._nthash)
86 except ldap.LDAPSessionError as e:
87 if str(e).find('strongerAuthRequired') >= 0:
88 # We need to try SSL
89 ldap_connection = ldap.LDAPConnection('ldaps://{}'.format(self._domain_controller),
90 base_dn, self._domain_controller)
91 ldap_connection.login(self._user, self._password, self._domain,
92 self._lmhash, self._nthash)
93 else:
94 raise e
95 except socket.error as e:
96 return
81 # base_dn is no longer used within `_create_ldap_connection()`, but I don't want to break
82 # the function call. So we store it in an attriute and use it in `_ldap_search()`
83 self._base_dn = base_dn
84
85 # Format the username and the domain
86 # ldap3 seems not compatible with USER@DOMAIN format
87 user = '{}\\{}'.format(self._domain, self._user)
88
89 # Choose between password or pth
90 if self._lmhash and self._nthash:
91 lm_nt_hash = '{}:{}'.format(self._lmhash, self._nthash)
92
93 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller))
94 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
95 authentication=ldap3.NTLM, raise_exceptions=True)
96
97 try:
98 ldap_connection.bind()
99 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
100 # We need to try SSL (pth version)
101 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller))
102 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
103 authentication=ldap3.NTLM, raise_exceptions=True)
104
105 ldap_connection.bind()
106
107 else:
108 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller))
109 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
110 authentication=ldap3.NTLM, raise_exceptions=True)
111
112 try:
113 ldap_connection.bind()
114 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
115 # We nedd to try SSL (password version)
116 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller))
117 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
118 authentication=ldap3.NTLM, raise_exceptions=True)
119
120 ldap_connection.bind()
97121
98122 self._ldap_connection = ldap_connection
99123
100124 def _ldap_search(self, search_filter, class_result, attributes=list()):
101125 results = list()
102 paged_search_control = ldapasn1.SimplePagedResultsControl(criticality=True,
103 size=1000)
104 try:
105 search_results = self._ldap_connection.search(searchFilter=search_filter,
106 searchControls=[paged_search_control],
107 attributes=attributes)
108 except ldap.LDAPSearchError as e:
109 # If we got a "size exceeded" error, we get the partial results
110 if e.error == 4:
111 search_results = e.answers
112 else:
113 raise e
114 # TODO: Filter parenthesis in LDAP filter
115 except ldap.LDAPFilterSyntaxError as e:
116 return list()
117
126
127 # if no attribute name specified, we return all attributes
128 if not attributes:
129 attributes = ldap3.ALL_ATTRIBUTES
130
131 try:
132 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
133 search_results=self._ldap_connection.extend.standard.paged_search(search_base=self._base_dn,
134 search_filter=search_filter, attributes=attributes,
135 paged_size=1000, generator=True)
136 # TODO: for debug only
137 except Exception as e:
138 import sys
139 print('Except: ', sys.exc_info()[0])
140
141 # Skip searchResRef
118142 for result in search_results:
119 if not isinstance(result, ldapasn1.SearchResultEntry):
143 if result['type'] is not 'searchResEntry':
120144 continue
121
122 results.append(class_result(result['attributes']))
145 results.append(class_result(result['raw_attributes']))
123146
124147 return results
125148
135158 (ads_path != instance._ads_path) or \
136159 (ads_prefix != instance._ads_prefix):
137160 if instance._ldap_connection:
138 instance._ldap_connection.close()
161 instance._ldap_connection.unbind()
139162 instance._create_ldap_connection(queried_domain=queried_domain,
140163 ads_path=ads_path, ads_prefix=ads_prefix)
141164 return f(*args, **kwargs)
147170
148171 def __exit__(self, type, value, traceback):
149172 try:
150 self._ldap_connection.close()
173 self._ldap_connection.unbind()
151174 except AttributeError:
152175 pass
153176 self._ldap_connection = None
0 # -*- coding: utf8 -*-
1
20 # This file is part of PywerView.
31
42 # PywerView is free software: you can redistribute it and/or modify
1412 # You should have received a copy of the GNU General Public License
1513 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1614
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1816
1917 from multiprocessing import Process, Pipe
2018
0 #!/usr/bin/env python
1 # -*- coding: utf8 -*-
0 #!/usr/bin/env python3
21 #
32 # This file is part of PywerView.
43
1514 # You should have received a copy of the GNU General Public License
1615 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1716
18 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2016
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
1918
2019 from pywerview.cli.main import main
2120
0 impacket>=0.9.16
0 impacket>=0.9.22
11 bs4
2 lxml
0 # -*- coding: utf8 -*-
0 #!/usr/bin/env python3
11
22 from setuptools import setup, find_packages
33
44 try:
55 import pypandoc
6 long_description = pypandoc.convert('README.md', 'rst')
6 long_description = pypandoc.convert_file('README.md', 'rst')
77 except(IOError, ImportError):
88 long_description = open('README.md').read()
99
1010 setup(name='pywerview',
11 version='0.2.0',
11 version='0.3.2',
1212 description='A Python port of PowerSploit\'s PowerView',
1313 long_description=long_description,
14 dependency_links = ['https://github.com/CoreSecurity/impacket/tarball/master#egg=impacket-0.9.16dev'],
14 dependency_links = ['https://github.com/SecureAuthCorp/impacket/tarball/master#egg=impacket-0.9.22'],
1515 classifiers=[
1616 'Environment :: Console',
1717 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
18 'Programming Language :: Python :: 2.7',
18 'Programming Language :: Python :: 3.6',
1919 'Topic :: Security',
2020 ],
2121 keywords='python powersploit pentesting recon active directory windows',
2727 "pywerview", "pywerview.*"
2828 ]),
2929 install_requires=[
30 'impacket>=0.9.16dev',
31 'pyasn1',
32 'pycrypto',
33 'pyopenssl',
34 'bs4'
30 'impacket>=0.9.22',
31 'bs4',
32 'lxml'
3533 ],
3634 entry_points = {
3735 'console_scripts': ['pywerview=pywerview.cli.main:main'],