From b9e3e2ea4060ec9605749abe2f9021652c4729e6 Mon Sep 17 00:00:00 2001 From: spyr0 Date: Mon, 8 Aug 2022 16:47:57 +0100 Subject: [PATCH 1/6] whoami LDAP module --- cme/modules/whoami.py | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 cme/modules/whoami.py diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py new file mode 100644 index 00000000..b2867173 --- /dev/null +++ b/cme/modules/whoami.py @@ -0,0 +1,57 @@ +from ldap3 import Server, Connection, NTLM, ALL + +class CMEModule: + ''' + Sanity check of current user groups and privileges + Module by spyr0 (@spyr0-sec) + ''' + name = 'whoami' + description = 'Get privileges of provided user' + supported_protocols = ['ldap'] + opsec_safe = True #Does the module touch disk? + multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time? + + def options(self, context, module_options): + ''' + No options required + ''' + pass + + def on_login(self, context, connection): + + # Grab the variables from the CME connection to fill our variables + inputUser = connection.domain + '\\' + connection.username + inputPassword = connection.password + dcTarget = connection.conn.getRemoteHost() + + try: + # Connect and bind to the LDAP server + ldapServer = Server(dcTarget, use_ssl=False, port=389, get_info=ALL) + ldapConn = Connection(ldapServer, user=inputUser, password=inputPassword, authentication=NTLM, auto_bind=True) + + # https://github.com/pycontribs/python3-ldap/blob/master/python3-ldap/ldap3/protocol/rfc4512.py + searchBase = ldapServer.info.naming_contexts[0] + searchFilter = f'(sAMAccountName={connection.username})' + + context.log.debug(f'Using naming context: {searchBase} and {searchFilter} as search filter') + + # Confirm login / get username + context.log.highlight(f'Username: {ldapConn.extend.standard.who_am_i().replace("u:","")}') + + # Get attributes of provided user + ldapConn.search(search_base=searchBase,search_filter=searchFilter,attributes=['description','distinguishedName', 'memberOf', 'name', 'pwdLastSet']) + + for response in ldapConn.response: + context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") + context.log.highlight(f"Human name: {response['attributes']['name']}") + context.log.highlight(f"Description: {response['attributes']['description'][0]}") + context.log.highlight(f"Password last set: {response['attributes']['pwdLastSet']}") + + for group in response['attributes']['memberOf']: + context.log.highlight(f'Member of: {group}') + + # Only want output from first response + break + + except Exception as e: + context.log.error(f'UNEXPECTED ERROR: {e}') \ No newline at end of file From d520ecc7a523b8af022e1a9f09375958a9bf400d Mon Sep 17 00:00:00 2001 From: spyr0 Date: Mon, 8 Aug 2022 17:18:45 +0100 Subject: [PATCH 2/6] Fixed description output --- cme/modules/whoami.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py index b2867173..0d3e0a7f 100644 --- a/cme/modules/whoami.py +++ b/cme/modules/whoami.py @@ -2,18 +2,18 @@ from ldap3 import Server, Connection, NTLM, ALL class CMEModule: ''' - Sanity check of current user groups and privileges + Basic enumeration of provided user information and privileges Module by spyr0 (@spyr0-sec) ''' name = 'whoami' - description = 'Get privileges of provided user' + description = 'Get details of provided user' supported_protocols = ['ldap'] opsec_safe = True #Does the module touch disk? multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time? def options(self, context, module_options): ''' - No options required + No options requireds ''' pass @@ -44,7 +44,7 @@ class CMEModule: for response in ldapConn.response: context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") context.log.highlight(f"Human name: {response['attributes']['name']}") - context.log.highlight(f"Description: {response['attributes']['description'][0]}") + context.log.highlight(f"Description: {response['attributes']['description']}") context.log.highlight(f"Password last set: {response['attributes']['pwdLastSet']}") for group in response['attributes']['memberOf']: From 293e9a91641ee7a056a0bc8b19c2d9788fd701d9 Mon Sep 17 00:00:00 2001 From: spyr0 Date: Thu, 11 Aug 2022 10:54:05 +0100 Subject: [PATCH 3/6] Added SamAccountName option, more attributes and error handling --- cme/modules/whoami.py | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py index 0d3e0a7f..251439fc 100644 --- a/cme/modules/whoami.py +++ b/cme/modules/whoami.py @@ -13,9 +13,11 @@ class CMEModule: def options(self, context, module_options): ''' - No options requireds + USER Enumerate information about a different SamAccountName ''' - pass + self.username = None + if 'USER' in module_options: + self.username = module_options['USER'] def on_login(self, context, connection): @@ -31,21 +33,42 @@ class CMEModule: # https://github.com/pycontribs/python3-ldap/blob/master/python3-ldap/ldap3/protocol/rfc4512.py searchBase = ldapServer.info.naming_contexts[0] - searchFilter = f'(sAMAccountName={connection.username})' + if self.username is None: + searchFilter = f'(sAMAccountName={connection.username})' + else: + searchFilter = f'(sAMAccountName={format(self.username)})' + context.log.debug(f'Using naming context: {searchBase} and {searchFilter} as search filter') # Confirm login / get username - context.log.highlight(f'Username: {ldapConn.extend.standard.who_am_i().replace("u:","")}') + context.log.debug(f'Running LDAP queries as: {ldapConn.extend.standard.who_am_i().replace("u:","")}') # Get attributes of provided user - ldapConn.search(search_base=searchBase,search_filter=searchFilter,attributes=['description','distinguishedName', 'memberOf', 'name', 'pwdLastSet']) + ldapConn.search(search_base=searchBase,search_filter=searchFilter, + attributes=['name','sAmAccountName','description','distinguishedName','pwdLastSet','logonCount','lastLogon','userAccountControl','memberOf']) for response in ldapConn.response: - context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") context.log.highlight(f"Human name: {response['attributes']['name']}") + context.log.highlight(f"Username: {response['attributes']['sAmAccountName']}") context.log.highlight(f"Description: {response['attributes']['description']}") + context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") context.log.highlight(f"Password last set: {response['attributes']['pwdLastSet']}") + context.log.highlight(f"Logon count: {response['attributes']['logonCount']}") + context.log.highlight(f"Last logon: {response['attributes']['lastLogon']}") + + if response['attributes']['userAccountControl'] == 512: + context.log.highlight(f"Enabled: Yes") + context.log.highlight(f"Password Never Expires: No") + if response['attributes']['userAccountControl'] == 514: + context.log.highlight(f"Enabled: No") + context.log.highlight(f"Password Never Expires: No") + if response['attributes']['userAccountControl'] == 66048: + context.log.highlight(f"Enabled: Yes") + context.log.highlight(f"Password Never Expires: Yes") + if response['attributes']['userAccountControl'] == 66050: + context.log.highlight(f"Enabled: No") + context.log.highlight(f"Password Never Expires: Yes") for group in response['attributes']['memberOf']: context.log.highlight(f'Member of: {group}') @@ -53,5 +76,8 @@ class CMEModule: # Only want output from first response break + except KeyError: + context.log.error(f'Username does not exist') + except Exception as e: - context.log.error(f'UNEXPECTED ERROR: {e}') \ No newline at end of file + context.log.error(f'UNEXPECTED ERROR: {repr(e)}') From 653a7107596e5c8e5e3cd66b226c600ea8a84e3a Mon Sep 17 00:00:00 2001 From: spyr0 Date: Thu, 11 Aug 2022 11:12:31 +0100 Subject: [PATCH 4/6] Added ServicePrincipalNames --- cme/modules/whoami.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py index 251439fc..d84c50a1 100644 --- a/cme/modules/whoami.py +++ b/cme/modules/whoami.py @@ -46,7 +46,7 @@ class CMEModule: # Get attributes of provided user ldapConn.search(search_base=searchBase,search_filter=searchFilter, - attributes=['name','sAmAccountName','description','distinguishedName','pwdLastSet','logonCount','lastLogon','userAccountControl','memberOf']) + attributes=['name','sAmAccountName','description','distinguishedName','pwdLastSet','logonCount','lastLogon','userAccountControl','servicePrincipalName','memberOf']) for response in ldapConn.response: context.log.highlight(f"Human name: {response['attributes']['name']}") @@ -68,8 +68,13 @@ class CMEModule: context.log.highlight(f"Password Never Expires: Yes") if response['attributes']['userAccountControl'] == 66050: context.log.highlight(f"Enabled: No") - context.log.highlight(f"Password Never Expires: Yes") + context.log.highlight(f"Password Never Expires: Yes") + if len(response['attributes']['servicePrincipalName']) != 0: + context.log.highlight(f"Service Account Name(s) found - Potentially Kerberoastable user!") + for spn in response['attributes']['servicePrincipalName']: + context.log.highlight(f"Service Account Name: {spn}") + for group in response['attributes']['memberOf']: context.log.highlight(f'Member of: {group}') From 8acbc3bba2dc9fe6920acf38f0d98cdaefb179f1 Mon Sep 17 00:00:00 2001 From: spyr0 Date: Thu, 11 Aug 2022 12:10:19 +0100 Subject: [PATCH 5/6] Never logged on handling --- cme/modules/whoami.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py index d84c50a1..c1035f90 100644 --- a/cme/modules/whoami.py +++ b/cme/modules/whoami.py @@ -55,7 +55,11 @@ class CMEModule: context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") context.log.highlight(f"Password last set: {response['attributes']['pwdLastSet']}") context.log.highlight(f"Logon count: {response['attributes']['logonCount']}") - context.log.highlight(f"Last logon: {response['attributes']['lastLogon']}") + + if '1601' in str(response['attributes']['lastLogon']): + context.log.highlight(f"Last logon: Never") + else: + context.log.highlight(f"Last logon: {response['attributes']['lastLogon']}") if response['attributes']['userAccountControl'] == 512: context.log.highlight(f"Enabled: Yes") From 91be977ebbddb170c7e982b5e43c4e8622cde479 Mon Sep 17 00:00:00 2001 From: mpgn Date: Thu, 8 Sep 2022 15:04:04 -0400 Subject: [PATCH 6/6] update module ldap whoami --- cme/modules/whoami.py | 91 ++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/cme/modules/whoami.py b/cme/modules/whoami.py index c1035f90..c6e25ec6 100644 --- a/cme/modules/whoami.py +++ b/cme/modules/whoami.py @@ -20,20 +20,7 @@ class CMEModule: self.username = module_options['USER'] def on_login(self, context, connection): - - # Grab the variables from the CME connection to fill our variables - inputUser = connection.domain + '\\' + connection.username - inputPassword = connection.password - dcTarget = connection.conn.getRemoteHost() - - try: - # Connect and bind to the LDAP server - ldapServer = Server(dcTarget, use_ssl=False, port=389, get_info=ALL) - ldapConn = Connection(ldapServer, user=inputUser, password=inputPassword, authentication=NTLM, auto_bind=True) - - # https://github.com/pycontribs/python3-ldap/blob/master/python3-ldap/ldap3/protocol/rfc4512.py - searchBase = ldapServer.info.naming_contexts[0] - + searchBase = connection.ldapConnection._baseDN if self.username is None: searchFilter = f'(sAMAccountName={connection.username})' else: @@ -41,52 +28,40 @@ class CMEModule: context.log.debug(f'Using naming context: {searchBase} and {searchFilter} as search filter') - # Confirm login / get username - context.log.debug(f'Running LDAP queries as: {ldapConn.extend.standard.who_am_i().replace("u:","")}') - # Get attributes of provided user - ldapConn.search(search_base=searchBase,search_filter=searchFilter, - attributes=['name','sAmAccountName','description','distinguishedName','pwdLastSet','logonCount','lastLogon','userAccountControl','servicePrincipalName','memberOf']) + r = connection.ldapConnection.search( + searchBase=searchBase, + searchFilter=searchFilter, + attributes=['name','sAmAccountName','description','distinguishedName','pwdLastSet','logonCount','lastLogon','userAccountControl','servicePrincipalName','memberOf'], + sizeLimit=999 - for response in ldapConn.response: - context.log.highlight(f"Human name: {response['attributes']['name']}") - context.log.highlight(f"Username: {response['attributes']['sAmAccountName']}") - context.log.highlight(f"Description: {response['attributes']['description']}") - context.log.highlight(f"Distinguished name: {response['attributes']['distinguishedName']}") - context.log.highlight(f"Password last set: {response['attributes']['pwdLastSet']}") - context.log.highlight(f"Logon count: {response['attributes']['logonCount']}") - - if '1601' in str(response['attributes']['lastLogon']): - context.log.highlight(f"Last logon: Never") - else: - context.log.highlight(f"Last logon: {response['attributes']['lastLogon']}") - - if response['attributes']['userAccountControl'] == 512: - context.log.highlight(f"Enabled: Yes") - context.log.highlight(f"Password Never Expires: No") - if response['attributes']['userAccountControl'] == 514: - context.log.highlight(f"Enabled: No") - context.log.highlight(f"Password Never Expires: No") - if response['attributes']['userAccountControl'] == 66048: - context.log.highlight(f"Enabled: Yes") - context.log.highlight(f"Password Never Expires: Yes") - if response['attributes']['userAccountControl'] == 66050: - context.log.highlight(f"Enabled: No") - context.log.highlight(f"Password Never Expires: Yes") - - if len(response['attributes']['servicePrincipalName']) != 0: + ) + for response in r[0]['attributes']: + if 'userAccountControl' in str(response['type']): + if str(response['vals'][0]) == "512": + context.log.highlight(f"Enabled: Yes") + context.log.highlight(f"Password Never Expires: No") + elif str(response['vals'][0]) == "514": + context.log.highlight(f"Enabled: No") + context.log.highlight(f"Password Never Expires: No") + elif str(response['vals'][0]) == "66048": + context.log.highlight(f"Enabled: Yes") + context.log.highlight(f"Password Never Expires: Yes") + elif str(response['vals'][0]) == "66050": + context.log.highlight(f"Enabled: No") + context.log.highlight(f"Password Never Expires: Yes") + elif 'lastLogon' in str(response['type']): + if str(response['vals'][0]) == "1601": + context.log.highlight(f"Last logon: Never") + else: + context.log.highlight(f"Last logon: {response['vals'][0]}") + elif 'memberOf' in str(response['type']): + for group in response['vals']: + context.log.highlight(f'Member of: {group}') + elif 'servicePrincipalName' in str(response['type']): context.log.highlight(f"Service Account Name(s) found - Potentially Kerberoastable user!") - for spn in response['attributes']['servicePrincipalName']: + for spn in response['vals']: context.log.highlight(f"Service Account Name: {spn}") - - for group in response['attributes']['memberOf']: - context.log.highlight(f'Member of: {group}') + else: + context.log.highlight(response['type'] + ": " + response['vals'][0]) - # Only want output from first response - break - - except KeyError: - context.log.error(f'Username does not exist') - - except Exception as e: - context.log.error(f'UNEXPECTED ERROR: {repr(e)}')