Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/gather/ad_to_sqlite.rb
Views: 11655
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'sqlite3'6require 'tempfile'78class MetasploitModule < Msf::Post9include Msf::Post::Windows::LDAP1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'AD Computer, Group and Recursive User Membership to Local SQLite DB',16'Description' => %q{17This module will gather a list of AD groups, identify the users (taking into account recursion)18and write this to a SQLite database for offline analysis and query using normal SQL syntax.19},20'License' => MSF_LICENSE,21'Author' => [22'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'23],24'Platform' => [ 'win' ],25'SessionTypes' => [ 'meterpreter' ]26)27)2829register_options([30OptString.new('GROUP_FILTER', [false, 'Additional LDAP filters to use when searching for initial groups', '']),31OptBool.new('SHOW_USERGROUPS', [true, 'Show the user/group membership in a greppable form to the console.', false]),32OptBool.new('SHOW_COMPUTERS', [true, 'Show basic computer information in a greppable form to the console.', false]),33OptInt.new('THREADS', [true, 'Number of threads to spawn to gather membership of each group.', 20])34])35end3637# Entry point38def run39max_search = datastore['MAX_SEARCH']4041db, dbfile = create_sqlite_db42print_status "Temporary database created: #{dbfile.path}"4344# Download the list of groups from Active Directory45vprint_status 'Retrieving AD Groups'46begin47group_fields = ['distinguishedName', 'objectSid', 'samAccountType', 'sAMAccountName', 'whenChanged', 'whenCreated', 'description', 'groupType', 'adminCount', 'comment', 'managedBy', 'cn']48if datastore['GROUP_FILTER'].nil? || datastore['GROUP_FILTER'].empty?49group_query = '(objectClass=group)'50else51group_query = "(&(objectClass=group)(#{datastore['GROUP_FILTER']}))"52end53groups = query(group_query, max_search, group_fields)54rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e55print_error("Error(Group): #{e.message}")56return57end5859# If no groups were downloaded, there's no point carrying on60if groups.nil? || groups[:results].empty?61print_error('No AD groups were discovered')62return63end6465# Go through each of the groups and identify the individual users in each group66vprint_status "Groups retrieval completed: #{groups[:results].size} group(s)"67vprint_status 'Retrieving AD Group Membership'68users_fields = ['distinguishedName', 'objectSid', 'sAMAccountType', 'sAMAccountName', 'displayName', 'description', 'logonCount', 'userAccountControl', 'userPrincipalName', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'comment', 'title', 'cn', 'adminCount', 'manager']6970remaining_groups = groups[:results]7172# If the number of threads exceeds the number of groups, reduce them down to the correct number73threadcount = remaining_groups.count < datastore['THREADS'] ? remaining_groups.count : datastore['THREADS']7475# Loop through each of the groups, creating threads where necessary76while !remaining_groups.nil? && !remaining_groups.empty?77group_gather = []781.upto(threadcount) do79group_gather << framework.threads.spawn("Module(#{refname})", false, remaining_groups.shift) do |individual_group|80next if !individual_group || individual_group.empty? || individual_group.nil?8182# Get the Group RID83group_rid = get_rid(individual_group[1][:value]).to_i8485# Perform the ADSI query to retrieve the effective users in each group (recursion)86vprint_status "Retrieving members of #{individual_group[3][:value]}"87users_filter = "(&(objectCategory=person)(objectClass=user)(|(memberOf:1.2.840.113556.1.4.1941:=#{individual_group[0][:value]})(primaryGroupID=#{group_rid})))"88users_in_group = query(users_filter, max_search, users_fields)8990grouptype_int = individual_group[7][:value].to_i # Set this here because it is used a lot below91sat_int = individual_group[2][:value].to_i9293# Add the group to the database94# groupType parameter interpretation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms675935(v=vs.85).aspx95# Note that the conversions to UTF-8 are necessary because of the way SQLite detects column type affinity96# Turns out that the 'fix' is documented in https://github.com/rails/rails/issues/196597sql_param_group = {98g_rid: group_rid,99g_distinguishedName: individual_group[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),100g_sAMAccountType: sat_int,101g_sAMAccountName: individual_group[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),102g_whenChanged: individual_group[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),103g_whenCreated: individual_group[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),104g_description: individual_group[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),105g_groupType: grouptype_int,106g_adminCount: individual_group[8][:value].to_i,107g_comment: individual_group[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),108g_managedBy: individual_group[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),109g_cn: individual_group[11][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),110# Specifies a group that is created by the system.111g_GT_GROUP_CREATED_BY_SYSTEM: (grouptype_int & 0x00000001).zero? ? 0 : 1,112# Specifies a group with global scope.113g_GT_GROUP_SCOPE_GLOBAL: (grouptype_int & 0x00000002).zero? ? 0 : 1,114# Specifies a group with local scope.115g_GT_GROUP_SCOPE_LOCAL: (grouptype_int & 0x00000004).zero? ? 0 : 1,116# Specifies a group with universal scope.117g_GT_GROUP_SCOPE_UNIVERSAL: (grouptype_int & 0x00000008).zero? ? 0 : 1,118# Specifies an APP_BASIC group for Windows Server Authorization Manager.119g_GT_GROUP_SAM_APP_BASIC: (grouptype_int & 0x00000010).zero? ? 0 : 1,120# Specifies an APP_QUERY group for Windows Server Authorization Manager.121g_GT_GROUP_SAM_APP_QUERY: (grouptype_int & 0x00000020).zero? ? 0 : 1,122# Specifies a security group. If this flag is not set, then the group is a distribution group.123g_GT_GROUP_SECURITY: (grouptype_int & 0x80000000).zero? ? 0 : 1,124# The inverse of the flag above. Technically GT_GROUP_SECURITY=0 makes it a distribution125# group so this is arguably redundant, but I have included it for ease. It makes a lot more sense126# to set DISTRIBUTION=1 in a query when your mind is on other things to remember that127# DISTRIBUTION is in fact the inverse of SECURITY...:)128g_GT_GROUP_DISTRIBUTION: (grouptype_int & 0x80000000).zero? ? 1 : 0,129# Now add sAMAccountType constants130g_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,131g_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,132g_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,133g_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,134g_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,135g_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,136g_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,137g_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,138g_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,139g_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,140g_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0141}142run_sqlite_query(db, 'ad_groups', sql_param_group)143144# Go through each group user145next if users_in_group[:results].empty?146147users_in_group[:results].each do |group_user|148user_rid = get_rid(group_user[1][:value]).to_i149print_line "Group [#{individual_group[3][:value]}][#{group_rid}] has member [#{group_user[3][:value]}][#{user_rid}]" if datastore['SHOW_USERGROUPS']150151uac_int = group_user[7][:value].to_i # Set this because it is used so frequently below152sat_int = group_user[2][:value].to_i153154# Add the group to the database155# Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx156sql_param_user = {157u_rid: user_rid,158u_distinguishedName: group_user[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),159u_sAMAccountType: group_user[2][:value].to_i,160u_sAMAccountName: group_user[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),161u_displayName: group_user[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),162u_description: group_user[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),163u_logonCount: group_user[6][:value].to_i,164u_userAccountControl: uac_int,165u_userPrincipalName: group_user[8][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),166u_whenChanged: group_user[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),167u_whenCreated: group_user[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),168u_primaryGroupID: group_user[11][:value].to_i,169u_badPwdCount: group_user[12][:value].to_i,170u_comment: group_user[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),171u_title: group_user[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),172u_cn: group_user[15][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),173# Indicates that a given object has had its ACLs changed to a more secure value by the174# system because it was a member of one of the administrative groups (directly or transitively).175u_adminCount: group_user[16][:value].to_i,176u_manager: group_user[17][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),177# The login script is executed178u_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,179# The user account is disabled.180u_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,181# The home directory is required.182u_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,183# The account is currently locked out.184u_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,185# No password is required.186u_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,187# The user cannot change the password.188u_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,189# The user can send an encrypted password.190u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,191# This is an account for users whose primary account is in another domain. This account192# provides user access to this domain, but not to any domain that trusts this domain.193# Also known as a local user account.194u_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,195# This is a default account type that represents a typical user.196u_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,197# This is a permit to trust account for a system domain that trusts other domains.198u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,199# This is a computer account for a computer that is a member of this domain.200u_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,201# This is a computer account for a system backup domain controller that is a member of this domain.202u_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,203# The password for this account will never expire.204u_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,205# This is an MNS logon account.206u_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,207# The user must log on using a smart card.208u_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,209# The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.210# Any such service can impersonate a client requesting the service.211u_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,212# The security context of the user will not be delegated to a service even if the service213# account is set as trusted for Kerberos delegation.214u_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,215# Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.216u_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,217# This account does not require Kerberos pre-authentication for logon.218u_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,219# The password has expired220u_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,221# The account is enabled for delegation. This is a security-sensitive setting; accounts with222# this option enabled should be strictly controlled. This setting enables a service running223# under the account to assume a client identity and authenticate as that user to other remote224# servers on the network.225u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,226# Now add sAMAccountType constants227u_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,228u_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,229u_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,230u_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,231u_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,232u_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,233u_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,234u_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,235u_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,236u_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,237u_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0238}239run_sqlite_query(db, 'ad_users', sql_param_user)240241# Now associate the user with the group242sql_param_mapping = {243user_rid: user_rid,244group_rid: group_rid245}246run_sqlite_query(db, 'ad_mapping', sql_param_mapping)247end248rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e249print_error("Error(Users): #{e.message}")250next251end252end253group_gather.map(&:join)254end255256vprint_status 'Retrieving computers'257begin258computer_filter = '(objectClass=computer)'259computer_fields = ['distinguishedName', 'objectSid', 'cn', 'dNSHostName', 'sAMAccountType', 'sAMAccountName', 'displayName', 'logonCount', 'userAccountControl', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'operatingSystem', 'operatingSystemServicePack', 'operatingSystemVersion', 'description', 'comment']260computers = query(computer_filter, max_search, computer_fields)261262computers[:results].each do |comp|263computer_rid = get_rid(comp[1][:value]).to_i264265uac_int = comp[8][:value].to_i # Set this because it is used so frequently below266sat_int = comp[4][:value].to_i267268# Add the group to the database269# Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx270# Note that userAccountControl is basically the same for a computer as a user; this is because a computer account is derived from a user account271# (if you look at the objectClass for a computer account, it includes 'user') and, for efficiency, we should really store it all in one272# table. However, the reality is that it will get annoying for users to have to remember to use the userAccountControl flags to work out whether273# its a user or a computer and so, for convenience and ease of use, I have put them in completely separate tables.274# Also add the sAMAccount type flags from https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637(v=vs.85).aspx275sql_param_computer = {276c_rid: computer_rid,277c_distinguishedName: comp[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),278c_cn: comp[2][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),279c_dNSHostName: comp[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),280c_sAMAccountType: sat_int,281c_sAMAccountName: comp[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),282c_displayName: comp[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),283c_logonCount: comp[7][:value].to_i,284c_userAccountControl: uac_int,285c_whenChanged: comp[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),286c_whenCreated: comp[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),287c_primaryGroupID: comp[11][:value].to_i,288c_badPwdCount: comp[12][:value].to_i,289c_operatingSystem: comp[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),290c_operatingSystemServicePack: comp[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),291c_operatingSystemVersion: comp[15][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),292c_description: comp[16][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),293c_comment: comp[17][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),294# The login script is executed295c_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,296# The user account is disabled.297c_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,298# The home directory is required.299c_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,300# The account is currently locked out.301c_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,302# No password is required.303c_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,304# The user cannot change the password.305c_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,306# The user can send an encrypted password.307c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,308# This is an account for users whose primary account is in another domain. This account309# provides user access to this domain, but not to any domain that trusts this domain.310# Also known as a local user account.311c_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,312# This is a default account type that represents a typical user.313c_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,314# This is a permit to trust account for a system domain that trusts other domains.315c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,316# This is a computer account for a computer that is a member of this domain.317c_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,318# This is a computer account for a system backup domain controller that is a member of this domain.319c_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,320# The password for this account will never expire.321c_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,322# This is an MNS logon account.323c_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,324# The user must log on using a smart card.325c_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,326# The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.327# Any such service can impersonate a client requesting the service.328c_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,329# The security context of the user will not be delegated to a service even if the service330# account is set as trusted for Kerberos delegation.331c_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,332# Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.333c_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,334# This account does not require Kerberos pre-authentication for logon.335c_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,336# The password has expired337c_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,338# The account is enabled for delegation. This is a security-sensitive setting; accounts with339# this option enabled should be strictly controlled. This setting enables a service running340# under the account to assume a client identity and authenticate as that user to other remote341# servers on the network.342c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,343# Now add the sAMAccountType objects344c_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,345c_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,346c_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,347c_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,348c_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,349c_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,350c_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,351c_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,352c_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,353c_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,354c_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0355}356run_sqlite_query(db, 'ad_computers', sql_param_computer)357print_line "Computer [#{sql_param_computer[:c_cn]}][#{sql_param_computer[:c_dNSHostName]}][#{sql_param_computer[:c_rid]}]" if datastore['SHOW_COMPUTERS']358end359rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e360print_error("Error(Computers): #{e.message}")361return362end363364loot_path = store_loot(365'host.ad_to_sqlite',366'application/x-sqlite3',367session,368File.binread(dbfile.path),369'ad_to_sqlite.db',370'AD Computer, Group and Recursive User Membership'371)372373print_good("Sqlite extraction stored in file: #{loot_path}")374ensure375# Finished enumeration, cleanup resources376if db377db.close378end379380if dbfile381dbfile.close382dbfile.unlink383end384end385386# Run the parameterised SQL query387def run_sqlite_query(db, table_name, values)388sql_param_columns = values.keys389sql_param_bind_params = values.keys.map { |k| ":#{k}" }390db.execute("replace into #{table_name} (#{sql_param_columns.join(',')}) VALUES (#{sql_param_bind_params.join(',')})", values)391end392393# Creat the SQLite Database394def create_sqlite_db395dbfile = Tempfile.new('ad_to_sqlite')396db = SQLite3::Database.new(dbfile.path)397db.type_translation = true398399# Create the table for the AD Computers400db.execute('DROP TABLE IF EXISTS ad_computers')401sql_table_computers = 'CREATE TABLE ad_computers ('\402'c_rid INTEGER PRIMARY KEY NOT NULL,'\403'c_distinguishedName TEXT UNIQUE NOT NULL,'\404'c_cn TEXT,'\405'c_sAMAccountType INTEGER,'\406'c_sAMAccountName TEXT UNIQUE NOT NULL,'\407'c_dNSHostName TEXT,'\408'c_displayName TEXT,'\409'c_logonCount INTEGER,'\410'c_userAccountControl INTEGER,'\411'c_primaryGroupID INTEGER,'\412'c_badPwdCount INTEGER,'\413'c_description TEXT,'\414'c_comment TEXT,'\415'c_operatingSystem TEXT,'\416'c_operatingSystemServicePack TEXT,'\417'c_operatingSystemVersion TEXT,'\418'c_whenChanged TEXT,'\419'c_whenCreated TEXT,'\420'c_ADS_UF_SCRIPT INTEGER,'\421'c_ADS_UF_ACCOUNTDISABLE INTEGER,'\422'c_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\423'c_ADS_UF_LOCKOUT INTEGER,'\424'c_ADS_UF_PASSWD_NOTREQD INTEGER,'\425'c_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\426'c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\427'c_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\428'c_ADS_UF_NORMAL_ACCOUNT INTEGER,'\429'c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\430'c_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\431'c_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\432'c_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\433'c_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\434'c_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\435'c_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\436'c_ADS_UF_NOT_DELEGATED INTEGER,'\437'c_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\438'c_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\439'c_ADS_UF_PASSWORD_EXPIRED INTEGER,'\440'c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\441'c_SAM_DOMAIN_OBJECT INTEGER,'\442'c_SAM_GROUP_OBJECT INTEGER,'\443'c_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\444'c_SAM_ALIAS_OBJECT INTEGER,'\445'c_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\446'c_SAM_NORMAL_USER_ACCOUNT INTEGER,'\447'c_SAM_MACHINE_ACCOUNT INTEGER,'\448'c_SAM_TRUST_ACCOUNT INTEGER,'\449'c_SAM_APP_BASIC_GROUP INTEGER,'\450'c_SAM_APP_QUERY_GROUP INTEGER,'\451'c_SAM_ACCOUNT_TYPE_MAX INTEGER)'452db.execute(sql_table_computers)453454# Create the table for the AD Groups455db.execute('DROP TABLE IF EXISTS ad_groups')456sql_table_group = 'CREATE TABLE ad_groups ('\457'g_rid INTEGER PRIMARY KEY NOT NULL,'\458'g_distinguishedName TEXT UNIQUE NOT NULL,'\459'g_sAMAccountType INTEGER,'\460'g_sAMAccountName TEXT UNIQUE NOT NULL,'\461'g_groupType INTEGER,'\462'g_adminCount INTEGER,'\463'g_description TEXT,'\464'g_comment TEXT,'\465'g_cn TEXT,'\466'g_managedBy TEXT,'\467'g_whenChanged TEXT,'\468'g_whenCreated TEXT,'\469'g_GT_GROUP_CREATED_BY_SYSTEM INTEGER,'\470'g_GT_GROUP_SCOPE_GLOBAL INTEGER,'\471'g_GT_GROUP_SCOPE_LOCAL INTEGER,'\472'g_GT_GROUP_SCOPE_UNIVERSAL INTEGER,'\473'g_GT_GROUP_SAM_APP_BASIC INTEGER,'\474'g_GT_GROUP_SAM_APP_QUERY INTEGER,'\475'g_GT_GROUP_SECURITY INTEGER,'\476'g_GT_GROUP_DISTRIBUTION INTEGER,'\477'g_SAM_DOMAIN_OBJECT INTEGER,'\478'g_SAM_GROUP_OBJECT INTEGER,'\479'g_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\480'g_SAM_ALIAS_OBJECT INTEGER,'\481'g_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\482'g_SAM_NORMAL_USER_ACCOUNT INTEGER,'\483'g_SAM_MACHINE_ACCOUNT INTEGER,'\484'g_SAM_TRUST_ACCOUNT INTEGER,'\485'g_SAM_APP_BASIC_GROUP INTEGER,'\486'g_SAM_APP_QUERY_GROUP INTEGER,'\487'g_SAM_ACCOUNT_TYPE_MAX INTEGER)'488db.execute(sql_table_group)489490# Create the table for the AD Users491db.execute('DROP TABLE IF EXISTS ad_users')492sql_table_users = 'CREATE TABLE ad_users ('\493'u_rid INTEGER PRIMARY KEY NOT NULL,'\494'u_distinguishedName TEXT UNIQUE NOT NULL,'\495'u_description TEXT,'\496'u_displayName TEXT,'\497'u_sAMAccountType INTEGER,'\498'u_sAMAccountName TEXT,'\499'u_logonCount INTEGER,'\500'u_userAccountControl INTEGER,'\501'u_primaryGroupID INTEGER,'\502'u_cn TEXT,'\503'u_adminCount INTEGER,'\504'u_badPwdCount INTEGER,'\505'u_userPrincipalName TEXT UNIQUE,'\506'u_comment TEXT,'\507'u_title TEXT,'\508'u_manager TEXT,'\509'u_whenCreated TEXT,'\510'u_whenChanged TEXT,'\511'u_ADS_UF_SCRIPT INTEGER,'\512'u_ADS_UF_ACCOUNTDISABLE INTEGER,'\513'u_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\514'u_ADS_UF_LOCKOUT INTEGER,'\515'u_ADS_UF_PASSWD_NOTREQD INTEGER,'\516'u_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\517'u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\518'u_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\519'u_ADS_UF_NORMAL_ACCOUNT INTEGER,'\520'u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\521'u_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\522'u_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\523'u_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\524'u_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\525'u_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\526'u_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\527'u_ADS_UF_NOT_DELEGATED INTEGER,'\528'u_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\529'u_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\530'u_ADS_UF_PASSWORD_EXPIRED INTEGER,'\531'u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\532'u_SAM_DOMAIN_OBJECT INTEGER,'\533'u_SAM_GROUP_OBJECT INTEGER,'\534'u_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\535'u_SAM_ALIAS_OBJECT INTEGER,'\536'u_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\537'u_SAM_NORMAL_USER_ACCOUNT INTEGER,'\538'u_SAM_MACHINE_ACCOUNT INTEGER,'\539'u_SAM_TRUST_ACCOUNT INTEGER,'\540'u_SAM_APP_BASIC_GROUP INTEGER,'\541'u_SAM_APP_QUERY_GROUP INTEGER,'\542'u_SAM_ACCOUNT_TYPE_MAX INTEGER)'543db.execute(sql_table_users)544545# Create the table for the mapping between the two (membership)546db.execute('DROP TABLE IF EXISTS ad_mapping')547sql_table_mapping = 'CREATE TABLE ad_mapping ('\548'user_rid INTEGER NOT NULL,' \549'group_rid INTEGER NOT NULL,'\550'PRIMARY KEY (user_rid, group_rid),'\551'FOREIGN KEY(user_rid) REFERENCES ad_users(u_rid)'\552'FOREIGN KEY(group_rid) REFERENCES ad_groups(g_rid))'553db.execute(sql_table_mapping)554555# Create the view for the AD User/Group membership556db.execute('DROP VIEW IF EXISTS view_mapping')557sql_view_mapping = 'CREATE VIEW view_mapping AS SELECT ad_groups.*,ad_users.* FROM ad_mapping '\558'INNER JOIN ad_groups ON ad_groups.g_rid = ad_mapping.group_rid '\559'INNER JOIN ad_users ON ad_users.u_rid = ad_mapping.user_rid'560db.execute(sql_view_mapping)561562return db, dbfile563rescue SQLite3::Exception => e564print_error("Error(Database): #{e.message}")565return566end567568def get_rid(data)569sid = data.unpack('bbbbbbbbV*')[8..]570sid[-1]571end572end573574575