Skip to content

Commit

Permalink
Merge pull request #60 from pbalmelle/develop
Browse files Browse the repository at this point in the history
Added support for sMSA
  • Loading branch information
ThePirateWhoSmellsOfSunflowers authored Jun 15, 2023
2 parents 4336964 + 59068a7 commit ed9a0b0
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 18 deletions.
17 changes: 15 additions & 2 deletions pywerview/cli/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,32 @@ def get_adobject(domain_controller, domain, user, password=str(),
queried_sam_account_name=queried_sam_account_name,
ads_path=ads_path, attributes=attributes, custom_filter=custom_filter)

def get_adserviceaccount(domain_controller, domain, user, password=str(),
def get_netgmsa(domain_controller, domain, user, password=str(),
lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
user_cert=str(), user_key=str(),
queried_domain=str(), queried_sid=str(), queried_name=str(),
queried_sam_account_name=str(), ads_path=str(), resolve_sids=False):
requester = NetRequester(domain_controller, domain, user, password,
lmhash, nthash, do_kerberos, do_tls,
user_cert, user_key)
return requester.get_adserviceaccount(queried_domain=queried_domain,
return requester.get_netgmsa(queried_domain=queried_domain,
queried_sid=queried_sid, queried_name=queried_name,
queried_sam_account_name=queried_sam_account_name,
ads_path=ads_path, resolve_sids=resolve_sids)

def get_netsmsa(domain_controller, domain, user, password=str(),
lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
user_cert=str(), user_key=str(),
queried_domain=str(), queried_sid=str(), queried_name=str(),
queried_sam_account_name=str(), ads_path=str()):
requester = NetRequester(domain_controller, domain, user, password,
lmhash, nthash, do_kerberos, do_tls,
user_cert, user_key)
return requester.get_netsmsa(queried_domain=queried_domain,
queried_sid=queried_sid, queried_name=queried_name,
queried_sam_account_name=queried_sam_account_name,
ads_path=ads_path)

def get_objectacl(domain_controller, domain, user, password=str(),
lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
queried_domain=str(), queried_sid=str(), queried_name=str(),
Expand Down
34 changes: 25 additions & 9 deletions pywerview/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,40 @@ def main():
default=[], help='Object attributes to return')
get_adobject_parser.set_defaults(func=get_adobject)

# Parser for the get-adserviceaccount command
get_adserviceaccount_parser = subparsers.add_parser('get-adserviceaccount', help='Returns a list of all the '\
# Parser for the get-netgmsa command
get_netgmsa_parser = subparsers.add_parser('get-netgmsa', help='Returns a list of all the '\
'gMSA of the specified domain. To retrieve passwords, you need a privileged account and '\
'a TLS connection to the LDAP server (use the --tls switch).',
parents=[ad_parser, logging_parser, json_output_parser, certificate_parser])
get_adserviceaccount_parser.add_argument('--sid', dest='queried_sid',
get_netgmsa_parser.add_argument('--sid', dest='queried_sid',
help='SID to query (wildcards accepted)')
get_adserviceaccount_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
get_netgmsa_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
help='samAccountName to query (wildcards accepted)')
get_adserviceaccount_parser.add_argument('--name', dest='queried_name',
get_netgmsa_parser.add_argument('--name', dest='queried_name',
help='Name to query (wildcards accepted)')
get_adserviceaccount_parser.add_argument('-d', '--domain', dest='queried_domain',
get_netgmsa_parser.add_argument('-d', '--domain', dest='queried_domain',
help='Domain to query')
get_adserviceaccount_parser.add_argument('-a', '--ads-path',
get_netgmsa_parser.add_argument('-a', '--ads-path',
help='Additional ADS path')
get_adserviceaccount_parser.add_argument('--resolve-sids', dest='resolve_sids',
get_netgmsa_parser.add_argument('--resolve-sids', dest='resolve_sids',
action='store_true', help='Resolve SIDs when querying PrincipalsAllowedToRetrieveManagedPassword')
get_adserviceaccount_parser.set_defaults(func=get_adserviceaccount)
get_netgmsa_parser.set_defaults(func=get_netgmsa)

# Parser for the get-netsmsa command
get_netsmsa_parser = subparsers.add_parser('get-netsmsa', help='Returns a list of all the '\
'sMSA of the specified domain.',
parents=[ad_parser, logging_parser, json_output_parser, certificate_parser])
get_netsmsa_parser.add_argument('--sid', dest='queried_sid',
help='SID to query (wildcards accepted)')
get_netsmsa_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
help='samAccountName to query (wildcards accepted)')
get_netsmsa_parser.add_argument('--name', dest='queried_name',
help='Name to query (wildcards accepted)')
get_netsmsa_parser.add_argument('-d', '--domain', dest='queried_domain',
help='Domain to query')
get_netsmsa_parser.add_argument('-a', '--ads-path',
help='Additional ADS path')
get_netsmsa_parser.set_defaults(func=get_netsmsa)

# Parser for the get-objectacl command
get_objectacl_parser = subparsers.add_parser('get-objectacl', help='Takes a domain SID, '\
Expand Down
40 changes: 33 additions & 7 deletions pywerview/functions/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def get_adobject(self, queried_domain=str(), queried_sid=str(),
return self._ldap_search(object_filter, adobj.ADObject, attributes=attributes)

@LDAPRPCRequester._ldap_connection_init
def get_adserviceaccount(self, queried_domain=str(), queried_sid=str(),
def get_netgmsa(self, queried_domain=str(), queried_sid=str(),
queried_name=str(), queried_sam_account_name=str(),
ads_path=str(), resolve_sids=False):
filter_objectclass = '(ObjectClass=msDS-GroupManagedServiceAccount)'
Expand All @@ -69,10 +69,10 @@ def get_adserviceaccount(self, queried_domain=str(), queried_sid=str(),
else:
object_filter = '(&(name=*){})'.format(filter_objectclass)

adserviceaccounts = self._ldap_search(object_filter, adobj.GMSAAccount, attributes=attributes)
gmsa = self._ldap_search(object_filter, adobj.GMSAAccount, attributes=attributes)

# In this loop, we resolve SID (if true) and we populate 'enabled' attribute
for i, adserviceaccount in enumerate(adserviceaccounts):
for i, adserviceaccount in enumerate(gmsa):
if resolve_sids:
results = list()
for sid in getattr(adserviceaccount, 'msds-groupmsamembership'):
Expand All @@ -83,10 +83,36 @@ def get_adserviceaccount(self, queried_domain=str(), queried_sid=str(),
self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(sid))
resolved_sid = sid
results.append(resolved_sid)
adserviceaccounts[i].add_attributes({'msds-groupmsamembership': results})
adserviceaccounts[i].add_attributes({'Enabled': 'ACCOUNTDISABLE' not in adserviceaccount.useraccountcontrol})
adserviceaccounts[i]._attributes_dict.pop('useraccountcontrol')
return adserviceaccounts
gmsa[i].add_attributes({'msds-groupmsamembership': results})
gmsa[i].add_attributes({'Enabled': 'ACCOUNTDISABLE' not in adserviceaccount.useraccountcontrol})
gmsa[i]._attributes_dict.pop('useraccountcontrol')

return gmsa

@LDAPRPCRequester._ldap_connection_init
def get_netsmsa(self, queried_domain=str(), queried_sid=str(),
queried_name=str(), queried_sam_account_name=str(),
ads_path=str()):

filter_objectclass = '(ObjectClass=msDS-ManagedServiceAccount)'
attributes = ['samaccountname', 'distinguishedname', 'objectsid', 'description',
'msds-hostserviceaccountbl', 'useraccountcontrol']
for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
('samAccountName', escape_filter_chars(queried_sam_account_name))):
if attr_value:
object_filter = '(&({}={}){})'.format(attr_desc, attr_value, filter_objectclass)
break
else:
object_filter = '(&(name=*){})'.format(filter_objectclass)

smsas = self._ldap_search(object_filter, adobj.SMSAAccount, attributes=attributes)

# In this loop, we populate 'enabled' attribute
for i, adserviceaccount in enumerate(smsas):
smsas[i].add_attributes({'Enabled': 'ACCOUNTDISABLE' not in adserviceaccount.useraccountcontrol})
smsas[i]._attributes_dict.pop('useraccountcontrol')

return smsas

@LDAPRPCRequester._ldap_connection_init
def get_objectacl(self, queried_domain=str(), queried_sid=str(),
Expand Down
2 changes: 2 additions & 0 deletions pywerview/objects/adobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,5 @@ class GPOLocation(ADObject):
class GMSAAccount(ADObject):
pass

class SMSAAccount(ADObject):
pass

0 comments on commit ed9a0b0

Please sign in to comment.