def formatGrepAttribute(self, att): aname = att.key.lower() #User flags if aname == 'useraccountcontrol': return ', '.join(self.parseFlags(att, uac_flags)) #List of groups if aname == 'member' or aname == 'memberof' and type( att.values) is list: return self.formatGroupsGrep(att.values) if aname == 'primarygroupid': return self.formatGroupsGrep([self.dd.groups_dnmap[att.value]]) #Domain trust flags if aname == 'trustattributes': return ', '.join(self.parseFlags(att, trust_flags)) if aname == 'trustdirection': if att.value == 0: return 'DISABLED' else: return ', '.join( self.parseTrustDirection(att, trust_directions)) if aname == 'trusttype': return ', '.join(self.parseFlags(att, trust_type)) if aname == 'securityidentifier': return format_sid(att.raw_values[0]) #Pwd flags if aname == 'pwdproperties': return ', '.join(self.parseFlags(att, pwd_flags)) if aname == 'minpwdage' or aname == 'maxpwdage': return '%.2f days' % self.nsToDays(att.value) if aname == 'lockoutobservationwindow' or aname == 'lockoutduration': return '%.1f minutes' % self.nsToMinutes(att.value) return self.formatString(att.value)
def formatGrepAttribute(self, att): aname = att.key.lower() #User flags if aname == 'useraccountcontrol': return ', '.join(self.parseFlags(att, uac_flags)) #List of groups if aname == 'member' or aname == 'memberof' and type(att.values) is list: return self.formatGroupsGrep(att.values) if aname == 'primarygroupid': try: return self.formatGroupsGrep([self.dd.groups_dnmap[att.value]]) except KeyError: return 'NOT FOUND!' #Domain trust flags if aname == 'trustattributes': return ', '.join(self.parseFlags(att, trust_flags)) if aname == 'trustdirection': if att.value == 0: return 'DISABLED' else: return ', '.join(self.parseTrustDirection(att, trust_directions)) if aname == 'trusttype': return ', '.join(self.parseFlags(att, trust_type)) if aname == 'securityidentifier': return format_sid(att.raw_values[0]) #Pwd flags if aname == 'pwdproperties': return ', '.join(self.parseFlags(att, pwd_flags)) if aname == 'minpwdage' or aname == 'maxpwdage': return '%.2f days' % self.nsToDays(att.value) if aname == 'lockoutobservationwindow' or aname == 'lockoutduration': return '%.1f minutes' % self.nsToMinutes(att.value) return self.formatString(att.value)
def get_enrollment_principals(entry): # Mostly taken from github.com/ly4k/Certipy/certipy/security.py sd = ldaptypes.SR_SECURITY_DESCRIPTOR() sd.fromString(entry["raw_attributes"]["nTSecurityDescriptor"][0]) enrollment_uuids = [ "00000000-0000-0000-0000-000000000000", # All-Extended-Rights "0e10c968-78fb-11d2-90d4-00c04f79dc55", # Certificate-Enrollment "a05b8cc2-17bc-4802-a710-e7c15ab866a2", # Certificate-AutoEnrollment ] enrollment_principals = set() for ace in (a for a in sd["Dacl"]["Data"] if a["AceType"] == ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE): sid = format_sid(ace["Ace"]["Sid"].getData()) if ace["Ace"]["ObjectTypeLen"] == 0: uuid = bin_to_string( ace["Ace"]["InheritedObjectType"]).lower() else: uuid = bin_to_string(ace["Ace"]["ObjectType"]).lower() if not uuid in enrollment_uuids: continue enrollment_principals.add(sid) return enrollment_principals
def get_user_info(self, samname): self.ldap_session.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid']) try: dn = self.ldap_session.entries[0].entry_dn sid = format_sid(self.ldap_session.entries[0]['objectSid'].raw_values[0]) return dn, sid except IndexError: logging.error('User not found in LDAP: %s' % samname) return False
def parse_aces(input_buffer, count): """ Parses the list of ACEs. """ out = [] while len(out) < count: ace = dict() fields = ('Raw Type', 'Raw Flags', 'Size', 'Raw Access Required') for k, v in zip(fields, unpack('<BBHI', input_buffer[:8])): ace[k] = v ace['Type'] = parse_sddl_dacl_ace_type(ace['Raw Type']) ace['Access Required'] = parse_ace_access(ace['Raw Access Required']) offset = 8 if ace['Type'].endswith('Object'): fields = ('Raw Object Flags', ) for k, v in zip(fields, unpack('<I', input_buffer[8:12])): ace[k] = v ace['Object Flags'] = parse_ace_object_flags( ace['Raw Object Flags']) offset = 12 if ace['Object Flags']['Object Type Present']: ace['GUID'] = format_uuid(input_buffer[offset:offset + 16]) offset += 16 if ace['Object Flags']['Inherited Object Type Present']: ace['Inherited GUID'] = format_uuid( input_buffer[offset:offset + 16]) offset += 16 ace['SID'] = format_sid(input_buffer[offset:ace['Size']]) ace['SID'] = format_sid(input_buffer[offset:ace['Size']]) input_buffer = input_buffer[ace['Size']:] out.append(ace) return out
def formatAttribute(self, att, formatCnAsGroup=False): aname = att.key.lower() #User flags if aname == 'useraccountcontrol': return ', '.join(self.parseFlags(att, uac_flags)) #List of groups if aname == 'member' or aname == 'memberof' and type( att.values) is list: return self.formatGroupsHtml(att.values) #Primary group if aname == 'primarygroupid': try: return self.formatGroupsHtml([self.dd.groups_dnmap[att.value]]) except KeyError: return 'NOT FOUND!' #Pwd flags if aname == 'pwdproperties': return ', '.join(self.parseFlags(att, pwd_flags)) #Domain trust flags if aname == 'trustattributes': return ', '.join(self.parseFlags(att, trust_flags)) if aname == 'trustdirection': if att.value == 0: return 'DISABLED' else: return ', '.join( self.parseTrustDirection(att, trust_directions)) if aname == 'trusttype': return ', '.join(self.parseFlags(att, trust_type)) if aname == 'securityidentifier': return format_sid(att.raw_values[0]) if aname == 'minpwdage' or aname == 'maxpwdage': return '%.2f days' % self.nsToDays(att.value) if aname == 'lockoutobservationwindow' or aname == 'lockoutduration': return '%.1f minutes' % self.nsToMinutes(att.value) if aname == 'objectsid': return '<abbr title="%s">%s</abbr>' % (att.value, att.value.split('-')[-1]) #Special case where the attribute is a CN and it should be made clear its a group if aname == 'cn' and formatCnAsGroup: return self.formatCnWithGroupLink(att.value) #Other return self.htmlescape(self.formatString(att.value))
def formatAttribute(self, att, formatCnAsGroup=False): aname = att.key.lower() #User flags if aname == 'useraccountcontrol': return ', '.join(self.parseFlags(att, uac_flags)) #List of groups if aname == 'member' or aname == 'memberof' and type(att.values) is list: return self.formatGroupsHtml(att.values) #Primary group if aname == 'primarygroupid': try: return self.formatGroupsHtml([self.dd.groups_dnmap[att.value]]) except KeyError: return 'NOT FOUND!' #Pwd flags if aname == 'pwdproperties': return ', '.join(self.parseFlags(att, pwd_flags)) #Domain trust flags if aname == 'trustattributes': return ', '.join(self.parseFlags(att, trust_flags)) if aname == 'trustdirection': if att.value == 0: return 'DISABLED' else: return ', '.join(self.parseTrustDirection(att, trust_directions)) if aname == 'trusttype': return ', '.join(self.parseFlags(att, trust_type)) if aname == 'securityidentifier': return format_sid(att.raw_values[0]) if aname == 'minpwdage' or aname == 'maxpwdage': return '%.2f days' % self.nsToDays(att.value) if aname == 'lockoutobservationwindow' or aname == 'lockoutduration': return '%.1f minutes' % self.nsToMinutes(att.value) if aname == 'objectsid': return '<abbr title="%s">%s</abbr>' % (att.value, att.value.split('-')[-1]) #Special case where the attribute is a CN and it should be made clear its a group if aname == 'cn' and formatCnAsGroup: return self.formatCnWithGroupLink(att.value) #Other return self.htmlescape(self.formatString(att.value))
def parse_ntSecurityDescriptor(input_buffer): """ Parses a ntSecurityDescriptor. """ out = dict() fields = ('Revision', 'Raw Type', 'Offset to owner SID', 'Offset to group SID', 'Offset to SACL', 'Offset to DACL') for k, v in zip(fields, unpack('<HHIIII', input_buffer[:20])): out[k] = v out['Type'] = parse_sddl_type(out['Raw Type']) for x in ('Owner', 'Group'): offset = out['Offset to %s SID' % (x.lower())] out['%s SID' % x] = format_sid(input_buffer[offset:offset + SID_SIZE]) if out['Type']['SACL Present']: out['SACL'] = parse_acl(input_buffer[out['Offset to SACL']:]) if out['Type']['DACL Present']: out['DACL'] = parse_acl(input_buffer[out['Offset to DACL']:]) return out
def formatAttribute(key, att, formatCnAsGroup=False): aname = key.lower() if isinstance(att, tuple) and \ len(att) == 1 and not isinstance(att[0], dict): att = att[0] if isinstance(att, (str, unicode)): att = att.strip() try: att = int(att) except ValueError: try: att = float(att) except ValueError: pass if aname == 'useraccountcontrol': return parseFlags(att, uac_flags) #Pwd flags elif aname == 'pwdproperties': return parseFlags(att, pwd_flags) #Sam flags elif aname == 'samaccounttype': return parseFlags(att, sam_flags, False) #Domain trust flags elif aname == 'trustattributes': return parseFlags(att, trust_flags) elif aname == 'trustdirection': if att == 0: return 'DISABLED' else: return parseFlags(att, trust_directions, False) elif aname == 'trusttype': return parseFlags(att, trust_type) elif aname in ('securityidentifier', 'objectsid') and att.startswith('hex:'): sid = format_sid(att[4:].decode('hex')) return _sid(sid) elif aname == 'minpwdage' or aname == 'maxpwdage': return '%.2f days' % nsToDays(att) elif aname == 'lockoutobservationwindow' or aname == 'lockoutduration': return '%.1f minutes' % nsToMinutes(att) elif aname == 'objectguid' and att.startswith('hex:'): return '{' + str(UUID(att[4:])) + '}' elif aname in ('pwdlastchange', 'badpasswordtime', 'lastlogon', 'lastlogontimestamp', 'lockouttime'): return toDateTime(att) elif aname in ('ntsecuritydescriptor', ): if att.startswith('hex:'): att = att[4:].decode('hex') srsd = SR_SECURITY_DESCRIPTOR() srsd.fromString(att) return LDAPSdToDict(srsd) return att
def main(self, IP, lusername, domain, password, port, unencrypted): global gid global l gid = "" username = [] Desc = [] HD = [] PLS = [] LLO = [] memberof = [] members = [] system = [] GroupID = [] UACL = [] sidnumber = [] operatingsystemversion = [] operatingsystem = [] FGminpass = [] FGlocknum = [] FGpasshis = [] FGlockdur = [] FGpassp = [] FGmembers = [] usernamelist = [] profilepath = [] samnamelist = [] sql = load() if port: IP = IP + ":" + port if unencrypted == True: con = ldap.initialize('ldap://' + IP) else: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) con = ldap.initialize('ldaps://' + IP) try: con.simple_bind_s(lusername + '@' + domain, password) print(colors.GRN + "[+] " + colors.NRM + "Credentials valid, generating database") except ldap.INVALID_CREDENTIALS: print(colors.RD + "[-] " + colors.NRM + "Username or password is incorrect.") os.remove(".db/" + domain + ".db") sys.exit() except ldap.SERVER_DOWN: print(colors.RD + "[-] " + colors.NRM + "Domain Controller either down or unreachable") os.remove(".db/" + domain + ".db") sys.exit() dictionary = ['distinguishedName', 'sAMAccountName'] user_attributes = [ 'distinguishedName', 'sAMAccountName', 'description', 'objectSid', 'homeDirectory', 'profilePath', 'pwdLastSet', 'lastLogon', 'memberOf', 'primaryGroupID', 'userAccountControl' ] group_attributes = [ 'distinguishedName', 'sAMAccountName', 'description', 'memberOf', 'member', 'objectSid' ] computer_attributes = [ 'description', 'memberOf', 'operatingSystem', 'operatingSystemVersion' ] FGPP_attributes = [ 'msDS-LockoutDuration', 'msDS-MaximumPasswordAge', 'msDS-LockoutThreshold', 'msDS-MinimumPasswordLength', 'msDS-PasswordComplexityEnabled', 'msDS-PasswordHistoryLength', 'msDS-PSOAppliesTo', 'pwdProperties' ] Kerb_attributes = [ 'servicePrincipalName', 'sAMAccountName', 'description', 'pwdLastSet', 'memberOf' ] searchScope = ldap.SCOPE_SUBTREE object = [ 'user', 'group', 'computer', 'msDS-PasswordSettings', 'FGPP-PasswordSettings', 'SPN' ] if unencrypted == True: l = ldap.initialize('ldap://' + IP) else: l = ldap.initialize('ldaps://' + IP) l.set_option(ldap.OPT_REFERRALS, 0) l.simple_bind_s(lusername + '@' + domain, password) domain = domain.replace('.', ',DC=') baseDN = "DC=" + domain sql = load() connect_db() sql.begin() try: for obj in object: searchFilter = '(&(objectCategory=' + obj + '))' if obj == 'SPN': print(colors.GRN + "[+] " + colors.NRM + "Table 5/5 : Generating SPN Table") searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" results = self.ldap_query(l, baseDN, searchScope, searchFilter, Kerb_attributes) if obj == 'group': print(colors.GRN + "[+] " + colors.NRM + "Table 2/5 : Generating Group Table") results = self.ldap_query(l, baseDN, searchScope, searchFilter, group_attributes) memberof = [] Desc = [] username = [] dictionary = dict(list(zip(usernamelist, samnamelist))) if obj == 'user': print(colors.GRN + "[+] " + colors.NRM + "Table 1/5 : Generating User Table") results = self.ldap_query(l, baseDN, searchScope, searchFilter, user_attributes) memberof = [] Desc = [] username = [] if obj == 'computer': print(colors.GRN + "[+] " + colors.NRM + "Table 3/5 : Generating Computer Table") results = self.ldap_query(l, baseDN, searchScope, searchFilter, computer_attributes) memberof = [] Desc = [] if obj == 'msDS-PasswordSettings': print(colors.GRN + "[+] " + colors.NRM + "Table 4/5 : Generating Password Policy Table") results = l.search_s(baseDN, ldap.SCOPE_BASE) if obj == 'FGPP-PasswordSettings': FGPPDN = 'CN=Password Settings Container,CN=System,' + ( baseDN) searchFilter = '(&(objectCategory=msDS-PasswordSettings))' results = l.search_s(FGPPDN, searchScope, searchFilter, FGPP_attributes) for result in results: if result[0] is None: pass else: result_dn = result[0].replace('CN=', '').split(',') results_attrs = result[1] name = result_dn[0] if 'sAMAccountName' in results_attrs: sAMAccountName = str( results_attrs['sAMAccountName']).replace( '[b\'', '').replace('\']', '') else: sAMAccountName = "" if 'description' in results_attrs: description = str( results_attrs['description']).replace( '[b\'', '').replace('\']', '') else: description = "" if 'profilePath' in results_attrs: profilepath = str( results_attrs['profilePath']).replace( '[b\'', '').replace('\']', '')[1:-1] else: profilepath = "" if 'homeDirectory' in results_attrs: homeDirectory = str( results_attrs['homeDirectory']).replace( '[b\'', '').replace('\']', '') else: homeDirectory = "" if 'pwdLastSet' in results_attrs: time = str(results_attrs['pwdLastSet']).replace( '[b\'', '').replace('\']', '') self.ADtime(time) pwdLastSet = date else: pwdLastSet = "" if 'lastLogon' in results_attrs: time = str(results_attrs['lastLogon']).replace( '[b\'', '').replace('\']', '') self.ADtime(time) lastLogon = date else: lastLogon = "" if 'primaryGroupID' in results_attrs: gidnum = str( results_attrs['primaryGroupID']).replace( '[b\'', '').replace('\']', '\n') else: pass if 'memberOf' in results_attrs: membersOf = [] for memberOf in results_attrs["memberOf"]: memberOf = memberOf.decode("utf-8") r = memberOf.split(',CN=')[0].replace( 'CN=', '').replace( ',Builtin,' + baseDN + '', '').replace(baseDN, '').replace('OU=', ' ') r = r.split(',') membersOf.append(r[0]) membersOf = ','.join(membersOf).replace(',', '\n') else: membersOf = "" if "member" in results_attrs: mem = [] if obj == 'group': trimmed_results = results_attrs.copy() del trimmed_results[ 'distinguishedName'], trimmed_results[ 'sAMAccountName'], trimmed_results[ 'objectSid'] if 'description' in trimmed_results: del trimmed_results['description'] if 'memberOf' in trimmed_results: del trimmed_results['memberOf'] for key, value in trimmed_results.items(): for v in value: v = v.decode("utf-8") t = v.split(',CN=')[0].replace( 'CN=', '').replace(baseDN, ' ').replace( 'OU=', ' ') t = t.split(',') if str(t[0]) in usernamelist: mem.append( dictionary['' + str(t[0]) + '']) else: mem.append(str(t[0])) else: for member in results_attrs["member"]: member = member.decode("utf-8") t = member.split(',CN=')[0].replace( 'CN=', '').replace(baseDN, ' ').replace('OU=', ' ') t = t.split(',') mem.append(str(t[0])) if str(t[0]) in usernamelist: mem.append(dictionary['' + str(t[0]) + '']) else: mem.append(str(t[0])) mem = ','.join(mem).replace(',', '\n') else: mem = " " if 'objectSid' in results_attrs: for SID in results_attrs["objectSid"]: sids = (format_sid(SID).split('-')[-1]) if 'userAccountControl' in results_attrs: userac = [] uac = { 'ACCOUNT_DISABLED': 0x00000002, 'ACCOUNT_LOCKED': 0x00000010, 'PASSWD_NOTREQD': 0x00000020, 'PASSWD_CANT_CHANGE': 0x00000040, 'NORMAL_ACCOUNT': 0x00000200, 'WORKSTATION_ACCOUNT': 0x00001000, 'SERVER_TRUST_ACCOUNT': 0x00002000, 'DONT_EXPIRE_PASSWD': 0x00010000, 'SMARTCARD_REQUIRED': 0x00040000, 'PASSWORD_EXPIRED': 0x00800000 } UAC = results_attrs["userAccountControl"] for trigger, val in list(uac.items()): vUAC = b''.join(UAC) if int(vUAC) & val: userac.append(trigger) userac = str(userac).replace('[', '').replace( ']', '').replace(',', '\n').replace( ' ', '').replace('\'', '') if 'operatingSystem' in results_attrs: OS = str(results_attrs["operatingSystem"]).replace( '\']', '').replace('[b\'', '') else: OS = "" if 'operatingSystemVersion' in results_attrs: OSV = str(results_attrs["operatingSystemVersion"] ).replace('\']', '').replace('[b\'', '') else: OSV = "" if "msDS-MinimumPasswordLength" in results_attrs: FGminpwd = (str( results_attrs["msDS-MinimumPasswordLength"]). replace('[b\'', '').replace('\']', '')) else: FGminpwd = ' ' if "msDS-LockoutThreshold" in results_attrs: FGlcknum = (str( results_attrs["msDS-LockoutThreshold"]). replace('[b\'', '').replace('\']', '')) else: FGlcknum = ' ' if "msDS-PasswordHistoryLength" in results_attrs: FGpwdhis = (str( results_attrs["msDS-PasswordHistoryLength"]). replace('[b\'', '').replace('\']', '')) else: FGpwdhis = ' ' if "msDS-LockoutDuration" in results_attrs: FGlckduration = results_attrs[ "msDS-LockoutDuration"] FGlckdur = (str((int( str(FGlckduration).replace( '[b\'-', '').replace('\']', '')) * 0.0000001) / 60)) else: FGlckdur = ' ' if "msDS-PasswordComplexityEnabled" in results_attrs: FGpwdp = (str( results_attrs["msDS-PasswordComplexityEnabled"] ).replace('[b\'', '').replace('\']', '')) else: FGpwdp = ' ' if "msDS-PSOAppliesTo" in results_attrs: FGmember = (str( results_attrs["msDS-PSOAppliesTo"]).replace( '[b\'', '').replace('\']', '').replace( ',CN=Users,DC=STARLABS,DC=local', '\n').replace('CN=', '')) else: FGmember = ' ' if obj == 'msDS-PasswordSettings': pwdpar = [] minpwd = str( results_attrs["minPwdLength"]).replace( '[b\'', '').replace('\']', '') lcknum = str( results_attrs["lockoutThreshold"]).replace( '[b\'', '').replace('\']', '') pwdhis = str( results_attrs["pwdHistoryLength"]).replace( '[b\'', '').replace('\']', '') lckdur = results_attrs["lockoutDuration"] lckdur = str((int( str(lckdur).replace('[b\'-', '').replace( '\']', '')) * 0.0000001) / 60) pwdp = str(results_attrs["pwdProperties"]).replace( '[b\'', '').replace('\']', '') pwdpa = { 'DOMAIN_PASSWORD_COMPLEX': 1, 'DOMAIN_PASSWORD_NO_ANON_CHANGE': 2, 'DOMAIN_PASSWORD_NO_CLEAR_CHANGE': 4, 'DOMAIN_LOCKOUT_ADMINS': 8, 'DOMAIN_PASSWORD_STORE_CLEARTEXT': 16, 'DOMAIN_REFUSE_PASSWORD_CHANGE': 32 } for trigger, val in list(pwdpa.items()): vpwdp = ''.join(pwdp).replace("[b'", "") if int(vpwdp) & val: pwdpar.append(trigger) pwdpar = str(pwdpar).replace('[\'', '').replace( '\']', '') sql.Insert_Passwd(minpwd, lcknum, lckdur, pwdhis, (pwdp + " " + pwdpar)) if 'servicePrincipalName' in results_attrs: for SPN in results_attrs["servicePrincipalName"]: SPN = SPN.decode("utf-8") r = SPN.split(',CN=')[0].replace( 'CN=', '').replace( ',Builtin,' + baseDN + '', '').replace(baseDN, '').replace('OU=', ' ') if obj == "SPN": sql.Insert_SPN(SPN, sAMAccountName, description, pwdLastSet, memberOf) if obj == 'FGPP-PasswordSettings': sql.Insert_FGPasswd(FGminpwd, FGlcknum, FGpwdhis, FGlckdur, FGpwdp, FGmember) if obj == 'group': sql.Insert_Groups(name, sids, description, str(membersOf), str(mem)) if obj == 'user': usernamelist.append(name) samnamelist.append(sAMAccountName) sql.insert_Users(sAMAccountName, description, sids, homeDirectory, profilepath, pwdLastSet, lastLogon, userac, str(gidnum), (str(membersOf))) if obj == 'computer': sql.Insert_Computers(name, description, OS, OSV, (str(membersOf))) except ldap.REFERRAL: print( colors.RD + "[-] " + colors.NRM + "Incorrect fully qualified domain name. Please check your settings and try again." ) sys.exit() sql.Close() # try: # fileshare () # except TypeError as e: # print colors.RD + "[-] " + colors.NRM + "Error occured generating list of file shares" # print "[*] Skipping Fileshare enumeration. As result show fileshare will not work (however manual queries will work" # pass print(colors.GRN + "[+] " + colors.NRM + "Database successfully created") groupIDquery()