Codebase list pywerview / 0551010
New upstream release. Kali Janitor 1 year, 5 months ago
18 changed file(s) with 1437 addition(s) and 667 deletion(s). Raw diff Collapse all Expand all
6060 * Python 3.6
6161 * impacket >= 0.9.22
6262 * ldap3 >= 2.8.1
63 * gssapi (Which requires `libkrb5-dev`)
6364
6465 ## FUNCTIONALITIES
6566
6869
6970 Here's the list of available commands:
7071
71 $ ./pywerview.py --help
72 $ pywerview.py --help
7273 usage: pywerview.py [-h]
73 {get-adobject,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netgpo,get-domainpolicy,get-gpttmpl,get-netgpogroup,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
74 {get-adobject,get-adserviceaccount,get-objectacl,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netdomaintrust,get-netgpo,get-netpso,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
7475 ...
7576
7677 Rewriting of some PowerView's functionalities in Python
8182 Subcommands:
8283 Available subcommands
8384
84 {get-adobject,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netgpo,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
85 get-adobject Takes a domain SID, samAccountName or name, and return
86 the associated object
85 {get-adobject,get-adserviceaccount,get-objectacl,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netdomaintrust,get-netgpo,get-netpso,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
86 get-adobject Takes a domain SID, samAccountName or name, and return the associated object
87 get-adserviceaccount
88 Returns a list of all the gMSA of the specified domain (you need privileged account to retrieve passwords)
89 get-objectacl Takes a domain SID, samAccountName or name, and return the ACL of the associated object
8790 get-netuser Queries information about a domain user
88 get-netgroup Get a list of all current domain groups, or a list of
89 groups a domain user is member of
91 get-netgroup Get a list of all current domain groups, or a list of groups a domain user is member of
9092 get-netcomputer Queries informations about domain computers
9193 get-netdomaincontroller
9294 Get a list of domain controllers for the given domain
93 get-netfileserver Return a list of file servers, extracted from the
94 domain users' homeDirectory, scriptPath, and
95 profilePath fields
96 get-dfsshare Return a list of all fault tolerant distributed file
97 systems for a given domain
95 get-netfileserver Return a list of file servers, extracted from the domain users' homeDirectory, scriptPath, and profilePath fields
96 get-dfsshare Return a list of all fault tolerant distributed file systems for a given domain
9897 get-netou Get a list of all current OUs in the domain
9998 get-netsite Get a list of all current sites in the domain
10099 get-netsubnet Get a list of all current subnets in the domain
100 get-netdomaintrust Returns a list of all the trusts of the specified domain
101101 get-netgpo Get a list of all current GPOs in the domain
102 get-domainpolicy Returns the default domain or DC policy for the
103 queried domain or DC
104 get-gpttmpl Helper to parse a GptTmpl.inf policy file path into a
105 custom object
106 get-netgpogroup Parses all GPOs in the domain that set "Restricted
107 Group" or "Groups.xml"
102 get-netpso Get a list of all current PSOs in the domain
103 get-domainpolicy Returns the default domain or DC policy for the queried domain or DC
104 get-gpttmpl Helper to parse a GptTmpl.inf policy file path into a custom object
105 get-netgpogroup Parses all GPOs in the domain that set "Restricted Group" or "Groups.xml"
108106 find-gpocomputeradmin
109 Takes a computer (or OU) and determine who has
110 administrative access to it via GPO
111 find-gpolocation Takes a username or a group name and determine the
112 computers it has administrative access to via GPO
107 Takes a computer (or OU) and determine who has administrative access to it via GPO
108 find-gpolocation Takes a username or a group name and determine the computers it has administrative access to via GPO
113109 get-netgroupmember Return a list of members of a domain group
114 get-netsession Queries a host to return a list of active sessions on
115 the host (you can use local credentials instead of
116 domain credentials)
117 get-localdisks Queries a host to return a list of active disks on the
118 host (you can use local credentials instead of domain
119 credentials)
110 get-netsession Queries a host to return a list of active sessions on the host (you can use local credentials instead of domain credentials)
111 get-localdisks Queries a host to return a list of active disks on the host (you can use local credentials instead of domain credentials)
120112 get-netdomain Queries a host for available domains
121 get-netshare Queries a host to return a list of available shares on
122 the host (you can use local credentials instead of
123 domain credentials)
124 get-netloggedon This function will execute the NetWkstaUserEnum RPC
125 call to query a given host for actively logged on
126 users
127 get-netlocalgroup Gets a list of members of a local group on a machine,
128 or returns every local group. You can use local
129 credentials instead of domain credentials, however,
130 domain credentials are needed to resolve domain SIDs.
113 get-netshare Queries a host to return a list of available shares on the host (you can use local credentials instead of domain credentials)
114 get-netloggedon This function will execute the NetWkstaUserEnum RPC call to query a given host for actively logged on users
115 get-netlocalgroup Gets a list of members of a local group on a machine, or returns every local group. You can use local credentials instead of domain credentials, however, domain credentials are needed
116 to resolve domain SIDs.
131117 invoke-checklocaladminaccess
132 Checks if the given user has local admin access on the
133 given host
134 get-netprocess This function will execute the 'Select * from
135 Win32_Process' WMI query to a given host for a list of
136 executed process
137 get-userevent This function will execute the 'Select * from
138 Win32_Process' WMI query to a given host for a list of
139 executed process
118 Checks if the given user has local admin access on the given host
119 get-netprocess This function will execute the 'Select * from Win32_Process' WMI query to a given host for a list of executed process
120 get-userevent This function will execute the 'SELECT * from Win32_NTLogEvent' WMI query to a given host for a list of executed process
140121 invoke-userhunter Finds which machines domain users are logged into
141122 invoke-processhunter
142 Searches machines for processes with specific name, or
143 ran by specific users
144 invoke-eventhunter Searches machines for events with specific name, or
145 ran by specific users
123 Searches machines for processes with specific name, or ran by specific users
124 invoke-eventhunter Searches machines for events with specific name, or ran by specific users
146125
147126 Take a look at the [wiki](https://github.com/the-useless-one/pywerview/wiki) to
148127 see a more detailed usage of every command.
154133 is `USELESSDOMAIN`. In every command, I must use __`uselessdomain.local`__ as
155134 an argument, and __not__ `USELESSDOMAIN`.
156135
136 ## GLOBAL ARGUMENTS
137
138 ### LOGGING
139
140 You can provide a logging level to `pywerview` modules by using `-l` or `--logging-level` options. Supported levels are:
141
142 * `CRITICAL`: Only critical errors are displayed **(default)**
143 * `WARNING` Warnings are displayed, along with citical errors
144 * `DEBUG`: Debug level (caution: **very** verbose)
145 * `ULTRA`: Extreme debugging level (caution: **very very** verbose)
146
147 (level names are case insensitive)
148
149 ### Kerberos authentication
150
151 Kerberos authentication is now (partially) supported, which means you can
152 pass the ticket and other stuff. To authenticate via Kerberos:
153
154 1. Point the `KRB5CCNAME` environment variable to your cache credential file.
155 2. Use the `-k` option in your function call, or the `do_kerberos` in your
156 library call.
157
158 ```console
159 $ klist stormtroopers.ccache
160 Ticket cache: FILE:stormtroopers.ccache
161 Default principal: [email protected]
162
163 Valid starting Expires Service principal
164 10/03/2022 16:46:45 11/03/2022 02:46:45 ldap/[email protected]
165 renew until 11/03/2022 16:43:17
166 $ KRB5CCNAME=stormtroopers.ccache python3 pywerview.py get-netcomputer -t srv-ad.contoso.com -u stormtroopers -k
167 dnshostname: centos.contoso.com
168
169 dnshostname: debian.contoso.com
170
171 dnshostname: Windows7.contoso.com
172
173 dnshostname: Windows10.contoso.com
174
175 dnshostname: SRV-MAIL.contoso.com
176
177 dnshostname: SRV-AD.contoso.com
178 ```
179
180 If your cache credential file contains a corresponding TGS, or a TGT for your
181 calling user, Kerberos authentication will be used.
182
183 __SPN patching is partial__. Right now, we're in a mixed configuration where we
184 use `ldap3` for LDAP commands and `impacket` for the other protocols (SMB,
185 RPC). That is because `impacket`'s LDAP implementation has several problems,
186 such as mismanagement of non-ASCII characters (which is problematic for us
187 baguette-eaters).
188
189 `ldap3` uses `gssapi` to authenticate with Kerberos, and `gssapi` needs the
190 full hostname in the SPN of a ticket, otherwise it throws an error. It would
191 be possible to patch an SPN with an incomplete hostname, however it's not done
192 for now.
193
194 For any functions that only rely on `impacket` (SMB or RPC functions), you can
195 use tickets with SPNs with an incomplete hostname. In the following example, we
196 use an LDAP ticket with an incomplete hostname for an SMB function, without any
197 trouble. You just have to make sure that the `--computername` argument matches
198 this incomplete hostname in the SPN:
199
200 ```console
201 $ klist skywalker.ccache
202 Ticket cache: FILE:skywalker.ccache
203 Default principal: [email protected]
204
205 Valid starting Expires Service principal
206 13/04/2022 14:26:59 14/04/2022 00:26:58 ldap/[email protected]
207 renew until 14/04/2022 14:23:29
208 $ KRB5CCNAME=skywalker.ccache python3 pywerview.py get-localdisks --computername srv-ad -u skywalker -k
209 disk: A:
210
211 disk: C:
212
213 disk: D:
214 ```
215
216 To recap:
217
218 | SPN in the ticket | Can be used with LDAP functions | Can be used with SMB/RPC functions |
219 | :-----------------------------------: | :-----------------------------: | :--------------------------------: |
220 | `ldap/[email protected]` | ✔️ | ✔️ |
221 | `cifs/[email protected]` | ✔️ | ✔️ |
222 | `ldap/[email protected]` | ❌ | ✔️ |
223
224 ### TLS CONNECTION
225
226 You can force a connection to the LDAPS port by using the `--tls` switch. It
227 can be necessary with some functions, for example when retrieving gMSA
228 passwords with `get-adserviceaccount`:
229
230 ```console
231 $ python3 pywerview.py get-adserviceaccount -t srv-ad.contoso.com -u 'SRV-MAIL$' --hashes $NT_HASH --resolve-sids
232 distinguishedname: CN=gMSA-01,CN=Managed Service Accounts,DC=contoso,DC=com
233 objectsid: S-1-5-21-863927164-4106933278-53377030-3115
234 samaccountname: gMSA-01$
235 msds-groupmsamembership: CN=SRV-MAIL,CN=Computers,DC=contoso,DC=com
236 description:
237 enabled: True
238 $ python3 pywerview.py get-adserviceaccount -t srv-ad.contoso.com -u 'SRV-MAIL$' --hashes $NT_HASH --resolve-sids --tls
239 distinguishedname: CN=gMSA-01,CN=Managed Service Accounts,DC=contoso,DC=com
240 objectsid: S-1-5-21-863927164-4106933278-53377030-3115
241 samaccountname: gMSA-01$
242 msds-managedpassword: 69730ce3914ac6[redacted]
243 msds-groupmsamembership: CN=SRV-MAIL,CN=Computers,DC=contoso,DC=com
244 description:
245 enabled: True
246 ```
247
248 ### JSON OUTPUT
249
250 Pywerview can print results in json format by using the `--json` switch.
251
157252 ## TODO
158253
159254 * Many, many more PowerView functionalities to implement. I'll now focus on
160255 forest functions, then inter-forest trust functions
161256 * Lots of rewrite due to the last version of PowerView
162 * Implement a debugging mode (for easier troubleshooting)
163257 * Gracefully fail against Unix machines running Samba
164 * Support Kerberos authentication
165258 * Perform range cycling in `get-netgroupmember`
166259 * Manage request to the Global Catalog
167260 * Try to fall back to `tcp/139` for RPC communications if `tcp/445` is closed
185278
186279 PywerView - A Python rewriting of PowerSploit's PowerView
187280
188 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
281 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
189282
190283 This program is free software: you can redistribute it and/or modify it
191284 under the terms of the GNU General Public License as published by the
0 pywerview (0.4.0-0kali1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Kali Janitor <[email protected]> Tue, 22 Nov 2022 00:14:11 -0000
5
06 pywerview (0.3.2-0kali1) kali-dev; urgency=medium
17
28 [ Ben Wilson ]
0 import logging
1 logging.getLogger().setLevel(100)
2
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1818
1919 from pywerview.functions.net import NetRequester
2020 from pywerview.functions.gpo import GPORequester
2222 from pywerview.functions.hunting import UserHunter, ProcessHunter, EventHunter
2323
2424 def get_adobject(domain_controller, domain, user, password=str(),
25 lmhash=str(), nthash=str(), queried_domain=str(), queried_sid=str(),
26 queried_name=str(), queried_sam_account_name=str(), ads_path=str(),
25 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
26 queried_domain=str(), queried_sid=str(), queried_name=str(),
27 queried_sam_account_name=str(), ads_path=str(), attributes=list(),
2728 custom_filter=str()):
2829 requester = NetRequester(domain_controller, domain, user, password,
29 lmhash, nthash)
30 lmhash, nthash, do_kerberos, do_tls)
3031 return requester.get_adobject(queried_domain=queried_domain,
3132 queried_sid=queried_sid, queried_name=queried_name,
3233 queried_sam_account_name=queried_sam_account_name,
33 ads_path=ads_path, custom_filter=custom_filter)
34 ads_path=ads_path, attributes=attributes, custom_filter=custom_filter)
35
36 def get_adserviceaccount(domain_controller, domain, user, password=str(),
37 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
38 queried_domain=str(), queried_sid=str(), queried_name=str(),
39 queried_sam_account_name=str(), ads_path=str(), resolve_sids=False):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash, do_kerberos, do_tls)
42 return requester.get_adserviceaccount(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, resolve_sids=resolve_sids)
46
47 def get_objectacl(domain_controller, domain, user, password=str(),
48 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
49 queried_domain=str(), queried_sid=str(), queried_name=str(),
50 queried_sam_account_name=str(), ads_path=str(), sacl=False,
51 rights_filter=str(), resolve_sids=False, resolve_guids=False,
52 custom_filter=str()):
53 requester = NetRequester(domain_controller, domain, user, password,
54 lmhash, nthash, do_kerberos, do_tls)
55 return requester.get_objectacl(queried_domain=queried_domain,
56 queried_sid=queried_sid, queried_name=queried_name,
57 queried_sam_account_name=queried_sam_account_name,
58 ads_path=ads_path, sacl=sacl, rights_filter=rights_filter,
59 resolve_sids=resolve_sids, resolve_guids=resolve_guids,
60 custom_filter=custom_filter)
3461
3562 def get_netuser(domain_controller, domain, user, password=str(), lmhash=str(),
36 nthash=str(), queried_username=str(), queried_domain=str(), ads_path=str(),
37 admin_count=False, spn=False, unconstrained=False, allow_delegation=False,
38 preauth_notreq=False, custom_filter=str(),
39 attributes=[]):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash)
63 nthash=str(), do_kerberos=False, do_tls=False, queried_username=str(),
64 queried_domain=str(), ads_path=str(), admin_count=False, spn=False,
65 unconstrained=False, allow_delegation=False, preauth_notreq=False,
66 custom_filter=str(), attributes=[]):
67 requester = NetRequester(domain_controller, domain, user, password,
68 lmhash, nthash, do_kerberos, do_tls)
4269 return requester.get_netuser(queried_username=queried_username,
4370 queried_domain=queried_domain, ads_path=ads_path, admin_count=admin_count,
4471 spn=spn, unconstrained=unconstrained, allow_delegation=allow_delegation,
4673 attributes=attributes)
4774
4875 def get_netgroup(domain_controller, domain, user, password=str(),
49 lmhash=str(), nthash=str(), queried_groupname='*', queried_sid=str(),
50 queried_username=str(), queried_domain=str(), ads_path=str(),
51 admin_count=False, full_data=False, custom_filter=str()):
52 requester = NetRequester(domain_controller, domain, user, password,
53 lmhash, nthash)
76 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
77 queried_groupname='*', queried_sid=str(), queried_username=str(),
78 queried_domain=str(), ads_path=str(), admin_count=False,
79 full_data=False, custom_filter=str()):
80 requester = NetRequester(domain_controller, domain, user, password,
81 lmhash, nthash, do_kerberos, do_tls)
5482 return requester.get_netgroup(queried_groupname=queried_groupname,
5583 queried_sid=queried_sid, queried_username=queried_username,
5684 queried_domain=queried_domain, ads_path=ads_path, admin_count=admin_count,
5785 full_data=full_data, custom_filter=custom_filter)
5886
5987 def get_netcomputer(domain_controller, domain, user, password=str(),
60 lmhash=str(), nthash=str(), queried_computername='*', queried_spn=str(),
61 queried_os=str(), queried_sp=str(), queried_domain=str(), ads_path=str(),
88 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
89 queried_computername='*', queried_spn=str(), queried_os=str(),
90 queried_sp=str(), queried_domain=str(), ads_path=str(),
6291 printers=False, unconstrained=False, ping=False, full_data=False,
6392 custom_filter=str(), attributes=[]):
6493 requester = NetRequester(domain_controller, domain, user, password,
65 lmhash, nthash)
94 lmhash, nthash, do_kerberos, do_tls)
6695 return requester.get_netcomputer(queried_computername=queried_computername,
6796 queried_spn=queried_spn, queried_os=queried_os, queried_sp=queried_sp,
6897 queried_domain=queried_domain, ads_path=ads_path, printers=printers,
7099 custom_filter=custom_filter, attributes=attributes)
71100
72101 def get_netdomaincontroller(domain_controller, domain, user, password=str(),
73 lmhash=str(), nthash=str(), queried_domain=str()):
74 requester = NetRequester(domain_controller, domain, user, password,
75 lmhash, nthash)
102 lmhash=str(), nthash=str(), do_kerberos=False,
103 do_tls=False, queried_domain=str()):
104 requester = NetRequester(domain_controller, domain, user, password,
105 lmhash, nthash, do_kerberos, do_tls)
76106 return requester.get_netdomaincontroller(queried_domain=queried_domain)
77107
78108 def get_netfileserver(domain_controller, domain, user, password=str(),
79 lmhash=str(), nthash=str(), queried_domain=str(), target_users=list()):
80 requester = NetRequester(domain_controller, domain, user, password,
81 lmhash, nthash)
109 lmhash=str(), nthash=str(), do_kerberos=False,
110 do_tls=False, queried_domain=str(), target_users=list()):
111 requester = NetRequester(domain_controller, domain, user, password,
112 lmhash, nthash, do_kerberos, do_tls)
82113 return requester.get_netfileserver(queried_domain=queried_domain,
83114 target_users=target_users)
84115
85116 def get_dfsshare(domain_controller, domain, user, password=str(),
86 lmhash=str(), nthash=str(), version=['v1', 'v2'], queried_domain=str(),
87 ads_path=str()):
88 requester = NetRequester(domain_controller, domain, user, password,
89 lmhash, nthash)
117 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
118 version=['v1', 'v2'], queried_domain=str(), ads_path=str()):
119 requester = NetRequester(domain_controller, domain, user, password,
120 lmhash, nthash, do_kerberos, do_tls)
90121 return requester.get_dfsshare(version=version, queried_domain=queried_domain, ads_path=ads_path)
91122
92123 def get_netou(domain_controller, domain, user, password=str(), lmhash=str(),
93 nthash=str(), queried_domain=str(), queried_ouname='*', queried_guid=str(),
94 ads_path=str(), full_data=False):
95 requester = NetRequester(domain_controller, domain, user, password,
96 lmhash, nthash)
124 nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str(),
125 queried_ouname='*', queried_guid=str(), ads_path=str(), full_data=False):
126 requester = NetRequester(domain_controller, domain, user, password,
127 lmhash, nthash, do_kerberos, do_tls)
97128 return requester.get_netou(queried_domain=queried_domain,
98129 queried_ouname=queried_ouname, queried_guid=queried_guid, ads_path=ads_path,
99130 full_data=full_data)
100131
101132 def get_netsite(domain_controller, domain, user, password=str(), lmhash=str(),
102 nthash=str(), queried_domain=str(), queried_sitename=str(),
103 queried_guid=str(), ads_path=str(), ads_prefix='CN=Sites,CN=Configuration',
104 full_data=False):
105 requester = NetRequester(domain_controller, domain, user, password,
106 lmhash, nthash)
133 nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str(),
134 queried_sitename=str(), queried_guid=str(), ads_path=str(),
135 ads_prefix='CN=Sites,CN=Configuration', full_data=False):
136 requester = NetRequester(domain_controller, domain, user, password,
137 lmhash, nthash, do_kerberos, do_tls)
107138 return requester.get_netsite(queried_domain=queried_domain,
108139 queried_sitename=queried_sitename, queried_guid=queried_guid,
109140 ads_path=ads_path, ads_prefix=ads_prefix, full_data=full_data)
110141
111142 def get_netsubnet(domain_controller, domain, user, password=str(),
112 lmhash=str(), nthash=str(), queried_domain=str(), queried_sitename=str(),
113 ads_path=str(), ads_prefix='CN=Sites,CN=Configuration', full_data=False):
114 requester = NetRequester(domain_controller, domain, user, password,
115 lmhash, nthash)
143 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
144 queried_domain=str(), queried_sitename=str(), ads_path=str(),
145 ads_prefix='CN=Sites,CN=Configuration', full_data=False):
146 requester = NetRequester(domain_controller, domain, user, password,
147 lmhash, nthash, do_kerberos, do_tls)
116148 return requester.get_netsubnet(queried_domain=queried_domain,
117149 queried_sitename=queried_sitename, ads_path=ads_path, ads_prefix=ads_prefix,
118150 full_data=full_data)
119151
120152 def get_netdomaintrust(domain_controller, domain, user, password=str(),
121 lmhash=str(), nthash=str(), queried_domain=str()):
122 requester = NetRequester(domain_controller, domain, user, password,
123 lmhash, nthash)
153 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str()):
154 requester = NetRequester(domain_controller, domain, user, password,
155 lmhash, nthash, do_kerberos, do_tls)
124156 return requester.get_netdomaintrust(queried_domain=queried_domain)
125157
126158 def get_netgroupmember(domain_controller, domain, user, password=str(),
127 lmhash=str(), nthash=str(), queried_groupname=str(), queried_sid=str(),
128 queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False,
159 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
160 queried_groupname=str(), queried_sid=str(), queried_domain=str(),
161 ads_path=str(), recurse=False, use_matching_rule=False,
129162 full_data=False, custom_filter=str()):
130163 requester = NetRequester(domain_controller, domain, user, password,
131 lmhash, nthash)
164 lmhash, nthash, do_kerberos, do_tls)
132165 return requester.get_netgroupmember(queried_groupname=queried_groupname,
133166 queried_sid=queried_sid, queried_domain=queried_domain,
134167 ads_path=ads_path, recurse=recurse,
136169 full_data=full_data, custom_filter=custom_filter)
137170
138171 def get_netsession(target_computername, domain, user, password=str(),
139 lmhash=str(), nthash=str()):
140 requester = NetRequester(target_computername, domain, user, password,
141 lmhash, nthash)
172 lmhash=str(), nthash=str(), do_kerberos=False):
173 requester = NetRequester(target_computername, domain, user, password,
174 lmhash, nthash, do_kerberos)
142175 return requester.get_netsession()
143176
144177 def get_netshare(target_computername, domain, user, password=str(),
145 lmhash=str(), nthash=str()):
146 requester = NetRequester(target_computername, domain, user, password,
147 lmhash, nthash)
178 lmhash=str(), nthash=str(), do_kerberos=False):
179 requester = NetRequester(target_computername, domain, user, password,
180 lmhash, nthash, do_kerberos)
148181 return requester.get_netshare()
149182
150183 def get_localdisks(target_computername, domain, user, password=str(),
151 lmhash=str(), nthash=str()):
152 requester = NetRequester(target_computername, domain, user, password,
153 lmhash, nthash)
184 lmhash=str(), nthash=str(), do_kerberos=False):
185 requester = NetRequester(target_computername, domain, user, password,
186 lmhash, nthash, do_kerberos)
154187 return requester.get_localdisks()
155188
156189 def get_netdomain(domain_controller, domain, user, password=str(),
157 lmhash=str(), nthash=str()):
158 requester = NetRequester(domain_controller, domain, user, password,
159 lmhash, nthash)
190 lmhash=str(), nthash=str(), do_kerberos=False,
191 do_tls=False):
192 requester = NetRequester(domain_controller, domain, user, password,
193 lmhash, nthash, do_kerberos, do_tls)
160194 return requester.get_netdomain()
161195
162196 def get_netloggedon(target_computername, domain, user, password=str(),
163 lmhash=str(), nthash=str()):
164 requester = NetRequester(target_computername, domain, user, password,
165 lmhash, nthash)
197 lmhash=str(), nthash=str(), do_kerberos=False):
198 requester = NetRequester(target_computername, domain, user, password,
199 lmhash, nthash, do_kerberos)
166200 return requester.get_netloggedon()
167201
168202 def get_netlocalgroup(target_computername, domain_controller, domain, user,
169 password=str(), lmhash=str(), nthash=str(), queried_groupname=str(),
170 list_groups=False, recurse=False):
171 requester = NetRequester(target_computername, domain, user, password,
172 lmhash, nthash, domain_controller)
203 password=str(), lmhash=str(), nthash=str(), do_kerberos=False,
204 do_tls=False, queried_groupname=str(), list_groups=False,
205 recurse=False):
206 requester = NetRequester(target_computername, domain, user, password,
207 lmhash, nthash, do_kerberos, do_tls, domain_controller)
173208 return requester.get_netlocalgroup(queried_groupname=queried_groupname,
174209 list_groups=list_groups, recurse=recurse)
175210
176211 def get_netprocess(target_computername, domain, user, password=str(),
177 lmhash=str(), nthash=str()):
178 requester = NetRequester(target_computername, domain, user, password,
179 lmhash, nthash)
212 lmhash=str(), nthash=str(), do_kerberos=False):
213 requester = NetRequester(target_computername, domain, user, password,
214 lmhash, nthash, do_kerberos)
180215 return requester.get_netprocess()
181216
182217 def get_userevent(target_computername, domain, user, password=str(),
183 lmhash=str(), nthash=str(), event_type=['logon', 'tgt'],
184 date_start=5):
185 requester = NetRequester(target_computername, domain, user, password,
186 lmhash, nthash)
218 lmhash=str(), nthash=str(), do_kerberos=False,
219 event_type=['logon', 'tgt'], date_start=5):
220 requester = NetRequester(target_computername, domain, user, password,
221 lmhash, nthash, do_kerberos)
187222 return requester.get_userevent(event_type=event_type,
188223 date_start=date_start)
189224
190225 def get_netgpo(domain_controller, domain, user, password=str(),
191 lmhash=str(), nthash=str(), queried_gponame='*',
192 queried_displayname=str(), queried_domain=str(), ads_path=str()):
193 requester = GPORequester(domain_controller, domain, user, password,
194 lmhash, nthash)
226 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
227 queried_gponame='*', queried_displayname=str(), queried_domain=str(),
228 ads_path=str()):
229 requester = GPORequester(domain_controller, domain, user, password,
230 lmhash, nthash, do_kerberos, do_tls)
195231 return requester.get_netgpo(queried_gponame=queried_gponame,
196232 queried_displayname=queried_displayname,
197233 queried_domain=queried_domain, ads_path=ads_path)
198234
235 def get_netpso(domain_controller, domain, user, password=str(),
236 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
237 queried_psoname='*', queried_displayname=str(), queried_domain=str(),
238 ads_path=str()):
239 requester = GPORequester(domain_controller, domain, user, password,
240 lmhash, nthash, do_kerberos, do_tls)
241 return requester.get_netpso(queried_psoname=queried_psoname,
242 queried_displayname=queried_displayname,
243 queried_domain=queried_domain, ads_path=ads_path)
244
199245 def get_domainpolicy(domain_controller, domain, user, password=str(),
200 lmhash=str(), nthash=str(), source='domain', queried_domain=str(),
201 resolve_sids=False):
202 requester = GPORequester(domain_controller, domain, user, password,
203 lmhash, nthash)
246 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
247 source='domain', queried_domain=str(), resolve_sids=False):
248 requester = GPORequester(domain_controller, domain, user, password,
249 lmhash, nthash, do_kerberos, do_tls)
204250
205251 return requester.get_domainpolicy(source=source, queried_domain=queried_domain,
206252 resolve_sids=resolve_sids)
207253
208254 def get_gpttmpl(gpttmpl_path, domain_controller, domain, user, password=str(), lmhash=str(),
209 nthash=str()):
210 requester = GPORequester(domain_controller, domain, user, password,
211 lmhash, nthash)
255 nthash=str(), do_kerberos=False, do_tls=False):
256 requester = GPORequester(domain_controller, domain, user, password,
257 lmhash, nthash, do_kerberos, do_tls)
212258
213259 return requester.get_gpttmpl(gpttmpl_path)
214260
215261 def get_netgpogroup(domain_controller, domain, user, password=str(), lmhash=str(),
216 nthash=str(), queried_gponame='*', queried_displayname=str(),
217 queried_domain=str(), ads_path=str(), resolve_sids=False):
218 requester = GPORequester(domain_controller, domain, user, password,
219 lmhash, nthash)
262 nthash=str(), do_kerberos=False, do_tls=False, queried_gponame='*',
263 queried_displayname=str(), queried_domain=str(), ads_path=str(),
264 resolve_sids=False):
265 requester = GPORequester(domain_controller, domain, user, password,
266 lmhash, nthash, do_kerberos, do_tls)
220267
221268 return requester.get_netgpogroup(queried_gponame=queried_gponame,
222269 queried_displayname=queried_displayname,
225272 resolve_sids=resolve_sids)
226273
227274 def find_gpocomputeradmin(domain_controller, domain, user, password=str(), lmhash=str(),
228 nthash=str(), queried_computername=str(),
229 queried_ouname=str(), queried_domain=str(),
230 recurse=False):
231 requester = GPORequester(domain_controller, domain, user, password,
232 lmhash, nthash)
275 nthash=str(), do_kerberos=False, do_tls=False, queried_computername=str(),
276 queried_ouname=str(), queried_domain=str(), recurse=False):
277 requester = GPORequester(domain_controller, domain, user, password,
278 lmhash, nthash, do_kerberos, do_tls)
233279
234280 return requester.find_gpocomputeradmin(queried_computername=queried_computername,
235281 queried_ouname=queried_ouname,
237283 recurse=recurse)
238284
239285 def find_gpolocation(domain_controller, domain, user, password=str(), lmhash=str(),
240 nthash=str(), queried_username=str(), queried_groupname=str(),
241 queried_localgroup=str(), queried_domain=str()):
242 requester = GPORequester(domain_controller, domain, user, password,
243 lmhash, nthash)
286 nthash=str(), do_kerberos=False, do_tls=False, queried_username=str(),
287 queried_groupname=str(), queried_localgroup=str(),
288 queried_domain=str()):
289 requester = GPORequester(domain_controller, domain, user, password,
290 lmhash, nthash, do_kerberos, do_tls)
244291 return requester.find_gpolocation(queried_username=queried_username,
245292 queried_groupname=queried_groupname,
246293 queried_localgroup=queried_localgroup,
247294 queried_domain=queried_domain)
248295
249296 def invoke_checklocaladminaccess(target_computername, domain, user, password=str(),
250 lmhash=str(), nthash=str()):
251 misc = Misc(target_computername, domain, user, password, lmhash, nthash)
297 lmhash=str(), nthash=str(), do_kerberos=False):
298 misc = Misc(target_computername, domain, user, password, lmhash, nthash, do_kerberos)
252299
253300 return misc.invoke_checklocaladminaccess()
254301
255302 def invoke_userhunter(domain_controller, domain, user, password=str(),
256 lmhash=str(), nthash=str(), queried_computername=list(),
257 queried_computerfile=None, queried_computerfilter=str(),
258 queried_computeradspath=str(), unconstrained=False,
259 queried_groupname=str(), target_server=str(),
303 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
304 queried_computername=list(), queried_computerfile=None,
305 queried_computerfilter=str(), queried_computeradspath=str(),
306 unconstrained=False, queried_groupname=str(), target_server=str(),
260307 queried_username=str(), queried_useradspath=str(),
261308 queried_userfilter=str(), queried_userfile=None,
262309 threads=1, admin_count=False, allow_delegation=False,
264311 stealth=False, stealth_source=['dfs', 'dc', 'file'],
265312 show_all=False, foreign_users=False):
266313 user_hunter = UserHunter(domain_controller, domain, user, password,
267 lmhash, nthash)
268
314 lmhash, nthash, do_kerberos, do_tls)
269315 return user_hunter.invoke_userhunter(queried_computername=queried_computername,
270316 queried_computerfile=queried_computerfile,
271317 queried_computerfilter=queried_computerfilter,
281327 foreign_users=foreign_users)
282328
283329 def invoke_processhunter(domain_controller, domain, user, password=str(),
284 lmhash=str(), nthash=str(), queried_computername=list(),
285 queried_computerfile=None, queried_computerfilter=str(),
286 queried_computeradspath=str(), queried_processname=list(),
287 queried_groupname=str(), target_server=str(),
330 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
331 queried_computername=list(), queried_computerfile=None,
332 queried_computerfilter=str(), queried_computeradspath=str(),
333 queried_processname=list(), queried_groupname=str(), target_server=str(),
288334 queried_username=str(), queried_useradspath=str(),
289335 queried_userfilter=str(), queried_userfile=None, threads=1,
290336 stop_on_success=False, queried_domain=str(), show_all=False):
291337 process_hunter = ProcessHunter(domain_controller, domain, user, password,
292 lmhash, nthash)
338 lmhash, nthash, do_kerberos, do_tls)
293339
294340 return process_hunter.invoke_processhunter(queried_computername=queried_computername,
295341 queried_computerfile=queried_computerfile,
304350 queried_domain=queried_domain, show_all=show_all)
305351
306352 def invoke_eventhunter(domain_controller, domain, user, password=str(),
307 lmhash=str(), nthash=str(), queried_computername=list(),
308 queried_computerfile=None, queried_computerfilter=str(),
309 queried_computeradspath=str(), queried_groupname=str(),
310 target_server=str(), queried_username=str(),
353 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
354 queried_computername=list(), queried_computerfile=None,
355 queried_computerfilter=str(), queried_computeradspath=str(),
356 queried_groupname=str(), target_server=str(), queried_username=str(),
311357 queried_useradspath=str(), queried_userfilter=str(),
312358 queried_userfile=None, threads=1, queried_domain=str(),
313359 search_days=3):
314360 event_hunter = EventHunter(domain_controller, domain, user, password,
315 lmhash, nthash)
361 lmhash, nthash, do_kerberos, do_tls)
316362
317363 return event_hunter.invoke_eventhunter(queried_computername=queried_computername,
318364 queried_computerfile=queried_computerfile,
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
18
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
18
19 import logging
1920 import argparse
21 import json
22 import datetime
2023 from pywerview.cli.helpers import *
2124 from pywerview.functions.hunting import *
2225
2427 # Main parser
2528 parser = argparse.ArgumentParser(description='Rewriting of some PowerView\'s functionalities in Python')
2629 subparsers = parser.add_subparsers(title='Subcommands', description='Available subcommands', dest='submodule')
27
30
2831 # hack for python < 3.9 : https://stackoverflow.com/questions/23349349/argparse-with-required-subparser
2932 subparsers.required = True
33
34 # Logging parser
35 logging_parser = argparse.ArgumentParser(add_help=False)
36 logging_parser.add_argument('-l', '--logging-level', dest='logging_level', type=str.upper,
37 choices=['CRITICAL', 'WARNING', 'DEBUG', 'ULTRA'], default='CRITICAL',
38 help='SDTERR logging level: '
39 'CRITICAL: Only critical errors are displayed (default), '
40 'WARNING: Warnings are displayed, along with citical errors, '
41 'DEBUG: Debug level (caution: very verbose), '
42 'ULTRA: Extreme debugging level (caution: very very verbose)')
43
44 # json parser
45 json_output_parser = argparse.ArgumentParser(add_help=False)
46 json_output_parser.add_argument('--json', dest='json_output', action='store_true',
47 help='Print results in JSON format')
3048
3149 # TODO: support keberos authentication
3250 # Credentials parser
3351 credentials_parser = argparse.ArgumentParser(add_help=False)
3452 credentials_parser.add_argument('-w', '--workgroup', dest='domain',
3553 default=str(), help='Name of the domain we authenticate with')
36 credentials_parser.add_argument('-u', '--user', required=True,
54 credentials_parser.add_argument('-u', '--user',
3755 help='Username used to connect to the Domain Controller')
3856 credentials_parser.add_argument('-p', '--password',
3957 help='Password associated to the username')
40 credentials_parser.add_argument('--hashes', action='store', metavar = 'LMHASH:NTHASH',
58 credentials_parser.add_argument('--hashes', action='store', metavar='LMHASH:NTHASH',
4159 help='NTLM hashes, format is [LMHASH:]NTHASH')
60 credentials_parser.add_argument('-k', action='store_true', dest='do_kerberos',
61 help='Use Kerberos authentication. Grabs credentials from ccache file '
62 '(KRB5CCNAME) based on target parameters. If valid credentials '
63 'cannot be found, it will use the ones specified in the command '
64 'line')
4265
4366 # AD parser, used for net* functions running against a domain controller
4467 ad_parser = argparse.ArgumentParser(add_help=False, parents=[credentials_parser])
4568 ad_parser.add_argument('-t', '--dc-ip', dest='domain_controller',
4669 required=True, help='IP address of the Domain Controller to target')
70 ad_parser.add_argument('--tls', action='store_true', dest='do_tls',
71 help='Force TLS connection to the Domain Controller')
4772
4873 # Target parser, used for net* functions running against a normal computer
4974 target_parser = argparse.ArgumentParser(add_help=False, parents=[credentials_parser])
79104
80105 # Parser for the get-adobject command
81106 get_adobject_parser = subparsers.add_parser('get-adobject', help='Takes a domain SID, '\
82 'samAccountName or name, and return the associated object', parents=[ad_parser])
107 'samAccountName or name, and return the associated object',
108 parents=[ad_parser, logging_parser, json_output_parser])
83109 get_adobject_parser.add_argument('--sid', dest='queried_sid',
84110 help='SID to query (wildcards accepted)')
85111 get_adobject_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
90116 help='Domain to query')
91117 get_adobject_parser.add_argument('-a', '--ads-path',
92118 help='Additional ADS path')
119 get_adobject_parser.add_argument('--attributes', nargs='+', dest='attributes',
120 default=[], help='Object attributes to return')
93121 get_adobject_parser.set_defaults(func=get_adobject)
122
123 # Parser for the get-adserviceaccount command
124 get_adserviceaccount_parser = subparsers.add_parser('get-adserviceaccount', help='Returns a list of all the '\
125 'gMSA of the specified domain. To retrieve passwords, you need a privileged account and '\
126 'a TLS connection to the LDAP server (use the --tls switch).',
127 parents=[ad_parser, logging_parser, json_output_parser])
128 get_adserviceaccount_parser.add_argument('--sid', dest='queried_sid',
129 help='SID to query (wildcards accepted)')
130 get_adserviceaccount_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
131 help='samAccountName to query (wildcards accepted)')
132 get_adserviceaccount_parser.add_argument('--name', dest='queried_name',
133 help='Name to query (wildcards accepted)')
134 get_adserviceaccount_parser.add_argument('-d', '--domain', dest='queried_domain',
135 help='Domain to query')
136 get_adserviceaccount_parser.add_argument('-a', '--ads-path',
137 help='Additional ADS path')
138 get_adserviceaccount_parser.add_argument('--resolve-sids', dest='resolve_sids',
139 action='store_true', help='Resolve SIDs when querying PrincipalsAllowedToRetrieveManagedPassword')
140 get_adserviceaccount_parser.set_defaults(func=get_adserviceaccount)
141
142 # Parser for the get-objectacl command
143 get_objectacl_parser = subparsers.add_parser('get-objectacl', help='Takes a domain SID, '\
144 'samAccountName or name, and return the ACL of the associated object',
145 parents=[ad_parser, logging_parser, json_output_parser])
146 get_objectacl_parser.add_argument('--sid', dest='queried_sid',
147 help='SID to query (wildcards accepted)')
148 get_objectacl_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
149 help='samAccountName to query (wildcards accepted)')
150 get_objectacl_parser.add_argument('--name', dest='queried_name',
151 help='Name to query (wildcards accepted)')
152 get_objectacl_parser.add_argument('-d', '--domain', dest='queried_domain',
153 help='Domain to query')
154 get_objectacl_parser.add_argument('-a', '--ads-path',
155 help='Additional ADS path')
156 get_objectacl_parser.add_argument('--sacl', action='store_true',
157 help='Return the SACL instead of the DACL for the object (requires '\
158 'a privileged account)')
159 get_objectacl_parser.add_argument('--rights-filter', dest='rights_filter',
160 choices=['reset-password', 'write-members', 'all'], help='A specific set of rights to return '\
161 '(reset-password, write-members, all)')
162 get_objectacl_parser.add_argument('--resolve-sids', dest='resolve_sids',
163 action='store_true', help='Resolve SIDs when querying an ACL')
164 get_objectacl_parser.add_argument('--resolve-guids', action='store_true',
165 help='Resolve GUIDs to their display names')
166 get_objectacl_parser.set_defaults(func=get_objectacl)
94167
95168 # Parser for the get-netuser command
96169 get_netuser_parser = subparsers.add_parser('get-netuser', help='Queries information about '\
97 'a domain user', parents=[ad_parser])
170 'a domain user', parents=[ad_parser, logging_parser, json_output_parser])
98171 get_netuser_parser.add_argument('--username', dest='queried_username',
99172 help='Username to query (wildcards accepted)')
100173 get_netuser_parser.add_argument('-d', '--domain', dest='queried_domain',
119192
120193 # Parser for the get-netgroup command
121194 get_netgroup_parser = subparsers.add_parser('get-netgroup', help='Get a list of all current '\
122 'domain groups, or a list of groups a domain user is member of', parents=[ad_parser])
195 'domain groups, or a list of groups a domain user is member of',
196 parents=[ad_parser, logging_parser, json_output_parser])
123197 get_netgroup_parser.add_argument('--groupname', dest='queried_groupname',
124198 default='*', help='Group to query (wildcards accepted)')
125199 get_netgroup_parser.add_argument('--sid', dest='queried_sid',
138212
139213 # Parser for the get-netcomputer command
140214 get_netcomputer_parser = subparsers.add_parser('get-netcomputer', help='Queries informations about '\
141 'domain computers', parents=[ad_parser])
215 'domain computers', parents=[ad_parser, logging_parser, json_output_parser])
142216 get_netcomputer_parser.add_argument('--computername', dest='queried_computername',
143217 default='*', help='Computer name to query')
144218 get_netcomputer_parser.add_argument('-os', '--operating-system', dest='queried_os',
165239
166240 # Parser for the get-netdomaincontroller command
167241 get_netdomaincontroller_parser = subparsers.add_parser('get-netdomaincontroller', help='Get a list of '\
168 'domain controllers for the given domain', parents=[ad_parser])
242 'domain controllers for the given domain', parents=[ad_parser, logging_parser, json_output_parser])
169243 get_netdomaincontroller_parser.add_argument('-d', '--domain', dest='queried_domain',
170244 help='Domain to query')
171245 get_netdomaincontroller_parser.set_defaults(func=get_netdomaincontroller)
172246
173247 # Parser for the get-netfileserver command
174248 get_netfileserver_parser = subparsers.add_parser('get-netfileserver', help='Return a list of '\
175 'file servers, extracted from the domain users\' homeDirectory, scriptPath, and profilePath fields', parents=[ad_parser])
249 'file servers, extracted from the domain users\' homeDirectory, scriptPath, and profilePath fields',
250 parents=[ad_parser, logging_parser, json_output_parser])
176251 get_netfileserver_parser.add_argument('--target-users', nargs='+',
177252 metavar='TARGET_USER', help='A list of users to target to find file servers (wildcards accepted)')
178253 get_netfileserver_parser.add_argument('-d', '--domain', dest='queried_domain',
181256
182257 # Parser for the get-dfsshare command
183258 get_dfsshare_parser = subparsers.add_parser('get-dfsshare', help='Return a list of '\
184 'all fault tolerant distributed file systems for a given domain', parents=[ad_parser])
259 'all fault tolerant distributed file systems for a given domain', parents=[ad_parser, logging_parser, json_output_parser])
185260 get_dfsshare_parser.add_argument('-d', '--domain', dest='queried_domain',
186261 help='Domain to query')
187262 get_dfsshare_parser.add_argument('-v', '--version', nargs='+', choices=['v1', 'v2'],
192267
193268 # Parser for the get-netou command
194269 get_netou_parser = subparsers.add_parser('get-netou', help='Get a list of all current '\
195 'OUs in the domain', parents=[ad_parser])
270 'OUs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
196271 get_netou_parser.add_argument('--ouname', dest='queried_ouname',
197272 default='*', help='OU name to query (wildcards accepted)')
198273 get_netou_parser.add_argument('--guid', dest='queried_guid',
207282
208283 # Parser for the get-netsite command
209284 get_netsite_parser = subparsers.add_parser('get-netsite', help='Get a list of all current '\
210 'sites in the domain', parents=[ad_parser])
285 'sites in the domain', parents=[ad_parser, logging_parser, json_output_parser])
211286 get_netsite_parser.add_argument('--sitename', dest='queried_sitename',
212287 help='Site name to query (wildcards accepted)')
213288 get_netsite_parser.add_argument('--guid', dest='queried_guid',
222297
223298 # Parser for the get-netsubnet command
224299 get_netsubnet_parser = subparsers.add_parser('get-netsubnet', help='Get a list of all current '\
225 'subnets in the domain', parents=[ad_parser])
300 'subnets in the domain', parents=[ad_parser, logging_parser, json_output_parser])
226301 get_netsubnet_parser.add_argument('--sitename', dest='queried_sitename',
227302 help='Only return subnets for the specified site name (wildcards accepted)')
228303 get_netsubnet_parser.add_argument('-d', '--domain', dest='queried_domain',
235310
236311 # Parser for the get-netdomaintrust command
237312 get_netdomaintrust_parser = subparsers.add_parser('get-netdomaintrust', help='Returns a list of all the '\
238 'trusts of the specified domain', parents=[ad_parser])
313 'trusts of the specified domain', parents=[ad_parser, logging_parser, json_output_parser])
239314 get_netdomaintrust_parser.add_argument('-d', '--domain', dest='queried_domain',
240315 help='Domain to query')
241316 get_netdomaintrust_parser.set_defaults(func=get_netdomaintrust)
242317
243318 # Parser for the get-netgpo command
244319 get_netgpo_parser = subparsers.add_parser('get-netgpo', help='Get a list of all current '\
245 'GPOs in the domain', parents=[ad_parser])
320 'GPOs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
246321 get_netgpo_parser.add_argument('--gponame', dest='queried_gponame',
247322 default='*', help='GPO name to query for (wildcards accepted)')
248323 get_netgpo_parser.add_argument('--displayname', dest='queried_displayname',
253328 help='Additional ADS path')
254329 get_netgpo_parser.set_defaults(func=get_netgpo)
255330
331 # Parser for the get-netpso command
332 get_netpso_parser = subparsers.add_parser('get-netpso', help='Get a list of all current '\
333 'PSOs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
334 get_netpso_parser.add_argument('--psoname', dest='queried_psoname',
335 default='*', help='pso name to query for (wildcards accepted)')
336 get_netpso_parser.add_argument('--displayname', dest='queried_displayname',
337 help='Display name to query for (wildcards accepted)')
338 get_netpso_parser.add_argument('-d', '--domain', dest='queried_domain',
339 help='Domain to query')
340 get_netpso_parser.add_argument('-a', '--ads-path',
341 help='Additional ADS path')
342 get_netpso_parser.set_defaults(func=get_netpso)
343
256344 # Parser for the get-domainpolicy command
257345 get_domainpolicy_parser = subparsers.add_parser('get-domainpolicy', help='Returns the default domain or DC '\
258 'policy for the queried domain or DC', parents=[ad_parser])
346 'policy for the queried domain or DC', parents=[ad_parser, logging_parser, json_output_parser])
259347 get_domainpolicy_parser.add_argument('--source', dest='source', default='domain',
260348 choices=['domain', 'dc'], help='Extract domain or DC policy (default: %(default)s)')
261349 get_domainpolicy_parser.add_argument('-d', '--domain', dest='queried_domain',
266354
267355 # Parser for the get-gpttmpl command
268356 get_gpttmpl_parser = subparsers.add_parser('get-gpttmpl', help='Helper to parse a GptTmpl.inf policy '\
269 'file path into a custom object', parents=[ad_parser])
357 'file path into a custom object', parents=[ad_parser, logging_parser, json_output_parser])
270358 get_gpttmpl_parser.add_argument('--gpt-tmpl-path', type=str, required=True,
271359 dest='gpttmpl_path', help='The GptTmpl.inf file path name to parse')
272360 get_gpttmpl_parser.set_defaults(func=get_gpttmpl)
273361
274362 # Parser for the get-netgpogroup command
275363 get_netgpogroup_parser = subparsers.add_parser('get-netgpogroup', help='Parses all GPOs in the domain '\
276 'that set "Restricted Group" or "Groups.xml"', parents=[ad_parser])
364 'that set "Restricted Group" or "Groups.xml"', parents=[ad_parser, logging_parser, json_output_parser])
277365 get_netgpogroup_parser.add_argument('--gponame', dest='queried_gponame',
278366 default='*', help='GPO name to query for (wildcards accepted)')
279367 get_netgpogroup_parser.add_argument('--displayname', dest='queried_displayname',
288376
289377 # Parser for the find-gpocomputeradmin command
290378 find_gpocomputeradmin_parser = subparsers.add_parser('find-gpocomputeradmin', help='Takes a computer (or OU) and determine '\
291 'who has administrative access to it via GPO', parents=[ad_parser])
379 'who has administrative access to it via GPO', parents=[ad_parser, logging_parser, json_output_parser])
292380 find_gpocomputeradmin_parser.add_argument('--computername', dest='queried_computername',
293381 default=str(), help='The computer to determine who has administrative access to it')
294382 find_gpocomputeradmin_parser.add_argument('--ouname', dest='queried_ouname',
302390
303391 # Parser for the find-gpolocation command
304392 find_gpolocation_parser = subparsers.add_parser('find-gpolocation', help='Takes a username or a group name and determine '\
305 'the computers it has administrative access to via GPO', parents=[ad_parser])
393 'the computers it has administrative access to via GPO', parents=[ad_parser, logging_parser, json_output_parser])
306394 find_gpolocation_parser.add_argument('--username', dest='queried_username',
307395 default=str(), help='The username to query for access (no wildcard)')
308396 find_gpolocation_parser.add_argument('--groupname', dest='queried_groupname',
315403 find_gpolocation_parser.set_defaults(func=find_gpolocation)
316404
317405 # Parser for the get-netgroup command
318 get_netgroupmember_parser = subparsers.add_parser('get-netgroupmember', help='Return a list of members of a domain group', parents=[ad_parser])
406 get_netgroupmember_parser = subparsers.add_parser('get-netgroupmember', help='Return a list of members of a domain group',
407 parents=[ad_parser, logging_parser, json_output_parser])
319408 get_netgroupmember_parser.add_argument('--groupname', dest='queried_groupname',
320409 help='Group to query, defaults to the \'Domain Admins\' group (wildcards accepted)')
321410 get_netgroupmember_parser.add_argument('--sid', dest='queried_sid',
335424
336425 # Parser for the get-netsession command
337426 get_netsession_parser = subparsers.add_parser('get-netsession', help='Queries a host to return a '\
338 'list of active sessions on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
427 'list of active sessions on the host (you can use local credentials instead of domain credentials)',
428 parents=[target_parser, logging_parser, json_output_parser])
339429 get_netsession_parser.set_defaults(func=get_netsession)
340430
341431 #Parser for the get-localdisks command
342432 get_localdisks_parser = subparsers.add_parser('get-localdisks', help='Queries a host to return a '\
343 'list of active disks on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
433 'list of active disks on the host (you can use local credentials instead of domain credentials)',
434 parents=[target_parser, logging_parser, json_output_parser])
344435 get_localdisks_parser.set_defaults(func=get_localdisks)
345436
346437 #Parser for the get-netdomain command
347438 get_netdomain_parser = subparsers.add_parser('get-netdomain', help='Queries a host for available domains',
348 parents=[ad_parser])
439 parents=[ad_parser, logging_parser, json_output_parser])
349440 get_netdomain_parser.set_defaults(func=get_netdomain)
350441
351442 # Parser for the get-netshare command
352443 get_netshare_parser = subparsers.add_parser('get-netshare', help='Queries a host to return a '\
353 'list of available shares on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
444 'list of available shares on the host (you can use local credentials instead of domain credentials)',
445 parents=[target_parser, logging_parser, json_output_parser])
354446 get_netshare_parser.set_defaults(func=get_netshare)
355447
356448 # Parser for the get-netloggedon command
357449 get_netloggedon_parser = subparsers.add_parser('get-netloggedon', help='This function will '\
358450 'execute the NetWkstaUserEnum RPC call to query a given host for actively logged on '\
359 'users', parents=[target_parser])
451 'users', parents=[target_parser, logging_parser, json_output_parser])
360452 get_netloggedon_parser.set_defaults(func=get_netloggedon)
361453
362454 # Parser for the get-netlocalgroup command
363455 get_netlocalgroup_parser = subparsers.add_parser('get-netlocalgroup', help='Gets a list of '\
364456 'members of a local group on a machine, or returns every local group. You can use local '\
365457 'credentials instead of domain credentials, however, domain credentials are needed to '\
366 'resolve domain SIDs.', parents=[target_parser])
458 'resolve domain SIDs.', parents=[target_parser, logging_parser, json_output_parser])
367459 get_netlocalgroup_parser.add_argument('--groupname', dest='queried_groupname',
368460 help='Group to list the members of (defaults to the local \'Administrators\' group')
369461 get_netlocalgroup_parser.add_argument('--list-groups', action='store_true',
370462 help='If set, returns a list of the local groups on the targets')
371463 get_netlocalgroup_parser.add_argument('-t', '--dc-ip', dest='domain_controller',
372464 default=str(), help='IP address of the Domain Controller (used to resolve domain SIDs)')
465 get_netlocalgroup_parser.add_argument('--tls', action='store_true', dest='do_tls',
466 help='Force TLS connection to the Domain Controller')
373467 get_netlocalgroup_parser.add_argument('-r', '--recurse', action='store_true',
374468 help='If the group member is a domain group, try to resolve its members as well')
375469 get_netlocalgroup_parser.set_defaults(func=get_netlocalgroup)
376470
377471 # Parser for the invoke-checklocaladminaccess command
378472 invoke_checklocaladminaccess_parser = subparsers.add_parser('invoke-checklocaladminaccess', help='Checks '\
379 'if the given user has local admin access on the given host', parents=[target_parser])
473 'if the given user has local admin access on the given host',
474 parents=[target_parser, logging_parser, json_output_parser])
380475 invoke_checklocaladminaccess_parser.set_defaults(func=invoke_checklocaladminaccess)
381476
382477 # Parser for the get-netprocess command
383478 get_netprocess_parser = subparsers.add_parser('get-netprocess', help='This function will '\
384479 'execute the \'Select * from Win32_Process\' WMI query to a given host for a list of '\
385 'executed process', parents=[target_parser])
480 'executed process', parents=[target_parser, logging_parser, json_output_parser])
386481 get_netprocess_parser.set_defaults(func=get_netprocess)
387482
388483 # Parser for the get-userevent command
389484 get_userevent_parser = subparsers.add_parser('get-userevent', help='This function will '\
390 'execute the \'Select * from Win32_Process\' WMI query to a given host for a list of '\
391 'executed process', parents=[target_parser])
485 'execute the \'SELECT * from Win32_NTLogEvent\' WMI query to a given host for a list of '\
486 'executed process', parents=[target_parser, logging_parser, json_output_parser])
392487 get_userevent_parser.add_argument('--event-type', nargs='+', choices=['logon', 'tgt'],
393488 default=['logon', 'tgt'], help='The type of event to search for: logon, tgt, or all (default: all)')
394489 get_userevent_parser.add_argument('--date-start', type=int,
397492
398493 # Parser for the invoke-userhunter command
399494 invoke_userhunter_parser = subparsers.add_parser('invoke-userhunter', help='Finds '\
400 'which machines domain users are logged into', parents=[ad_parser, hunter_parser])
495 'which machines domain users are logged into', parents=[ad_parser, hunter_parser, logging_parser])
401496 invoke_userhunter_parser.add_argument('--unconstrained', action='store_true',
402497 help='Query only computers with unconstrained delegation')
403498 invoke_userhunter_parser.add_argument('--admin-count', action='store_true',
423518
424519 # Parser for the invoke-processhunter command
425520 invoke_processhunter_parser = subparsers.add_parser('invoke-processhunter', help='Searches machines '\
426 'for processes with specific name, or ran by specific users', parents=[ad_parser, hunter_parser])
521 'for processes with specific name, or ran by specific users',
522 parents=[ad_parser, hunter_parser, logging_parser])
427523 invoke_processhunter_parser.add_argument('--processname', dest='queried_processname',
428524 nargs='+', default=list(), help='Names of the process to hunt')
429525 invoke_processhunter_parser.add_argument('--stop-on-success', action='store_true',
434530
435531 # Parser for the invoke-eventhunter command
436532 invoke_eventhunter_parser = subparsers.add_parser('invoke-eventhunter', help='Searches machines '\
437 'for events with specific name, or ran by specific users', parents=[ad_parser, hunter_parser])
533 'for events with specific name, or ran by specific users',
534 parents=[ad_parser, hunter_parser, logging_parser])
438535 invoke_eventhunter_parser.add_argument('--search-days', dest='search_days',
439536 type=int, default=3, help='Number of days back to search logs for (default: %(default)s)')
440537 invoke_eventhunter_parser.set_defaults(func=invoke_eventhunter)
441538
442539 args = parser.parse_args()
540
541 # setup the main logger
542 logger = logging.getLogger('pywerview_main_logger')
543 logging.addLevelName(5, 'ULTRA')
544 logger.setLevel(args.logging_level)
545 console_handler = logging.StreamHandler()
546 console_handler.setLevel(args.logging_level)
547 formatter = logging.Formatter('[%(levelname)s] %(name)s - %(funcName)s : %(message)s')
548 console_handler.setFormatter(formatter)
549 logger.addHandler(console_handler)
550
443551 if args.hashes:
444552 try:
445553 args.lmhash, args.nthash = args.hashes.split(':')
450558 else:
451559 args.lmhash = args.nthash = str()
452560
453 if args.password is None and not args.hashes:
561 if args.password is None and args.hashes is None and not args.do_kerberos:
454562 from getpass import getpass
455563 args.password = getpass('Password:')
456564
457565 parsed_args = dict()
458566 for k, v in vars(args).items():
459 if k not in ('func', 'hashes', 'submodule'):
567 if k not in ('func', 'hashes', 'submodule', 'logging_level', 'json_output'):
460568 parsed_args[k] = v
461569
462 #try:
570 starting_time = datetime.datetime.now()
463571 results = args.func(**parsed_args)
464 #except Exception, e:
465 #print >>sys.stderr, repr(e)
466 #sys.exit(-1)
572 ending_time = datetime.datetime.now()
573
574 try:
575 json_output = args.json_output
576 except AttributeError:
577 json_output = False
467578
468579 if results is not None:
469 try:
470 for x in results:
471 print(x)
472 # for example, invoke_checklocaladminaccess returns a bool
473 except TypeError:
474 print(results)
475
580 if json_output:
581 results_json = {'cmd' : {'submodule' : args.submodule, 'args' : parsed_args,
582 'starting_time': starting_time, 'ending_time': ending_time}}
583 try:
584 objects_json = [x.to_json() for x in results]
585 except TypeError:
586 try:
587 objects_json = [results.to_json()]
588 except AttributeError:
589 objects_json = results
590 results_json['results'] = objects_json
591 print(json.dumps(results_json, default=str))
592 else:
593 try:
594 print('\n\n'.join(str(x) for x in results))
595 except TypeError:
596 print(results)
597
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 © 2022
16
17 import logging
18 import binascii
19 from Cryptodome.Hash import MD4
20 from impacket.examples.ntlmrelayx.attacks.ldapattack import MSDS_MANAGEDPASSWORD_BLOB
21 from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR
22
23 __uac_flags = {0x0000001: 'SCRIPT',
24 0x0000002: 'ACCOUNTDISABLE',
25 0x0000008: 'HOMEDIR_REQUIRED',
26 0x0000010: 'LOCKOUT',
27 0x0000020: 'PASSWD_NOTREQD',
28 0x0000040: 'PASSWD_CANT_CHANGE',
29 0x0000080: 'ENCRYPTED_TEXT_PWD_ALLOWED',
30 0x0000100: 'TEMP_DUPLICATE_ACCOUNT',
31 0x0000200: 'NORMAL_ACCOUNT',
32 0x0000800: 'INTERDOMAIN_TRUST_ACCOUNT',
33 0x0001000: 'WORKSTATION_TRUST_ACCOUNT',
34 0x0002000: 'SERVER_TRUST_ACCOUNT',
35 0x0010000: 'DONT_EXPIRE_PASSWORD',
36 0x0020000: 'MNS_LOGON_ACCOUNT',
37 0x0040000: 'SMARTCARD_REQUIRED',
38 0x0080000: 'TRUSTED_FOR_DELEGATION',
39 0x0100000: 'NOT_DELEGATED',
40 0x0200000: 'USE_DES_KEY_ONLY',
41 0x0400000: 'DONT_REQ_PREAUTH',
42 0x0800000: 'PASSWORD_EXPIRED',
43 0x1000000: 'TRUSTED_TO_AUTH_FOR_DELEGATION',
44 0x4000000: 'PARTIAL_SECRETS_ACCOUNT'}
45
46 __ace_flags = {0x1: 'object_inherit', 0x2: 'container_inherit',
47 0x4: 'non_propagate_inherit', 0x8: 'inherit_only',
48 0x10: 'inherited_ace', 0x20: 'audit_successful_accesses',
49 0x40: 'audit_failed_access'}
50
51 __object_ace_flags = {0x1: 'object_ace_type_present', 0x2: 'inherited_object_ace_type_present'}
52
53 # Resources: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/990fb975-ab31-4bc1-8b75-5da132cd4584
54 __access_mask = {0x1: 'create_child', 0x2: 'delete_child',
55 0x4: 'list_children', 0x08: 'self',
56 0x10: 'read_property', 0x20: 'write_property',
57 0x40: 'delete_tree', 0x80: 'list_object',
58 0x100: 'extended_right', 0x10000: 'delete',
59 0x20000: 'read_control', 0x40000: 'write_dacl',
60 0x80000: 'write_owner'}
61
62 __access_mask_generic = {0xf01ff: 'generic_all', 0x20094: 'generic_read',
63 0x20028: 'generic_write', 0x20004: 'generic_execute'}
64
65 __trust_attrib = {0x1: 'non_transitive', 0x2: 'uplevel_only',
66 0x4: 'filter_sids', 0x8: 'forest_transitive',
67 0x10: 'cross_organization', 0x20: 'within_forest',
68 0x40: 'treat_as_external',
69 0x80: 'trust_uses_rc4_encryption',
70 0x100: 'trust_uses_aes_keys',
71 0X200: 'cross_organization_no_tgt_delegation',
72 0x400: 'pim_trust'}
73
74 __trust_direction = {0: 'disabled', 1: 'inbound',
75 2: 'outbound', 3: 'bidirectional'}
76
77 __trust_type = {1: 'windows_non_active_directory',
78 2: 'windows_active_directory', 3: 'mit'}
79
80 def __format_flag(raw_value, flag_dict):
81 try:
82 int_value = int(raw_value)
83 except ValueError:
84 self._logger.warning('Unable to convert raw flag value to int')
85 return raw_value
86
87 parsed_flags = list()
88 for flag, flag_label in flag_dict.items():
89 if (int_value & flag) == flag:
90 parsed_flags.append(flag_label)
91 return parsed_flags
92
93 def __format_dict_lookup(raw_value, dictionary):
94 try:
95 return dictionary[int(raw_value)]
96 except (ValueError, KeyError):
97 self._logger.warning('Unable to convert raw value to int')
98 return raw_value
99
100 def format_useraccountcontrol(raw_value):
101 return __format_flag(raw_value, __uac_flags)
102
103 def format_ace_access_mask(raw_value):
104 try:
105 int_value = int(raw_value)
106 except ValueError:
107 self._logger.warning('Unable to convert raw ace acess mask value to int')
108 return raw_value
109
110 activedirectoryrights = list()
111 for flag, flag_label in __access_mask_generic.items():
112 if (int_value & flag) == flag:
113 activedirectoryrights.append(flag_label)
114 int_value ^= flag
115 activedirectoryrights += __format_flag(raw_value, __access_mask)
116
117 return activedirectoryrights
118
119
120 def format_managedpassword(raw_value):
121 blob = MSDS_MANAGEDPASSWORD_BLOB()
122 blob.fromString(raw_value)
123 return binascii.hexlify(MD4.new(blob['CurrentPassword'][:-2]).digest()).decode('utf8')
124
125 def format_groupmsamembership(raw_value):
126 sid = list()
127 sr = SR_SECURITY_DESCRIPTOR(data=raw_value)
128 for dacl in sr['Dacl']['Data']:
129 sid.append(dacl['Ace']['Sid'].formatCanonical())
130 return sid
131
132 def format_ace_flags(raw_value):
133 return __format_flag(raw_value, __ace_flags)
134
135 def format_object_ace_flags(raw_value):
136 return __format_flag(raw_value, __object_ace_flags)
137
138 def format_trustdirection(raw_value):
139 return __format_dict_lookup(raw_value, __trust_direction)
140
141 def format_trusttype(raw_value):
142 return __format_dict_lookup(raw_value, __trust_type)
143
144 def format_trustattributes(raw_value):
145 return __format_flag(raw_value, __trust_attrib)
146
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 import codecs
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
1817 from bs4 import BeautifulSoup
1918 from io import BytesIO
2019
4039 gpo_search_filter = '(&{})'.format(gpo_search_filter)
4140
4241 return self._ldap_search(gpo_search_filter, GPO)
42
43 @LDAPRequester._ldap_connection_init
44 def get_netpso(self, queried_psoname='*', queried_displayname=str(),
45 queried_domain=str(), ads_path=str()):
46
47 pso_search_filter = '(objectClass=msDS-PasswordSettings)'
48
49 if queried_displayname:
50 pso_search_filter += '(displayname={})'.format(queried_displayname)
51 else:
52 pso_search_filter += '(name={})'.format(queried_psoname)
53
54 pso_search_filter = '(&{})'.format(pso_search_filter)
55
56 return self._ldap_search(pso_search_filter, PSO)
4357
4458 def get_gpttmpl(self, gpttmpl_path):
4559 content_io = BytesIO()
5064 file_name = '\\'.join(gpttmpl_path_split[4:])
5165
5266 smb_connection = SMBConnection(remoteName=target, remoteHost=target)
53 # TODO: kerberos login
54 smb_connection.login(self._user, self._password, self._domain,
55 self._lmhash, self._nthash)
56
67 if self._do_kerberos:
68 smb_connection.kerberosLogin(self._user, self._password, self._domain,
69 self._lmhash, self._nthash)
70 else:
71 smb_connection.login(self._user, self._password, self._domain,
72 self._lmhash, self._nthash)
73
74 self._logger.debug('Get File: Share = {0}, file_name ={1}'.format(share, file_name))
5775 smb_connection.connectTree(share)
5876 smb_connection.getFile(share, file_name, content_io.write)
5977 try:
60 content = codecs.decode(content_io.getvalue(), 'utf-16le')[1:].replace('\r', '')
78 content = content_io.getvalue().decode('utf-16le')[1:].replace('\r', '')
6179 except UnicodeDecodeError:
62 content = str(content_io.getvalue()).replace('\r', '')
80 self._logger.warning('Unicode error: trying utf-8')
81 content = content_io.getvalue().decode('utf-8').replace('\r', '')
6382
6483 gpttmpl_final = GptTmpl(list())
6584 for l in content.split('\n'):
6685 if l.startswith('['):
6786 section_name = l.strip('[]').replace(' ', '').lower()
68 setattr(gpttmpl_final, section_name, Policy(list()))
87 gpttmpl_final._attributes_dict[section_name] = Policy(list())
6988 elif '=' in l:
7089 property_name, property_values = [x.strip() for x in l.split('=')]
7190 if ',' in property_values:
7291 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)
92 gpttmpl_final._attributes_dict[section_name]._attributes_dict[property_name] = property_values
7893
7994 return gpttmpl_final
8095
99114 try:
100115 privilege_rights_policy = gpttmpl.privilegerights
101116 except AttributeError:
117 self._logger.critical('Could not parse privilegerights from the DC policy, SIDs will not be resolved')
102118 return gpttmpl
103119
104120 members = inspect.getmembers(privilege_rights_policy, lambda x: not(inspect.isroutine(x)))
105121 with NetRequester(self._domain_controller, self._domain, self._user,
106 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]]
122 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
123 for attr in privilege_rights_policy._attributes_dict:
124 attribute = privilege_rights_policy._attributes_dict[attr]
125 if not isinstance(attribute, list):
126 sids = [attribute]
112127 else:
113 sids = member[1]
128 sids = attribute
114129 resolved_sids = list()
115130 for sid in sids:
116131 if not sid:
117132 continue
133 sid = sid.replace('*', '')
118134 try:
119 resolved_sid = net_requester.get_adobject(queried_sid=sid, queried_domain=queried_domain)[0]
135 resolved_sid = net_requester.get_adobject(queried_sid=sid, queried_domain=self._queried_domain)[0]
120136 except IndexError:
137 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(sid))
121138 resolved_sid = sid
122139 else:
123140 resolved_sid = resolved_sid.distinguishedname.split(',')[:2]
124 resolved_sid = '{}\\{}'.format(resolved_sid[1], resolved_sid[0])
141 resolved_sid = resolved_sid[1] + '\\' + resolved_sid[0]
125142 resolved_sid = resolved_sid.replace('CN=', '')
143 finally:
126144 resolved_sids.append(resolved_sid)
127145 if len(resolved_sids) == 1:
128146 resolved_sids = resolved_sids[0]
129 setattr(privilege_rights_policy, member[0], resolved_sids)
147 privilege_rights_policy._attributes_dict[attr] = resolved_sids
130148
131149 gpttmpl.privilegerights = privilege_rights_policy
132150
144162 file_name = '\\'.join(groupsxml_path_split[4:])
145163
146164 smb_connection = SMBConnection(remoteName=target, remoteHost=target)
147 # TODO: kerberos login
148 smb_connection.login(self._user, self._password, self._domain,
149 self._lmhash, self._nthash)
150
165 if self._do_kerberos:
166 smb_connection.kerberosLogin(self._user, self._password, self._domain,
167 self._lmhash, self._nthash)
168 else:
169 smb_connection.login(self._user, self._password, self._domain,
170 self._lmhash, self._nthash)
171
172 self._logger.debug('Get File: Share = {0}, file_name ={1}'.format(share, file_name))
151173 smb_connection.connectTree(share)
152174 try:
153175 smb_connection.getFile(share, file_name, content_io.write)
154176 except SessionError:
177 self._logger.warning('Error while getting the file {}, skipping...'.format(file_name))
155178 return list()
156179
157180 content = content_io.getvalue().replace(b'\r', b'')
158 groupsxml_soup = BeautifulSoup(content, 'xml')
159
181 groupsxml_soup = BeautifulSoup(content.decode('utf-8'), 'xml')
160182 for group in groupsxml_soup.find_all('Group'):
161183 members = list()
162184 memberof = list()
185
186 raw_xml_member = group.Properties.find_all('Member')
187 if not raw_xml_member:
188 continue
189
163190 local_sid = group.Properties.get('groupSid', str())
191
164192 if not local_sid:
165193 if 'administrators' in group.Properties['groupName'].lower():
166194 local_sid = 'S-1-5-32-544'
170198 local_sid = group.Properties['groupName']
171199 memberof.append(local_sid)
172200
173 for member in group.Properties.find_all('Member'):
201 for member in raw_xml_member:
174202 if not member['action'].lower() == 'add':
175203 continue
176204 if member['sid']:
184212 # have the barest support for filters, so ¯\_(ツ)_/¯
185213
186214 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)
215 gpo_group._attributes_dict['gpodisplayname'] = gpo_display_name
216 gpo_group._attributes_dict['gponame'] = gpo_name
217 gpo_group._attributes_dict['gpopath'] = groupsxml_path
218 gpo_group._attributes_dict['members'] = members
219 gpo_group._attributes_dict['memberof'] = memberof
192220
193221 gpo_groups.append(gpo_group)
194222
195223 return gpo_groups
196224
197225 def _get_groupsgpttmpl(self, gpttmpl_path, gpo_display_name):
198 import inspect
199226 gpo_groups = list()
200227
201228 gpt_tmpl = self.get_gpttmpl(gpttmpl_path)
206233 except AttributeError:
207234 return list()
208235
209 membership = inspect.getmembers(group_membership, lambda x: not(inspect.isroutine(x)))
210 for m in membership:
211 if not m[1]:
236 membership = group_membership._attributes_dict
237
238 for ma,mv in membership.items():
239 if not mv:
212240 continue
213241 members = list()
214242 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]]
243 if ma.lower().endswith('__memberof'):
244 members.append(ma.upper().lstrip('*').replace('__MEMBEROF', ''))
245 if not isinstance(mv, list):
246 memberof_list = [mv]
219247 else:
220 memberof_list = m[1]
248 memberof_list = mv
221249 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]]
250 elif ma.lower().endswith('__members'):
251 memberof.append(ma.upper().lstrip('*').replace('__MEMBERS', ''))
252 if not isinstance(mv, list):
253 members_list = [mv]
226254 else:
227 members_list = m[1]
255 members_list = mv
228256 members += [x.lstrip('*') for x in members_list]
229257
230258 if members and memberof:
231259 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)
260 gpo_group.add_attributes({'gpodisplayname' : gpo_display_name})
261 gpo_group.add_attributes({'gponame' : gpo_name})
262 gpo_group.add_attributes({'gpopath' : gpttmpl_path})
263 gpo_group.add_attributes({'members' : members})
264 gpo_group.add_attributes({'memberof' : memberof})
237265
238266 gpo_groups.append(gpo_group)
239267
258286 results += self._get_groupsgpttmpl(gpttmpl_path, gpo_display_name)
259287 except SessionError:
260288 # If the GptTmpl file doesn't exist, we skip this
289 self._logger.warning('Error while getting the file {}, skipping...'.format(gpttmpl_path,))
261290 pass
262291
263292 if resolve_sids:
268297 resolved_members = list()
269298 resolved_memberof = list()
270299 with NetRequester(self._domain_controller, self._domain, self._user,
271 self._password, self._lmhash, self._nthash) as net_requester:
300 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
272301 for member in members:
273302 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=', '')
303 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
304 resolved_member = resolved_member.distinguishedname
279305 except IndexError:
306 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member))
280307 resolved_member = member
281308 finally:
282309 resolved_members.append(resolved_member)
283 gpo_group.members = resolved_members
310 gpo_group._attributes_dict['members'] = resolved_members
284311
285312 for member in memberof:
286313 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=', '')
314 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
315 resolved_member = resolved_member.distinguishedname
291316 except IndexError:
317 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member))
292318 resolved_member = member
293319 finally:
294320 resolved_memberof.append(resolved_member)
295 gpo_group.memberof = memberof = resolved_memberof
296
321 gpo_group._attributes_dict['memberof'] = memberof = resolved_memberof
297322 return results
298323
299324 def find_gpocomputeradmin(self, queried_computername=str(),
305330 raise ValueError('You must specify either a computer name or an OU name')
306331
307332 net_requester = NetRequester(self._domain_controller, self._domain, self._user,
308 self._password, self._lmhash, self._nthash)
333 self._password, self._lmhash, self._nthash, self._do_kerberos,
334 self._do_tls)
309335 if queried_computername:
310336 computers = net_requester.get_netcomputer(queried_computername=queried_computername,
311337 queried_domain=queried_domain,
326352 for target_ou in target_ous:
327353 ous = net_requester.get_netou(ads_path=target_ou, queried_domain=queried_domain,
328354 full_data=True)
329
330355 for ou in ous:
331 for gplink in ou.gplink.strip('[]').split(']['):
356 try:
357 gplinks = ou.gplink.strip('[]').split('][')
358 except AttributeError:
359 continue
360 for gplink in gplinks:
332361 gplink = gplink.split(';')[0]
333362 gpo_groups = self.get_netgpogroup(queried_domain=queried_domain,
334363 ads_path=gplink)
335364 for gpo_group in gpo_groups:
336365 for member in gpo_group.members:
337366 obj = net_requester.get_adobject(queried_sid=member,
338 queried_domain=queried_domain)[0]
367 queried_domain=self._queried_domain)[0]
339368 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'))
369 gpo_computer_admin.add_attributes({'computername' : queried_computername})
370 gpo_computer_admin.add_attributes({'ou' : target_ou})
371 gpo_computer_admin.add_attributes({'gpodisplayname' : gpo_group.gpodisplayname})
372 gpo_computer_admin.add_attributes({'gpopath' : gpo_group.gpopath})
373 gpo_computer_admin.add_attributes({'objectname' : obj.name})
374 gpo_computer_admin.add_attributes({'objectdn' : obj.distinguishedname})
375 gpo_computer_admin.add_attributes({'objectsid' : obj.objectsid})
376 gpo_computer_admin.add_attributes({'isgroup' : (obj.samaccounttype != 805306368)})
348377
349378 results.append(gpo_computer_admin)
350379
352381 groups_to_resolve = [gpo_computer_admin.objectsid]
353382 while groups_to_resolve:
354383 group_to_resolve = groups_to_resolve.pop(0)
384
355385 group_members = net_requester.get_netgroupmember(queried_sid=group_to_resolve,
356 queried_domain=queried_domain,
386 queried_domain=self._queried_domain,
357387 full_data=True)
358388 for group_member in group_members:
359389 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'))
390 gpo_computer_admin.add_attributes({'computername' : queried_computername})
391 gpo_computer_admin.add_attributes({'ou' : target_ou})
392 gpo_computer_admin.add_attributes({'gpodisplayname' : gpo_group.gpodisplayname})
393 gpo_computer_admin.add_attributes({'gpopath' : gpo_group.gpopath})
394 gpo_computer_admin.add_attributes({'objectname' : group_member.samaccountname})
395 gpo_computer_admin.add_attributes({'objectdn' : group_member.distinguishedname})
396 gpo_computer_admin.add_attributes({'objectsid' : group_member.objectsid})
397 gpo_computer_admin.add_attributes({'isgroup' : (group_member.samaccounttype != 805306368)})
368398
369399 results.append(gpo_computer_admin)
370400
377407 queried_localgroup=str(), queried_domain=str()):
378408 results = list()
379409 net_requester = NetRequester(self._domain_controller, self._domain, self._user,
380 self._password, self._lmhash, self._nthash)
410 self._password, self._lmhash, self._nthash, self._do_kerberos,
411 self._do_tls)
381412 if queried_username:
382413 try:
383414 user = net_requester.get_netuser(queried_username=queried_username,
384 queried_domain=queried_domain)[0]
415 queried_domain=self._queried_domain)[0]
385416 except IndexError:
386417 raise ValueError('Username \'{}\' was not found'.format(queried_username))
387418 else:
391422 elif queried_groupname:
392423 try:
393424 group = net_requester.get_netgroup(queried_groupname=queried_groupname,
394 queried_domain=queried_domain,
425 queried_domain=self._queried_domain,
395426 full_data=True)[0]
396427 except IndexError:
397428 raise ValueError('Group name \'{}\' was not found'.format(queried_groupname))
417448 for object_group in object_groups:
418449 try:
419450 object_group_sid = net_requester.get_adobject(queried_sam_account_name=object_group.samaccountname,
420 queried_domain=queried_domain)[0].objectsid
451 queried_domain=self._queried_domain)[0].objectsid
421452 except IndexError:
422453 # We may have the name of the group, but not its sam account name
454 self._logger.warning('We may have the name of the group, but not its sam account name.')
423455 try:
424456 object_group_sid = net_requester.get_adobject(queried_name=object_group.samaccountname,
425 queried_domain=queried_domain)[0].objectsid
457 queried_domain=self._queried_domain)[0].objectsid
426458 except IndexError:
427459 # Freak accident when someone is a member of a group, but
428460 # we can't find the group in the AD
461 self._logger.warning('Freak accident when someone is a member of a group, but we can\'t find the group in the AD,'
462 'see DEBUG level for more info')
463 self._logger.debug('Dumping the mysterious object = {}'.format(object_group))
429464 continue
430465
431466 target_sid.append(object_group_sid)
434469 for gpo_group in self.get_netgpogroup(queried_domain=queried_domain):
435470 try:
436471 for member in gpo_group.members:
472 member = member
437473 if not member.upper().startswith('S-1-5'):
438474 try:
439475 member = net_requester.get_adobject(queried_sam_account_name=member,
440 queried_domain=queried_domain)[0].objectsid
476 queried_domain=self._queried_domain)[0].objectsid
441477 except (IndexError, AttributeError):
442478 continue
443479 if (member.upper() in target_sid) or (member.lower() in target_sid):
450486
451487 for gpo_group in gpo_groups:
452488 gpo_guid = gpo_group.gponame
453 ous = net_requester.get_netou(queried_domain=queried_domain,
489 ous = net_requester.get_netou(queried_domain=self._queried_domain,
454490 queried_guid=gpo_guid, full_data=True)
455491 for ou in ous:
492 ou_distinguishedname = 'LDAP://{}'.format(ou.distinguishedname)
456493 # TODO: support filters for GPO
457494 ou_computers = [x.dnshostname for x in \
458 net_requester.get_netcomputer(queried_domain=queried_domain,
459 ads_path=ou.distinguishedname)]
495 net_requester.get_netcomputer(queried_domain=self._queried_domain,
496 ads_path=ou_distinguishedname)]
460497 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)
498 gpo_location.add_attributes({'objectname' : object_distinguished_name})
499 gpo_location.add_attributes({'gponame' : gpo_group.gpodisplayname})
500 gpo_location.add_attributes({'gpoguid' : gpo_guid})
501 gpo_location.add_attributes({'containername' : ou.distinguishedname})
502 gpo_location.add_attributes({'computers' : ou_computers})
466503
467504 results.append(gpo_location)
468505
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 import random
1818 import multiprocessing
2525
2626 class Hunter(NetRequester):
2727 def __init__(self, target_computer, domain=str(), user=(), password=str(),
28 lmhash=str(), nthash=str(), domain_controller=str(), queried_domain=str()):
28 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
29 domain_controller=str(), queried_domain=str()):
2930 NetRequester.__init__(self, target_computer, domain, user, password,
30 lmhash, nthash, domain_controller)
31 lmhash, nthash, do_kerberos, do_tls, domain_controller)
3132 self._target_domains = list()
3233 self._target_computers = list()
3334 self._target_users = list()
8889 self._target_users.append(rpcobj.TargetUser(attributes))
8990 elif target_server:
9091 with NetRequester(target_server, domain, user, password, lmhash,
91 nthash, domain_controller) as target_server_requester:
92 nthash, do_kerberos, do_tls, domain_controller) as target_server_requester:
9293 for x in target_server_requester.get_netlocalgroup(recurse=True):
9394 if x.isdomain and not x.isgroup:
9495 attributes = {'memberdomain': x.name.split('/')[0].lower(),
138139 self._parent_pipes.append(parent_pipe)
139140 worker = worker_class(worker_pipe, self._domain, self._user,
140141 self._password, self._lmhash, self._nthash,
141 *worker_args)
142 self._do_kerberos, self._do_tls, *worker_args)
142143
143144 worker.start()
144145 self._workers.append(worker)
154155 rlist, wlist, _ = select.select(self._parent_pipes, write_watch_list, list())
155156
156157 for readable in rlist:
157 jobs_done += 1
158 jobs_done += 1
158159 results = readable.recv()
159160 for result in results:
160161 yield result
199200
200201 if foreign_users:
201202 with Misc(self._domain_controller, self._domain, self._user,
202 self._password, self._lmhash, self._nthash) as misc_requester:
203 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as misc_requester:
203204 domain_sid = misc_requester.get_domainsid(queried_domain)
204205 domain_short_name = misc_requester.convert_sidtont4(domain_sid).split('\\')[0]
205206 else:
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 from impacket.dcerpc.v5.rpcrt import DCERPCException
1818 from impacket.dcerpc.v5 import scmr, drsuapi
4949 def get_domainsid(self, queried_domain=str()):
5050
5151 with pywerview.functions.net.NetRequester(self._domain_controller, self._domain, self._user,
52 self._password, self._lmhash, self._nthash) as r:
52 self._password, self._lmhash, self._nthash,
53 self._do_kerberos, self._do_tls) as r:
5354 domain_controllers = r.get_netdomaincontroller(queried_domain=queried_domain)
5455
5556 if domain_controllers:
5657 primary_dc = domain_controllers[0]
57 domain_sid = '-'.join(primary_dc.objectsid.split('-')[:-1])
58 domain_sid = primary_dc.objectsid
59
60 # we need to retrieve the domain sid from the controller sid
61 domain_sid = '-'.join(domain_sid.split('-')[:-1])
5862 else:
5963 domain_sid = None
6064
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 import socket
18 import datetime
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
17 from datetime import datetime, timedelta
1918 from impacket.dcerpc.v5.ndr import NULL
2019 from impacket.dcerpc.v5 import wkst, srvs, samr
2120 from impacket.dcerpc.v5.samr import DCERPCSessionError
2322 from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY
2423 from bs4 import BeautifulSoup
2524 from ldap3.utils.conv import escape_filter_chars
25 from ldap3.protocol.microsoft import security_descriptor_control
26 from ldap3.protocol.formatters.formatters import *
27 from impacket.ldap.ldaptypes import ACE, ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, LDAP_SID, SR_SECURITY_DESCRIPTOR
2628
2729 from pywerview.requester import LDAPRPCRequester
2830 import pywerview.objects.adobjects as adobj
2931 import pywerview.objects.rpcobjects as rpcobj
3032 import pywerview.functions.misc
33 import pywerview.formatters as fmt
3134
3235 class NetRequester(LDAPRPCRequester):
3336 @LDAPRPCRequester._ldap_connection_init
3437 def get_adobject(self, queried_domain=str(), queried_sid=str(),
3538 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)):
39 ads_path=str(), attributes=list(), custom_filter=str()):
40 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
41 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
4042 if attr_value:
4143 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter)
4244 break
43
44 return self._ldap_search(object_filter, adobj.ADObject)
45 else:
46 object_filter = '(&(name=*){})'.format(custom_filter)
47
48 return self._ldap_search(object_filter, adobj.ADObject, attributes=attributes)
49
50 @LDAPRPCRequester._ldap_connection_init
51 def get_adserviceaccount(self, queried_domain=str(), queried_sid=str(),
52 queried_name=str(), queried_sam_account_name=str(),
53 ads_path=str(), resolve_sids=False):
54 filter_objectclass = '(ObjectClass=msDS-GroupManagedServiceAccount)'
55 attributes = ['samaccountname', 'distinguishedname', 'objectsid', 'description',
56 'msds-managedpassword', 'msds-groupmsamembership', 'useraccountcontrol']
57
58 if not self._ldap_connection.server.ssl:
59 self._logger.warning('LDAP connection is not encrypted, we can\'t ask '\
60 'for msds-managedpassword, removing from list of attributes')
61 attributes.remove('msds-managedpassword')
62
63 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
64 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
65 if attr_value:
66 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, filter_objectclass)
67 break
68 else:
69 object_filter = '(&(name=*){})'.format(filter_objectclass)
70
71 adserviceaccounts = self._ldap_search(object_filter, adobj.GMSAAccount, attributes=attributes)
72 sid_resolver = NetRequester(self._domain_controller, self._domain, self._user, self._password, self._lmhash, self._nthash)
73
74 # In this loop, we resolve SID (if true) and we populate 'enabled' attribute
75 for i, adserviceaccount in enumerate(adserviceaccounts):
76 if resolve_sids:
77 results = list()
78 for sid in getattr(adserviceaccount, 'msds-groupmsamembership'):
79 try:
80 resolved_sid = sid_resolver.get_adobject(queried_sid=sid, queried_domain=self._queried_domain,
81 attributes=['distinguishedname'])[0].distinguishedname
82 except IndexError:
83 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(sid))
84 resolved_sid = sid
85 results.append(resolved_sid)
86 adserviceaccounts[i].add_attributes({'msds-groupmsamembership': results})
87 adserviceaccounts[i].add_attributes({'Enabled': 'ACCOUNTDISABLE' not in adserviceaccount.useraccountcontrol})
88 adserviceaccounts[i]._attributes_dict.pop('useraccountcontrol')
89 return adserviceaccounts
90
91 @LDAPRPCRequester._ldap_connection_init
92 def get_objectacl(self, queried_domain=str(), queried_sid=str(),
93 queried_name=str(), queried_sam_account_name=str(),
94 ads_path=str(), sacl=False, rights_filter=str(),
95 resolve_sids=False, resolve_guids=False, custom_filter=str()):
96 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
97 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
98 if attr_value:
99 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter)
100 break
101 else:
102 object_filter = '(&(name=*){})'.format(custom_filter)
103
104 guid_map = dict()
105 # This works on a mono-domain forest, must be tested on a more complex one
106 if resolve_guids:
107 # Dirty fix to get base DN even if custom ADS path was given
108 base_dn = ','.join(self._base_dn.split(',')[-2:])
109 guid_map = {'{00000000-0000-0000-0000-000000000000}': 'All'}
110 with NetRequester(self._domain_controller, self._domain, self._user, self._password,
111 self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
112 for o in net_requester.get_adobject(ads_path='CN=Schema,CN=Configuration,{}'.format(base_dn),
113 attributes=['name', 'schemaIDGUID'], custom_filter='(schemaIDGUID=*)'):
114 guid_map['{{{}}}'.format(o.schemaidguid)] = o.name
115
116 for o in net_requester.get_adobject(ads_path='CN=Extended-Rights,CN=Configuration,{}'.format(base_dn),
117 attributes=['name', 'rightsGuid'], custom_filter='(objectClass=controlAccessRight)'):
118 guid_map['{{{}}}'.format(o.rightsguid.lower())] = o.name
119
120 attributes = ['distinguishedname', 'objectsid', 'ntsecuritydescriptor']
121 if sacl:
122 controls = list()
123 acl_type = 'Sacl'
124 else:
125 # The control is used to get access to ntSecurityDescriptor with an
126 # unprivileged user, see https://stackoverflow.com/questions/40771503/selecting-the-ad-ntsecuritydescriptor-attribute-as-a-non-admin/40773088
127 # /!\ May break pagination from what I've read (see Stack Overflow answer)
128 controls = security_descriptor_control(criticality=True, sdflags=0x07)
129 acl_type = 'Dacl'
130
131 security_descriptors = self._ldap_search(object_filter, adobj.ADObject,
132 attributes=attributes, controls=controls)
133
134 acl = list()
135
136 rights_to_guid = {'reset-password': '{00299570-246d-11d0-a768-00aa006e0529}',
137 'write-members': '{bf9679c0-0de6-11d0-a285-00aa003049e2}',
138 'all': '{00000000-0000-0000-0000-000000000000}'}
139 guid_filter = rights_to_guid.get(rights_filter, None)
140
141 if resolve_sids:
142 sid_resolver = NetRequester(self._domain_controller, self._domain,
143 self._user, self._password, self._lmhash, self._nthash,
144 self._do_kerberos, self._do_tls)
145 sid_mapping = adobj.ADObject._well_known_sids.copy()
146 else:
147 sid_resolver = None
148
149 for security_descriptor in security_descriptors:
150 sd = SR_SECURITY_DESCRIPTOR()
151 try:
152 sd.fromString(security_descriptor.ntsecuritydescriptor)
153 except TypeError:
154 continue
155 for ace in sd[acl_type]['Data']:
156 if guid_filter:
157 try:
158 object_type = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
159 except KeyError:
160 continue
161 if object_type != guid_filter:
162 continue
163 attributes = dict()
164 attributes['objectdn'] = security_descriptor.distinguishedname
165 attributes['objectsid'] = security_descriptor.objectsid
166 attributes['acetype'] = ace['TypeName']
167 attributes['binarysize'] = ace['AceSize']
168 attributes['aceflags'] = fmt.format_ace_flags(ace['AceFlags'])
169 attributes['accessmask'] = ace['Ace']['Mask']['Mask']
170 attributes['activedirectoryrights'] = fmt.format_ace_access_mask(ace['Ace']['Mask']['Mask'])
171 attributes['isinherited'] = bool(ace['AceFlags'] & 0x10)
172 attributes['securityidentifier'] = format_sid(ace['Ace']['Sid'].getData())
173 if sid_resolver:
174 converted_sid = attributes['securityidentifier']
175 try:
176 resolved_sid = sid_mapping[converted_sid]
177 except KeyError:
178 try:
179 resolved_sid = sid_resolver.get_adobject(queried_sid=converted_sid,
180 queried_domain=self._queried_domain, attributes=['distinguishedname'])[0]
181 resolved_sid = resolved_sid.distinguishedname
182 except IndexError:
183 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(converted_sid))
184 resolved_sid = attributes['securityidentifier']
185 finally:
186 sid_mapping[converted_sid] = resolved_sid
187 attributes['securityidentifier'] = resolved_sid
188 try:
189 attributes['objectaceflags'] = fmt.format_object_ace_flags(ace['Ace']['Flags'])
190 except KeyError:
191 pass
192 try:
193 attributes['objectacetype'] = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
194 attributes['objectacetype'] = guid_map[attributes['objectacetype']]
195 except KeyError:
196 pass
197 try:
198 attributes['inheritedobjectacetype'] = format_uuid_le(ace['Ace']['InheritedObjectType']) if ace['Ace']['InheritedObjectType'] else '{00000000-0000-0000-0000-000000000000}'
199 attributes['inheritedobjectacetype'] = guid_map[attributes['inheritedobjectacetype']]
200 except KeyError:
201 pass
202
203 acl.append(adobj.ACE(attributes))
204
205 return acl
45206
46207 @LDAPRPCRequester._ldap_connection_init
47208 def get_netuser(self, queried_username=str(), queried_domain=str(),
79240
80241 # RFC 4515, section 3
81242 # However if we escape *, we can no longer use wildcard within `--groupname`
82 # Maybe we can raise a warning here ?
83243 if not '*' in queried_groupname:
84244 queried_groupname = escape_filter_chars(queried_groupname)
245 else:
246 self._logger.warning('"*" detected in "{}", if it also contains "(",")" or "\\", '
247 'script will probably crash ("invalid filter"). '
248 'Don\'t use wildcard with these characters'.format(queried_groupname))
85249
86250 if queried_username:
251 self._logger.debug('Queried username = {}'.format(queried_username))
87252 results = list()
88253 sam_account_name_to_resolve = [queried_username]
89254 first_run = True
119284 final_results = list()
120285 for group_sam_account_name in results:
121286 obj_member_of = adobj.Group(list())
122 setattr(obj_member_of, 'samaccountname', group_sam_account_name)
287 obj_member_of._attributes_dict['samaccountname'] = group_sam_account_name
123288 final_results.append(obj_member_of)
124289 return final_results
125290 else:
130295 group_search_filter += '(objectCategory=group)'
131296
132297 if queried_sid:
298 self._logger.debug('Queried SID = {}'.format(queried_username))
133299 group_search_filter += '(objectSid={})'.format(queried_sid)
134300 elif queried_groupname:
301 self._logger.debug('Queried groupname = {}'.format(queried_groupname))
135302 group_search_filter += '(name={})'.format(queried_groupname)
136303
137304 if full_data:
208375
209376 final_results = list()
210377 for file_server_name in results:
211 attributes = list()
212 attributes.append({'type': 'dnshostname', 'vals': [file_server_name]})
378 attributes = dict()
379 attributes['dnshostname'] = file_server_name
213380 final_results.append(adobj.FileServer(attributes))
214381
215382 return final_results
227394 for remote_server in dfs.remoteservername:
228395 remote_server = str(remote_server)
229396 if '\\' in remote_server:
230 attributes = {'name': [dfs.name.encode('utf-8')],
231 'remoteservername': [remote_server.split('\\')[2].encode('utf-8')]}
397 attributes = {'name': dfs.name,
398 'remoteservername': remote_server.split('\\')[2]}
232399 results.append(adobj.DFS(attributes))
233400
234401 return results
249416 for target in soup_target_list.targets.contents:
250417 if '\\' in target.string:
251418 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')]}
419 attributes = {'name': '{}{}'.format(dfs_root, share_name),
420 'remoteservername': server_name}
254421
255422 results.append(adobj.DFS(attributes))
256423
337504 try:
338505 # `--groupname` option is supplied
339506 if _groupname:
507 self._logger.debug('Queried groupname = {}'.format(queried_groupname))
340508 groups = self.get_netgroup(queried_groupname=_groupname,
341 queried_domain=queried_domain,
509 queried_domain=self._queried_domain,
342510 full_data=True)
343511
344512 # `--groupname` option is missing, falling back to the "Domain Admins"
345513 else:
514 self._logger.debug('No groupname provided, falling back to the "Domain Admins"'.format(queried_groupname))
346515 if _sid:
347516 queried_sid = _sid
348517 else:
349518 with pywerview.functions.misc.Misc(self._domain_controller,
350519 self._domain, self._user,
351520 self._password, self._lmhash,
352 self._nthash) as misc_requester:
521 self._nthash, self._do_kerberos,
522 self._do_tls) as misc_requester:
353523 queried_sid = misc_requester.get_domainsid(queried_domain) + '-512'
524 self._logger.debug('Found Domains Admins SID = {}'.format(queried_sid))
354525 groups = self.get_netgroup(queried_sid=queried_sid,
355 queried_domain=queried_domain,
526 queried_domain=self._queried_domain,
356527 full_data=True)
357528 except IndexError:
358529 raise ValueError('The group {} was not found'.format(_groupname))
365536 group_memberof_filter = '(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:={}){})'.format(group.distinguishedname, custom_filter)
366537
367538 members = self.get_netuser(custom_filter=group_memberof_filter,
368 queried_domain=queried_domain)
539 queried_domain=self._queried_domain)
369540 else:
370541 # TODO: range cycling
371542 try:
372543 for member in group.member:
373544 # RFC 4515, section 3
545 self._logger.warning('Member name = "{}" will be escaped'.format(member))
374546 member = escape_filter_chars(member, encoding='utf-8')
375547 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)
548 members += self.get_netuser(custom_filter=dn_filter, queried_domain=self._queried_domain)
549 members += self.get_netgroup(custom_filter=dn_filter, queried_domain=self._queried_domain, full_data=True)
378550 # The group doesn't have any members
379551 except AttributeError:
552 self._logger.debug('The group doesn\'t have any members')
380553 continue
381554
382555 for member in members:
389562 try:
390563 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
391564 except IndexError:
565 self._logger.warning('Exception was raised while handling member_dn, falling back to empty string')
392566 member_domain = str()
393 is_group = (member.samaccounttype != '805306368')
567 is_group = (member.samaccounttype != 805306368)
394568
395569 attributes = dict()
396570 if queried_domain:
397571 attributes['groupdomain'] = queried_domain
398572 else:
399 attributes['groupdomain'] = self._domain
573 attributes['groupdomain'] = self._queried_domain
400574 attributes['groupname'] = group.name
401575 attributes['membername'] = member.samaccountname
402576 attributes['memberdomain'] = member_domain
403577 attributes['isgroup'] = is_group
404578 attributes['memberdn'] = member_dn
405 attributes['membersid'] = member.objectsid
579 attributes['objectsid'] = member.objectsid
406580
407581 final_member.add_attributes(attributes)
408582
603777 member_handle = resp['UserHandle']
604778 attributes['isgroup'] = False
605779 resp = samr.hSamrQueryInformationUser(self._rpc_connection, member_handle)
606 attributes['name'] = '{}/{}'.format(member_domain, resp['Buffer']['General']['UserName'])
780 attributes['name'] = '{}\\{}'.format(member_domain, resp['Buffer']['General']['UserName'])
607781 except DCERPCSessionError:
608782 resp = samr.hSamrOpenAlias(self._rpc_connection, domain_handle, aliasId=member_rid)
609783 member_handle = resp['AliasHandle']
610784 attributes['isgroup'] = True
611785 resp = samr.hSamrQueryInformationAlias(self._rpc_connection, member_handle)
612 attributes['name'] = '{}/{}'.format(member_domain, resp['Buffer']['General']['Name'])
613 attributes['lastlogin'] = str()
786 attributes['name'] = '{}\\{}'.format(member_domain, resp['Buffer']['General']['Name'])
787 attributes['lastlogon'] = str()
614788 break
615789 # It's a domain member
616790 else:
621795 member_dn = ad_object.distinguishedname
622796 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
623797 try:
624 attributes['name'] = '{}/{}'.format(member_domain, ad_object.samaccountname)
798 attributes['name'] = '{}\\{}'.format(member_domain, ad_object.samaccountname)
625799 except AttributeError:
626800 # Here, the member is a foreign security principal
627801 # TODO: resolve it properly
628 attributes['name'] = '{}/{}'.format(member_domain, ad_object.objectsid)
629 attributes['isgroup'] = ad_object.isgroup
802 self._logger.warning('The member is a foreign security principal, SID will not be resolved')
803 attributes['name'] = '{}\\{}'.format(member_domain, ad_object.objectsid)
804 attributes['isgroup'] = 'group' in ad_object.objectclass
630805 try:
631 attributes['lastlogin'] = ad_object.lastlogon
806 attributes['lastlogon'] = ad_object.lastlogon
632807 except AttributeError:
633 attributes['lastlogin'] = str()
808 self._logger.warning('lastlogon is not set, falling back to empty string')
809 attributes['lastlogon'] = str()
634810 except IndexError:
635811 # We did not manage to resolve this SID against the DC
812 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member_sid))
636813 attributes['isdomain'] = False
637814 attributes['isgroup'] = False
638815 attributes['name'] = attributes['sid']
639 attributes['lastlogin'] = str()
816 attributes['lastlogon'] = str()
640817 else:
641818 attributes['isgroup'] = False
642819 attributes['name'] = str()
643 attributes['lastlogin'] = str()
820 attributes['lastlogon'] = str()
644821
645822 results.append(rpcobj.RPCObject(attributes))
646823
652829 domain_member_attributes['isdomain'] = True
653830 member_dn = domain_member.distinguishedname
654831 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
655 domain_member_attributes['name'] = '{}/{}'.format(member_domain, domain_member.samaccountname)
832 domain_member_attributes['name'] = '{}\\{}'.format(member_domain, domain_member.samaccountname)
656833 domain_member_attributes['isgroup'] = domain_member.isgroup
657834 domain_member_attributes['isdomain'] = True
835 # TODO: Nope, maybe here we can call get-netdomaincontroller ?
836 # Need to check in powerview
658837 domain_member_attributes['server'] = attributes['name']
659838 domain_member_attributes['sid'] = domain_member.objectsid
660839 try:
661840 domain_member_attributes['lastlogin'] = ad_object.lastlogon
662841 except AttributeError:
842 self._logger.warning('lastlogon is not set, falling back to empty string')
663843 domain_member_attributes['lastlogin'] = str()
664844 results.append(rpcobj.RPCObject(domain_member_attributes))
665845
690870
691871 @LDAPRPCRequester._wmi_connection_init()
692872 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')
873 limit_date = (datetime.today() - timedelta(days=date_start)).strftime('%Y%m%d%H%M%S.%f-000')
694874 if event_type == ['logon']:
695875 where_clause = 'EventCode=4624'
696876 elif event_type == ['tgt']:
707887 wmi_event = wmi_enum_event.Next(0xffffffff, 1)[0]
708888 wmi_event_type = wmi_event.EventIdentifier
709889 wmi_event_info = wmi_event.InsertionStrings
710 time = datetime.datetime.strptime(wmi_event.TimeGenerated, '%Y%m%d%H%M%S.%f-000')
890 time = datetime.strptime(wmi_event.TimeGenerated, '%Y%m%d%H%M%S.%f-000')
711891 if wmi_event_type == 4624:
712892 logon_type = int(wmi_event_info[8])
713893 user = wmi_event_info[5]
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 from datetime import datetime, timedelta
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
1817 import inspect
19 import struct
20 import pyasn1
21 import codecs
18 import logging
19
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 logger = logging.getLogger('pywerview_main_logger.ADObject')
60 logger.ULTRA = 5
61 self._logger = logger
62
63 self._attributes_dict = dict()
4864 self.add_attributes(attributes)
4965
5066 def add_attributes(self, attributes):
67 self._logger.log(self._logger.ULTRA,'ADObject instancied with the following attributes : {}'.format(attributes))
5168 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'):
69 self._attributes_dict[attr.lower()] = attributes[attr]
70
71 def __getattr__(self, attr):
72 try:
73 return self._attributes_dict[attr]
74 except KeyError:
75 if attr == 'isgroup':
7976 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
77 return 'group' in self._attributes_dict['objectclass']
78 except KeyError:
79 return False
80 raise AttributeError
81
82 # In this method, we try to pretty print common AD attributes
11083 def __str__(self):
11184 s = str()
112 members = inspect.getmembers(self, lambda x: not(inspect.isroutine(x)))
11385 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)
86 for attr in self._attributes_dict:
87 if len(attr) > max_length:
88 max_length = len(attr)
89 for attr in self._attributes_dict:
90 attribute = self._attributes_dict[attr]
91 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(attr, attribute))
92 if isinstance(attribute, list):
93 if any(isinstance(x, bytes) for x in attribute):
94 attribute = ['{}...'.format(x.hex()[:97]) for x in attribute]
95 attribute_temp = ', '.join(str(x) for x in attribute)
96 if len(attribute_temp) > 100:
97 attribute_temp = str()
98 line = str()
99 for x in attribute:
100 if len(line) + len(str(x)) <= 100:
101 line += '{}, '.format(x)
102 else:
103 attribute_temp += line + '\n' + ' ' * (max_length + 2)
104 line = str()
105 line += '{}, '.format(x)
106 attribute_temp += line + '\n' + ' ' * (max_length + 2)
107 attribute = attribute_temp.rstrip().rstrip(',')
108 elif isinstance(attribute, bytes):
109 attribute = '{}...'.format(attribute.hex()[:100])
110 elif isinstance(attribute, ADObject):
111 attribute = ('\n' + str(attribute)).replace('\n', '\n\t')
112
113 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
150114
151115 s = s[:-1]
152116 return s
154118 def __repr__(self):
155119 return str(self)
156120
157 class User(ADObject):
121 def to_json(self):
122 return self._attributes_dict
123
124 class ACE(ADObject):
125
158126 def __init__(self, attributes):
159127 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())
128
129 # We set iscallback, depending on the type of ACE
130 self._attributes_dict['iscallbak'] = ('CALLBACK' in self.acetype)
131
132 class User(ADObject):
133 pass
165134
166135 class Group(ADObject):
136 pass
137
138 class Computer(ADObject):
139 pass
140
141 class FileServer(ADObject):
142 pass
143
144 class DFS(ADObject):
145 pass
146
147 class OU(ADObject):
148 pass
149
150 class Site(ADObject):
151 pass
152
153 class Subnet(ADObject):
154 pass
155
156 class Trust(ADObject):
157
167158 def __init__(self, attributes):
159 logger = logging.getLogger('pywerview_main_logger.Trust')
160 self._logger = logger
168161 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
162 trust_attributes = self.trustattributes
163 trust_direction = self.trustdirection
225164 # If the filter SIDs attribute is not manually set, we check if we're
226165 # not in a use case where SIDs are implicitly filtered
227166 # 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):
167 if 'filter_sids' not in trust_attributes:
168 if not (trust_direction == 'disabled' or \
169 trust_direction == 'inbound' or \
170 'within_forest' in trust_attributes or \
171 'pim_trust' in trust_attributes):
172 if 'forest_transitive' in trust_attributes and 'treat_as_external' not in trust_attributes:
173 self._attributes_dict['trustattributes'].append('filter_sids')
174
175 # Pretty printing Trust object, we don't want to print all the attributes
176 # so we only print useful ones (trustattributes, trustdirection, trustpartner
177 # trusttype, whenchanged, whencreated)
240178 def __str__(self):
241179 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)
180 max_length = len('trustattributes')
181
182 for attr in self._attributes_dict:
183 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(attr, self._attributes_dict[attr]))
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 self._logger.debug('Ignoring : attribute name = {0}'.format(attr, self._attributes_dict[attr]))
190 continue
191 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
249192
250193 s = s[:-1]
251194 return s
195 pass
196
197 class GPO(ADObject):
198 pass
199
200 class PSO(ADObject):
201 pass
202
203 class GptTmpl(ADObject):
204 def to_json(self):
205 json_dict = {}
206 for k, v in self._attributes_dict.items():
207 json_dict[k] = v.to_json()
208 return json_dict
252209
253210 class GPOGroup(ADObject):
254211 pass
262219 class GPOLocation(ADObject):
263220 pass
264221
222 class GMSAAccount(ADObject):
223 pass
224
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
17 from __future__ import unicode_literals
18
17 import logging
1918 import inspect
2019
2120 class RPCObject:
2221 def __init__(self, obj):
22 logger = logging.getLogger('pywerview_main_logger.RPCObject')
23 logger.ULTRA = 5
24 self._logger = logger
25
2326 attributes = dict()
2427 try:
2528 for key in obj.fields.keys():
2932 self.add_attributes(attributes)
3033
3134 def add_attributes(self, attributes):
35 self._logger.log(self._logger.ULTRA,'RPCObject instancied with the following attributes : {}'.format(attributes))
3236 for key, value in attributes.items():
3337 key = key.lower()
3438 if key in ('wkui1_logon_domain', 'wkui1_logon_server',
3539 'wkui1_oth_domains', 'wkui1_username',
3640 'sesi10_cname', 'sesi10_username'):
3741 value = value.rstrip('\x00')
38
42
3943 setattr(self, key.lower(), value)
4044
4145 def __str__(self):
4751 if len(member[0]) > max_length:
4852 max_length = len(member[0])
4953 for member in members:
54 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(member[0], member[1]))
5055 if not member[0].startswith('_'):
5156 s += '{}: {}{}\n'.format(member[0], ' ' * (max_length - len(member[0])), member[1])
5257
5560
5661 def __repr__(self):
5762 return str(self)
63
64 def to_json(self):
65 members = inspect.getmembers(self, lambda x: not(inspect.isroutine(x)))
66 results = dict()
67 for member in members:
68 if not member[0].startswith('_'):
69 results[member[0]]=member[1]
70 return(results)
5871
5972 class TargetUser(RPCObject):
6073 pass
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
17 import sys
18 import logging
1719 import socket
1820 import ntpath
1921 import ldap3
22 import os
23 import tempfile
24
25 from ldap3.protocol.formatters.formatters import *
2026
2127 from impacket.smbconnection import SMBConnection
28 from impacket.smbconnection import SessionError
29 from impacket.krb5.ccache import CCache, Credential, CountedOctetString
30 from impacket.krb5 import constants
31 from impacket.krb5.types import Principal
2232 from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
2333 from impacket.dcerpc.v5 import transport, wkst, srvs, samr, scmr, drsuapi, epm
2434 from impacket.dcerpc.v5.dcom import wmi
2636 from impacket.dcerpc.v5.dcomrt import DCOMConnection
2737 from impacket.dcerpc.v5.rpcrt import DCERPCException
2838
39 import pywerview.formatters as fmt
40
2941 class LDAPRequester():
3042 def __init__(self, domain_controller, domain=str(), user=(), password=str(),
31 lmhash=str(), nthash=str()):
43 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False):
3244 self._domain_controller = domain_controller
3345 self._domain = domain
3446 self._user = user
3547 self._password = password
3648 self._lmhash = lmhash
3749 self._nthash = nthash
50 self._do_kerberos = do_kerberos
51 self._do_tls = do_tls
3852 self._queried_domain = None
3953 self._ads_path = None
4054 self._ads_prefix = None
4155 self._ldap_connection = None
4256 self._base_dn = None
4357
58 logger = logging.getLogger('pywerview_main_logger.LDAPRequester')
59 self._logger = logger
60
4461 def _get_netfqdn(self):
4562 try:
4663 smb = SMBConnection(self._domain_controller, self._domain_controller)
4764 except socket.error:
65 self._logger.warning('Socket error when opening the SMB connection')
4866 return str()
67
68 self._logger.debug('SMB loging parameters : user = {0} / password = {1} / domain = {2} '
69 '/ LM hash = {3} / NT hash = {4}'.format(self._user, self._password,
70 self._domain, self._lmhash,
71 self._nthash))
4972
5073 smb.login(self._user, self._password, domain=self._domain,
5174 lmhash=self._lmhash, nthash=self._nthash)
5477
5578 return fqdn
5679
80 def _patch_spn(self, creds, principal):
81 self._logger.debug('Patching principal to {}'.format(principal))
82
83 from pyasn1.codec.der import decoder, encoder
84 from impacket.krb5.asn1 import TGS_REP, Ticket
85
86 # Code is ~~based on~~ stolen from https://github.com/SecureAuthCorp/impacket/pull/1256
87 tgs = creds.toTGS(principal)
88 decoded_st = decoder.decode(tgs['KDC_REP'], asn1Spec=TGS_REP())[0]
89 decoded_st['ticket']['sname']['name-string'][0] = 'ldap'
90 decoded_st['ticket']['sname']['name-string'][1] = self._domain_controller.lower()
91 decoded_st['ticket']['realm'] = self._queried_domain.upper()
92
93 new_creds = Credential(data=creds.getData())
94 new_creds.ticket = CountedOctetString()
95 new_creds.ticket['data'] = encoder.encode(decoded_st['ticket'].clone(tagSet=Ticket.tagSet, cloneValueFlag=True))
96 new_creds.ticket['length'] = len(new_creds.ticket['data'])
97 new_creds['server'].fromPrincipal(Principal(principal, type=constants.PrincipalNameType.NT_PRINCIPAL.value))
98
99 return new_creds
100
57101 def _create_ldap_connection(self, queried_domain=str(), ads_path=str(),
58102 ads_prefix=str()):
59103 if not self._domain:
60 self._domain = self._get_netfqdn()
104 if self._do_kerberos:
105 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
106 self._domain = ccache.principal.realm['data'].decode('utf-8')
107 else:
108 try:
109 self._domain = self._get_netfqdn()
110 except SessionError as e:
111 self._logger.critical(e)
112 sys.exit(-1)
61113
62114 if not queried_domain:
63 queried_domain = self._get_netfqdn()
115 if self._do_kerberos:
116 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
117 queried_domain = ccache.principal.realm['data'].decode('utf-8')
118 else:
119 try:
120 queried_domain = self._get_netfqdn()
121 except SessionError as e:
122 self._logger.critical(e)
123 sys.exit(-1)
64124 self._queried_domain = queried_domain
65125
66126 base_dn = str()
81141 # base_dn is no longer used within `_create_ldap_connection()`, but I don't want to break
82142 # the function call. So we store it in an attriute and use it in `_ldap_search()`
83143 self._base_dn = base_dn
84
144
85145 # Format the username and the domain
86146 # 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
147 if self._do_kerberos:
148 user = '{}@{}'.format(self._user, self._domain.upper())
149 else:
150 user = '{}\\{}'.format(self._domain, self._user)
151
152 # Call custom formatters for several AD attributes
153 formatter = {'userAccountControl': fmt.format_useraccountcontrol,
154 'trustType': fmt.format_trusttype,
155 'trustDirection': fmt.format_trustdirection,
156 'trustAttributes': fmt.format_trustattributes,
157 'msDS-MaximumPasswordAge': format_ad_timedelta,
158 'msDS-MinimumPasswordAge': format_ad_timedelta,
159 'msDS-LockoutDuration': format_ad_timedelta,
160 'msDS-LockoutObservationWindow': format_ad_timedelta,
161 'msDS-GroupMSAMembership': fmt.format_groupmsamembership,
162 'msDS-ManagedPassword': fmt.format_managedpassword}
163
164 if self._do_tls:
165 ldap_scheme = 'ldaps'
166 self._logger.debug('LDAPS connection forced')
167 else:
168 ldap_scheme = 'ldap'
169 ldap_server = ldap3.Server('{}://{}'.format(ldap_scheme, self._domain_controller), formatter=formatter)
170 ldap_connection_kwargs = {'user': user, 'raise_exceptions': True}
171
172 # We build the authentication arguments depending on auth mode
173 if self._do_kerberos:
174 self._logger.debug('LDAP authentication with Keberos')
175 ldap_connection_kwargs['authentication'] = ldap3.SASL
176 ldap_connection_kwargs['sasl_mechanism'] = ldap3.KERBEROS
177
178 # Verifying if we have the correct TGS/TGT to interrogate the LDAP server
179 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
180 principal = 'ldap/{}@{}'.format(self._domain_controller.lower(), self._queried_domain.upper())
181
182 # We look for the TGS with the right SPN
183 creds = ccache.getCredential(principal, anySPN=False)
184 if creds:
185 self._logger.debug('TGS found in KRB5CCNAME file')
186 if creds['server'].prettyPrint().lower() != creds['server'].prettyPrint():
187 self._logger.debug('SPN not in lowercase, patching SPN')
188 new_creds = self._patch_spn(creds, principal)
189 # We build a new CCache with the new ticket
190 ccache.credentials.append(new_creds)
191 temp_ccache = tempfile.NamedTemporaryFile()
192 ccache.saveFile(temp_ccache.name)
193 cred_store = {'ccache': 'FILE:{}'.format(temp_ccache.name)}
194 else:
195 cred_store = dict()
196 else:
197 self._logger.debug('TGS not found in KRB5CCNAME, looking for '
198 'TGS with alternative SPN')
199 # If we don't find it, we search for any SPN
200 creds = ccache.getCredential(principal, anySPN=True)
201 if creds:
202 # If we find one, we build a custom TGS
203 self._logger.debug('Alternative TGS found, patching SPN')
204 new_creds = self._patch_spn(creds, principal)
205 # We build a new CCache with the new ticket
206 ccache.credentials.append(new_creds)
207 temp_ccache = tempfile.NamedTemporaryFile()
208 ccache.saveFile(temp_ccache.name)
209 cred_store = {'ccache': 'FILE:{}'.format(temp_ccache.name)}
210 else:
211 # If we don't find any, we hope for the best (TGT in cache)
212 self._logger.debug('Alternative TGS not found, using KRB5CCNAME as is '
213 'while hoping it contains a TGT')
214 cred_store = dict()
215 ldap_connection_kwargs['cred_store'] = cred_store
216 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
217 '/ Kerberos auth'.format(self._domain_controller, user))
218 else:
219 self._logger.debug('LDAP authentication with NTLM')
220 ldap_connection_kwargs['authentication'] = ldap3.NTLM
221 if self._lmhash and self._nthash:
222 ldap_connection_kwargs['password'] = '{}:{}'.format(self._lmhash, self._nthash)
223 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
224 '/ hash = {2}'.format(self._domain_controller, user, ldap_connection_kwargs['password']))
225 else:
226 ldap_connection_kwargs['password'] = self._password
227 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
228 '/ password = {2}'.format(self._domain_controller, user, ldap_connection_kwargs['password']))
229
230 try:
231 ldap_connection = ldap3.Connection(ldap_server, **ldap_connection_kwargs)
97232 try:
98233 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
234 except ldap3.core.exceptions.LDAPSocketOpenError as e:
235 self._logger.critical(e)
236 if self._do_tls:
237 self._logger.critical('TLS negociation failed, this error is mostly due to your host '
238 'not supporting SHA1 as signing algorithm for certificates')
239 sys.exit(-1)
240 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
241 # We need to try TLS
242 self._logger.warning('Server returns LDAPStrongerAuthRequiredResult, falling back to LDAPS')
243 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller), formatter=formatter)
244 ldap_connection = ldap3.Connection(ldap_server, **ldap_connection_kwargs)
112245 try:
113246 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()
247 except ldap3.core.exceptions.LDAPSocketOpenError as e:
248 self._logger.critical(e)
249 self._logger.critical('TLS negociation failed, this error is mostly due to your host '
250 'not supporting SHA1 as signing algorithm for certificates')
251 sys.exit(-1)
121252
122253 self._ldap_connection = ldap_connection
123254
124 def _ldap_search(self, search_filter, class_result, attributes=list()):
255 def _ldap_search(self, search_filter, class_result, attributes=list(), controls=list()):
125256 results = list()
126
127 # if no attribute name specified, we return all attributes
257
258 # if no attribute name specified, we return all attributes
128259 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
142 for result in search_results:
143 if result['type'] is not 'searchResEntry':
144 continue
145 results.append(class_result(result['raw_attributes']))
260 attributes = ldap3.ALL_ATTRIBUTES
261
262 self._logger.debug('search_base = {0} / search_filter = {1} / attributes = {2}'.format(self._base_dn,
263 search_filter,
264 attributes))
265
266 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
267 search_results=self._ldap_connection.extend.standard.paged_search(search_base=self._base_dn,
268 search_filter=search_filter, attributes=attributes,
269 controls=controls, paged_size=1000, generator=True)
270
271 try:
272 # Skip searchResRef
273 for result in search_results:
274 if result['type'] != 'searchResEntry':
275 continue
276 results.append(class_result(result['attributes']))
277
278 except ldap3.core.exceptions.LDAPAttributeError as e:
279 self._logger.critical(e)
280 sys.exit(-1)
281
282 if not results:
283 self._logger.debug('Query returned an empty result')
146284
147285 return results
148286
172310 try:
173311 self._ldap_connection.unbind()
174312 except AttributeError:
313 self._logger.warning('Error when unbinding')
175314 pass
176315 self._ldap_connection = None
177316
178317 class RPCRequester():
179318 def __init__(self, target_computer, domain=str(), user=(), password=str(),
180 lmhash=str(), nthash=str()):
319 lmhash=str(), nthash=str(), do_kerberos=False):
181320 self._target_computer = target_computer
182321 self._domain = domain
183322 self._user = user
184323 self._password = password
185324 self._lmhash = lmhash
186325 self._nthash = nthash
326 self._do_kerberos = do_kerberos
187327 self._pipe = None
188328 self._rpc_connection = None
189329 self._dcom = None
190330 self._wmi_connection = None
331
332 logger = logging.getLogger('pywerview_main_logger.RPCRequester')
333 self._logger = logger
191334
192335 def _create_rpc_connection(self, pipe):
193336 # Here we build the DCE/RPC connection
212355 rpctransport = transport.SMBTransport(self._target_computer, 445, self._pipe,
213356 username=self._user, password=self._password,
214357 domain=self._domain, lmhash=self._lmhash,
215 nthash=self._nthash)
358 nthash=self._nthash, doKerberos=self._do_kerberos)
216359
217360 rpctransport.set_connect_timeout(10)
218361 dce = rpctransport.get_dce_rpc()
222365
223366 try:
224367 dce.connect()
225 except socket.error:
368 except Exception as e:
369 self._logger.critical('Error when creating RPC connection')
370 self._logger.critical(e)
226371 self._rpc_connection = None
227372 else:
228373 dce.bind(binding_strings[self._pipe[1:]])
231376 def _create_wmi_connection(self, namespace='root\\cimv2'):
232377 try:
233378 self._dcom = DCOMConnection(self._target_computer, self._user, self._password,
234 self._domain, self._lmhash, self._nthash)
235 except DCERPCException:
379 self._domain, self._lmhash, self._nthash, doKerberos=self._do_kerberos)
380 except Exception as e:
381 self._logger.critical('Error when creating WMI connection')
382 self._logger.critical(e)
236383 self._dcom = None
237384 else:
238385 i_interface = self._dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
283430
284431 class LDAPRPCRequester(LDAPRequester, RPCRequester):
285432 def __init__(self, target_computer, domain=str(), user=(), password=str(),
286 lmhash=str(), nthash=str(), domain_controller=str()):
433 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
434 domain_controller=str()):
287435 # If no domain controller was given, we assume that the user wants to
288436 # target a domain controller to perform LDAP requests against
289437 if not domain_controller:
290438 domain_controller = target_computer
291439 LDAPRequester.__init__(self, domain_controller, domain, user, password,
292 lmhash, nthash)
440 lmhash, nthash, do_kerberos, do_tls)
293441 RPCRequester.__init__(self, target_computer, domain, user, password,
294 lmhash, nthash)
442 lmhash, nthash, do_kerberos)
443
444 logger = logging.getLogger('pywerview_main_logger.LDAPRPCRequester')
445 self._logger = logger
446
295447 def __enter__(self):
296448 try:
297449 LDAPRequester.__enter__(self)
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 from multiprocessing import Process, Pipe
1818
2121 import pywerview.objects.rpcobjects as rpcobj
2222
2323 class HunterWorker(Process):
24 def __init__(self, pipe, domain, user, password, lmhash, nthash):
24 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos):
2525 Process.__init__(self)
2626 self._pipe = pipe
2727 self._domain = domain
2929 self._password = password
3030 self._lmhash = lmhash
3131 self._nthash = nthash
32 self._do_kerberos = do_kerberos
3233
3334 def terminate(self):
3435 self._pipe.close()
4142 self._pipe.send(result)
4243
4344 class UserHunterWorker(HunterWorker):
44 def __init__(self, pipe, domain, user, password, lmhash, nthash, foreign_users,
45 stealth, target_users, domain_short_name, check_access):
46 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
45 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
46 foreign_users, stealth, target_users, domain_short_name, check_access):
47 HunterWorker.__init__(self, pipe, domain, user, password, lmhash,
48 nthash, do_kerberos)
4749 self._foreign_users = foreign_users
4850 self._stealth = stealth
4951 self._target_users = target_users
5759 # First, we get every distant session on the target computer
5860 distant_sessions = list()
5961 with NetRequester(target_computer, self._domain, self._user, self._password,
60 self._lmhash, self._nthash) as net_requester:
62 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
6163 if not self._foreign_users:
6264 distant_sessions += net_requester.get_netsession()
6365 if not self._stealth:
98100
99101 if self._check_access:
100102 with Misc(target_computer, self._domain, self._user, self._password,
101 self._lmhash, self._nthash) as misc_requester:
103 self._lmhash, self._nthash, self._do_kerberos) as misc_requester:
102104 attributes['localadmin'] = misc_requester.invoke_checklocaladminaccess()
103105 else:
104106 attributes['localadmin'] = str()
108110 return results
109111
110112 class ProcessHunterWorker(HunterWorker):
111 def __init__(self, pipe, domain, user, password, lmhash, nthash, process_name,
112 target_users):
113 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
113 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
114 process_name, target_users):
115 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos)
114116 self._process_name = process_name
115117 self._target_users = target_users
116118
119121
120122 distant_processes = list()
121123 with NetRequester(target_computer, self._domain, self._user, self._password,
122 self._lmhash, self._nthash) as net_requester:
124 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
123125 distant_processes = net_requester.get_netprocess()
124126
125127 for process in distant_processes:
135137 return results
136138
137139 class EventHunterWorker(HunterWorker):
138 def __init__(self, pipe, domain, user, password, lmhash, nthash, search_days,
139 target_users):
140 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
140 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
141 search_days, target_users):
142 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos)
141143 self._target_users = target_users
142144 self._search_days = search_days
143145
146148
147149 distant_processes = list()
148150 with NetRequester(target_computer, self._domain, self._user, self._password,
149 self._lmhash, self._nthash) as net_requester:
151 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
150152 distant_events = net_requester.get_userevent(date_start=self._search_days)
151153
152154 for event in distant_events:
156158 results.append(event)
157159
158160 return results
161
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1818
1919 from pywerview.cli.main import main
2020
00 impacket>=0.9.22
11 bs4
22 lxml
3 pyasn1
4 ldap3>=2.8.1
5 gssapi
00 [metadata]
1 description-file = README.md
1 description_file = README.md
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.4.0',
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',
2926 install_requires=[
3027 'impacket>=0.9.22',
3128 'bs4',
32 'lxml'
29 'lxml',
30 'pyasn1',
31 'ldap3>=2.8.1',
32 'gssapi'
3333 ],
3434 entry_points = {
3535 'console_scripts': ['pywerview=pywerview.cli.main:main'],