Lucene search
K

AD Computer, Group and Recursive User Membership to Local SQLite DB

🗓️ 21 Dec 2015 18:16:06Reported by Stuart Morgan <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 65 Views

AD Computer, Group and Recursive User Membership to Local SQLite DB. This module gathers AD groups, identifies users with recursion and saves to SQLite for query

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'sqlite3'
require 'tempfile'

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::LDAP

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'AD Computer, Group and Recursive User Membership to Local SQLite DB',
        'Description' => %q{
          This module will gather a list of AD groups, identify the users (taking into account recursion)
          and write this to a SQLite database for offline analysis and query using normal SQL syntax.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'
        ],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ]
      )
    )

    register_options([
      OptString.new('GROUP_FILTER', [false, 'Additional LDAP filters to use when searching for initial groups', '']),
      OptBool.new('SHOW_USERGROUPS', [true, 'Show the user/group membership in a greppable form to the console.', false]),
      OptBool.new('SHOW_COMPUTERS', [true, 'Show basic computer information in a greppable form to the console.', false]),
      OptInt.new('THREADS', [true, 'Number of threads to spawn to gather membership of each group.', 20])
    ])
  end

  # Entry point
  def run
    max_search = datastore['MAX_SEARCH']

    db, dbfile = create_sqlite_db
    print_status "Temporary database created: #{dbfile.path}"

    # Download the list of groups from Active Directory
    vprint_status 'Retrieving AD Groups'
    begin
      group_fields = ['distinguishedName', 'objectSid', 'samAccountType', 'sAMAccountName', 'whenChanged', 'whenCreated', 'description', 'groupType', 'adminCount', 'comment', 'managedBy', 'cn']
      if datastore['GROUP_FILTER'].nil? || datastore['GROUP_FILTER'].empty?
        group_query = '(objectClass=group)'
      else
        group_query = "(&(objectClass=group)(#{datastore['GROUP_FILTER']}))"
      end
      groups = query(group_query, max_search, group_fields)
    rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
      print_error("Error(Group): #{e.message}")
      return
    end

    # If no groups were downloaded, there's no point carrying on
    if groups.nil? || groups[:results].empty?
      print_error('No AD groups were discovered')
      return
    end

    # Go through each of the groups and identify the individual users in each group
    vprint_status "Groups retrieval completed: #{groups[:results].size} group(s)"
    vprint_status 'Retrieving AD Group Membership'
    users_fields = ['distinguishedName', 'objectSid', 'sAMAccountType', 'sAMAccountName', 'displayName', 'description', 'logonCount', 'userAccountControl', 'userPrincipalName', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'comment', 'title', 'cn', 'adminCount', 'manager']

    remaining_groups = groups[:results]

    # If the number of threads exceeds the number of groups, reduce them down to the correct number
    threadcount = remaining_groups.count < datastore['THREADS'] ? remaining_groups.count : datastore['THREADS']

    # Loop through each of the groups, creating threads where necessary
    while !remaining_groups.nil? && !remaining_groups.empty?
      group_gather = []
      1.upto(threadcount) do
        group_gather << framework.threads.spawn("Module(#{refname})", false, remaining_groups.shift) do |individual_group|
          next if !individual_group || individual_group.empty? || individual_group.nil?

          # Get the Group RID
          group_rid = get_rid(individual_group[1][:value]).to_i

          # Perform the ADSI query to retrieve the effective users in each group (recursion)
          vprint_status "Retrieving members of #{individual_group[3][:value]}"
          users_filter = "(&(objectCategory=person)(objectClass=user)(|(memberOf:1.2.840.113556.1.4.1941:=#{individual_group[0][:value]})(primaryGroupID=#{group_rid})))"
          users_in_group = query(users_filter, max_search, users_fields)

          grouptype_int = individual_group[7][:value].to_i # Set this here because it is used a lot below
          sat_int = individual_group[2][:value].to_i

          # Add the group to the database
          # groupType parameter interpretation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms675935(v=vs.85).aspx
          # Note that the conversions to UTF-8 are necessary because of the way SQLite detects column type affinity
          # Turns out that the 'fix' is documented in https://github.com/rails/rails/issues/1965
          sql_param_group = {
            g_rid: group_rid,
            g_distinguishedName: individual_group[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_sAMAccountType: sat_int,
            g_sAMAccountName: individual_group[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_whenChanged: individual_group[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_whenCreated: individual_group[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_description: individual_group[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_groupType: grouptype_int,
            g_adminCount: individual_group[8][:value].to_i,
            g_comment: individual_group[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_managedBy: individual_group[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            g_cn: individual_group[11][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
            # Specifies a group that is created by the system.
            g_GT_GROUP_CREATED_BY_SYSTEM: (grouptype_int & 0x00000001).zero? ? 0 : 1,
            # Specifies a group with global scope.
            g_GT_GROUP_SCOPE_GLOBAL: (grouptype_int & 0x00000002).zero? ? 0 : 1,
            # Specifies a group with local scope.
            g_GT_GROUP_SCOPE_LOCAL: (grouptype_int & 0x00000004).zero? ? 0 : 1,
            # Specifies a group with universal scope.
            g_GT_GROUP_SCOPE_UNIVERSAL: (grouptype_int & 0x00000008).zero? ? 0 : 1,
            # Specifies an APP_BASIC group for Windows Server Authorization Manager.
            g_GT_GROUP_SAM_APP_BASIC: (grouptype_int & 0x00000010).zero? ? 0 : 1,
            # Specifies an APP_QUERY group for Windows Server Authorization Manager.
            g_GT_GROUP_SAM_APP_QUERY: (grouptype_int & 0x00000020).zero? ? 0 : 1,
            # Specifies a security group. If this flag is not set, then the group is a distribution group.
            g_GT_GROUP_SECURITY: (grouptype_int & 0x80000000).zero? ? 0 : 1,
            # The inverse of the flag above. Technically GT_GROUP_SECURITY=0 makes it a distribution
            # group so this is arguably redundant, but I have included it for ease. It makes a lot more sense
            # to set DISTRIBUTION=1 in a query when your mind is on other things to remember that
            # DISTRIBUTION is in fact the inverse of SECURITY...:)
            g_GT_GROUP_DISTRIBUTION: (grouptype_int & 0x80000000).zero? ? 1 : 0,
            # Now add sAMAccountType constants
            g_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
            g_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
            g_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
            g_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
            g_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
            g_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
            g_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
            g_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
            g_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
            g_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
            g_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
          }
          run_sqlite_query(db, 'ad_groups', sql_param_group)

          # Go through each group user
          next if users_in_group[:results].empty?

          users_in_group[:results].each do |group_user|
            user_rid = get_rid(group_user[1][:value]).to_i
            print_line "Group [#{individual_group[3][:value]}][#{group_rid}] has member [#{group_user[3][:value]}][#{user_rid}]" if datastore['SHOW_USERGROUPS']

            uac_int = group_user[7][:value].to_i # Set this because it is used so frequently below
            sat_int = group_user[2][:value].to_i

            # Add the group to the database
            # Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx
            sql_param_user = {
              u_rid: user_rid,
              u_distinguishedName: group_user[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_sAMAccountType: group_user[2][:value].to_i,
              u_sAMAccountName: group_user[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_displayName: group_user[4][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_description: group_user[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_logonCount: group_user[6][:value].to_i,
              u_userAccountControl: uac_int,
              u_userPrincipalName: group_user[8][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_whenChanged: group_user[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_whenCreated: group_user[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_primaryGroupID: group_user[11][:value].to_i,
              u_badPwdCount: group_user[12][:value].to_i,
              u_comment: group_user[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_title: group_user[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              u_cn: group_user[15][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              # Indicates that a given object has had its ACLs changed to a more secure value by the
              # system because it was a member of one of the administrative groups (directly or transitively).
              u_adminCount: group_user[16][:value].to_i,
              u_manager: group_user[17][:value].to_s.encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
              # The login script is executed
              u_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,
              # The user account is disabled.
              u_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,
              # The home directory is required.
              u_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,
              # The account is currently locked out.
              u_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,
              # No password is required.
              u_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,
              # The user cannot change the password.
              u_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,
              # The user can send an encrypted password.
              u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,
              # This is an account for users whose primary account is in another domain. This account
              # provides user access to this domain, but not to any domain that trusts this domain.
              # Also known as a local user account.
              u_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,
              # This is a default account type that represents a typical user.
              u_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,
              # This is a permit to trust account for a system domain that trusts other domains.
              u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,
              # This is a computer account for a computer that is a member of this domain.
              u_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,
              # This is a computer account for a system backup domain controller that is a member of this domain.
              u_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,
              # The password for this account will never expire.
              u_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,
              # This is an MNS logon account.
              u_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,
              # The user must log on using a smart card.
              u_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,
              # The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.
              # Any such service can impersonate a client requesting the service.
              u_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,
              # The security context of the user will not be delegated to a service even if the service
              # account is set as trusted for Kerberos delegation.
              u_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,
              # Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.
              u_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,
              # This account does not require Kerberos pre-authentication for logon.
              u_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,
              # The password has expired
              u_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,
              # The account is enabled for delegation. This is a security-sensitive setting; accounts with
              # this option enabled should be strictly controlled. This setting enables a service running
              # under the account to assume a client identity and authenticate as that user to other remote
              # servers on the network.
              u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,
              # Now add sAMAccountType constants
              u_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
              u_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
              u_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
              u_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
              u_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
              u_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
              u_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
              u_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
              u_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
              u_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
              u_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
            }
            run_sqlite_query(db, 'ad_users', sql_param_user)

            # Now associate the user with the group
            sql_param_mapping = {
              user_rid: user_rid,
              group_rid: group_rid
            }
            run_sqlite_query(db, 'ad_mapping', sql_param_mapping)
          end
        rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
          print_error("Error(Users): #{e.message}")
          next
        end
      end
      group_gather.map(&:join)
    end

    vprint_status 'Retrieving computers'
    begin
      computer_filter = '(objectClass=computer)'
      computer_fields = ['distinguishedName', 'objectSid', 'cn', 'dNSHostName', 'sAMAccountType', 'sAMAccountName', 'displayName', 'logonCount', 'userAccountControl', 'whenChanged', 'whenCreated', 'primaryGroupID', 'badPwdCount', 'operatingSystem', 'operatingSystemServicePack', 'operatingSystemVersion', 'description', 'comment']
      computers = query(computer_filter, max_search, computer_fields)

      computers[:results].each do |comp|
        computer_rid = get_rid(comp[1][:value]).to_i

        uac_int = comp[8][:value].to_i # Set this because it is used so frequently below
        sat_int = comp[4][:value].to_i

        # Add the group to the database
        # Also parse the ADF_ flags from userAccountControl: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx
        # Note that userAccountControl is basically the same for a computer as a user; this is because a computer account is derived from a user account
        # (if you look at the objectClass for a computer account, it includes 'user') and, for efficiency, we should really store it all in one
        # table. However, the reality is that it will get annoying for users to have to remember to use the userAccountControl flags to work out whether
        # its a user or a computer and so, for convenience and ease of use, I have put them in completely separate tables.
        # Also add the sAMAccount type flags from https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637(v=vs.85).aspx
        sql_param_computer = {
          c_rid: computer_rid,
          c_distinguishedName: comp[0][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_cn: comp[2][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_dNSHostName: comp[3][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_sAMAccountType: sat_int,
          c_sAMAccountName: comp[5][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_displayName: comp[6][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_logonCount: comp[7][:value].to_i,
          c_userAccountControl: uac_int,
          c_whenChanged: comp[9][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_whenCreated: comp[10][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_primaryGroupID: comp[11][:value].to_i,
          c_badPwdCount: comp[12][:value].to_i,
          c_operatingSystem: comp[13][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_operatingSystemServicePack: comp[14][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_operatingSystemVersion: comp[15][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_description: comp[16][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          c_comment: comp[17][:value].encode('UTF-16be', invalid: :replace, undef: :replace, replace: '?').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
          # The login script is executed
          c_ADS_UF_SCRIPT: (uac_int & 0x00000001).zero? ? 0 : 1,
          # The user account is disabled.
          c_ADS_UF_ACCOUNTDISABLE: (uac_int & 0x00000002).zero? ? 0 : 1,
          # The home directory is required.
          c_ADS_UF_HOMEDIR_REQUIRED: (uac_int & 0x00000008).zero? ? 0 : 1,
          # The account is currently locked out.
          c_ADS_UF_LOCKOUT: (uac_int & 0x00000010).zero? ? 0 : 1,
          # No password is required.
          c_ADS_UF_PASSWD_NOTREQD: (uac_int & 0x00000020).zero? ? 0 : 1,
          # The user cannot change the password.
          c_ADS_UF_PASSWD_CANT_CHANGE: (uac_int & 0x00000040).zero? ? 0 : 1,
          # The user can send an encrypted password.
          c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: (uac_int & 0x00000080).zero? ? 0 : 1,
          # This is an account for users whose primary account is in another domain. This account
          # provides user access to this domain, but not to any domain that trusts this domain.
          # Also known as a local user account.
          c_ADS_UF_TEMP_DUPLICATE_ACCOUNT: (uac_int & 0x00000100).zero? ? 0 : 1,
          # This is a default account type that represents a typical user.
          c_ADS_UF_NORMAL_ACCOUNT: (uac_int & 0x00000200).zero? ? 0 : 1,
          # This is a permit to trust account for a system domain that trusts other domains.
          c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: (uac_int & 0x00000800).zero? ? 0 : 1,
          # This is a computer account for a computer that is a member of this domain.
          c_ADS_UF_WORKSTATION_TRUST_ACCOUNT: (uac_int & 0x00001000).zero? ? 0 : 1,
          # This is a computer account for a system backup domain controller that is a member of this domain.
          c_ADS_UF_SERVER_TRUST_ACCOUNT: (uac_int & 0x00002000).zero? ? 0 : 1,
          # The password for this account will never expire.
          c_ADS_UF_DONT_EXPIRE_PASSWD: (uac_int & 0x00010000).zero? ? 0 : 1,
          # This is an MNS logon account.
          c_ADS_UF_MNS_LOGON_ACCOUNT: (uac_int & 0x00020000).zero? ? 0 : 1,
          # The user must log on using a smart card.
          c_ADS_UF_SMARTCARD_REQUIRED: (uac_int & 0x00040000).zero? ? 0 : 1,
          # The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation.
          # Any such service can impersonate a client requesting the service.
          c_ADS_UF_TRUSTED_FOR_DELEGATION: (uac_int & 0x00080000).zero? ? 0 : 1,
          # The security context of the user will not be delegated to a service even if the service
          # account is set as trusted for Kerberos delegation.
          c_ADS_UF_NOT_DELEGATED: (uac_int & 0x00100000).zero? ? 0 : 1,
          # Restrict this principal to use only Data #Encryption Standard (DES) encryption types for keys.
          c_ADS_UF_USE_DES_KEY_ONLY: (uac_int & 0x00200000).zero? ? 0 : 1,
          # This account does not require Kerberos pre-authentication for logon.
          c_ADS_UF_DONT_REQUIRE_PREAUTH: (uac_int & 0x00400000).zero? ? 0 : 1,
          # The password has expired
          c_ADS_UF_PASSWORD_EXPIRED: (uac_int & 0x00800000).zero? ? 0 : 1,
          # The account is enabled for delegation. This is a security-sensitive setting; accounts with
          # this option enabled should be strictly controlled. This setting enables a service running
          # under the account to assume a client identity and authenticate as that user to other remote
          # servers on the network.
          c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: (uac_int & 0x01000000).zero? ? 0 : 1,
          # Now add the sAMAccountType objects
          c_SAM_DOMAIN_OBJECT: (sat_int == 0) ? 1 : 0,
          c_SAM_GROUP_OBJECT: (sat_int == 0x10000000) ? 1 : 0,
          c_SAM_NON_SECURITY_GROUP_OBJECT: (sat_int == 0x10000001) ? 1 : 0,
          c_SAM_ALIAS_OBJECT: (sat_int == 0x20000000) ? 1 : 0,
          c_SAM_NON_SECURITY_ALIAS_OBJECT: (sat_int == 0x20000001) ? 1 : 0,
          c_SAM_NORMAL_USER_ACCOUNT: (sat_int == 0x30000000) ? 1 : 0,
          c_SAM_MACHINE_ACCOUNT: (sat_int == 0x30000001) ? 1 : 0,
          c_SAM_TRUST_ACCOUNT: (sat_int == 0x30000002) ? 1 : 0,
          c_SAM_APP_BASIC_GROUP: (sat_int == 0x40000000) ? 1 : 0,
          c_SAM_APP_QUERY_GROUP: (sat_int == 0x40000001) ? 1 : 0,
          c_SAM_ACCOUNT_TYPE_MAX: (sat_int == 0x7fffffff) ? 1 : 0
        }
        run_sqlite_query(db, 'ad_computers', sql_param_computer)
        print_line "Computer [#{sql_param_computer[:c_cn]}][#{sql_param_computer[:c_dNSHostName]}][#{sql_param_computer[:c_rid]}]" if datastore['SHOW_COMPUTERS']
      end
    rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
      print_error("Error(Computers): #{e.message}")
      return
    end

    loot_path = store_loot(
      'host.ad_to_sqlite',
      'application/x-sqlite3',
      session,
      File.binread(dbfile.path),
      'ad_to_sqlite.db',
      'AD Computer, Group and Recursive User Membership'
    )

    print_good("Sqlite extraction stored in file: #{loot_path}")
  ensure
    # Finished enumeration, cleanup resources
    if db
      db.close
    end

    if dbfile
      dbfile.close
      dbfile.unlink
    end
  end

  # Run the parameterised SQL query
  def run_sqlite_query(db, table_name, values)
    sql_param_columns = values.keys
    sql_param_bind_params = values.keys.map { |k| ":#{k}" }
    db.execute("replace into #{table_name} (#{sql_param_columns.join(',')}) VALUES (#{sql_param_bind_params.join(',')})", values)
  end

  # Creat the SQLite Database
  def create_sqlite_db
    dbfile = Tempfile.new('ad_to_sqlite')
    db = SQLite3::Database.new(dbfile.path)
    db.type_translation = true

    # Create the table for the AD Computers
    db.execute('DROP TABLE IF EXISTS ad_computers')
    sql_table_computers = 'CREATE TABLE ad_computers ('\
                         'c_rid INTEGER PRIMARY KEY NOT NULL,'\
                         'c_distinguishedName TEXT UNIQUE NOT NULL,'\
                         'c_cn TEXT,'\
                         'c_sAMAccountType INTEGER,'\
                         'c_sAMAccountName TEXT UNIQUE NOT NULL,'\
                         'c_dNSHostName TEXT,'\
                         'c_displayName TEXT,'\
                         'c_logonCount INTEGER,'\
                         'c_userAccountControl INTEGER,'\
                         'c_primaryGroupID INTEGER,'\
                         'c_badPwdCount INTEGER,'\
                         'c_description TEXT,'\
                         'c_comment TEXT,'\
                         'c_operatingSystem TEXT,'\
                         'c_operatingSystemServicePack TEXT,'\
                         'c_operatingSystemVersion TEXT,'\
                         'c_whenChanged TEXT,'\
                         'c_whenCreated TEXT,'\
                         'c_ADS_UF_SCRIPT INTEGER,'\
                         'c_ADS_UF_ACCOUNTDISABLE INTEGER,'\
                         'c_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\
                         'c_ADS_UF_LOCKOUT INTEGER,'\
                         'c_ADS_UF_PASSWD_NOTREQD INTEGER,'\
                         'c_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\
                         'c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\
                         'c_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\
                         'c_ADS_UF_NORMAL_ACCOUNT INTEGER,'\
                         'c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\
                         'c_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\
                         'c_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\
                         'c_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\
                         'c_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\
                         'c_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\
                         'c_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\
                         'c_ADS_UF_NOT_DELEGATED INTEGER,'\
                         'c_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\
                         'c_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\
                         'c_ADS_UF_PASSWORD_EXPIRED INTEGER,'\
                         'c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\
                         'c_SAM_DOMAIN_OBJECT INTEGER,'\
                         'c_SAM_GROUP_OBJECT INTEGER,'\
                         'c_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
                         'c_SAM_ALIAS_OBJECT INTEGER,'\
                         'c_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
                         'c_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
                         'c_SAM_MACHINE_ACCOUNT INTEGER,'\
                         'c_SAM_TRUST_ACCOUNT INTEGER,'\
                         'c_SAM_APP_BASIC_GROUP INTEGER,'\
                         'c_SAM_APP_QUERY_GROUP INTEGER,'\
                         'c_SAM_ACCOUNT_TYPE_MAX INTEGER)'
    db.execute(sql_table_computers)

    # Create the table for the AD Groups
    db.execute('DROP TABLE IF EXISTS ad_groups')
    sql_table_group = 'CREATE TABLE ad_groups ('\
                         'g_rid INTEGER PRIMARY KEY NOT NULL,'\
                         'g_distinguishedName TEXT UNIQUE NOT NULL,'\
                         'g_sAMAccountType INTEGER,'\
                         'g_sAMAccountName TEXT UNIQUE NOT NULL,'\
                         'g_groupType INTEGER,'\
                         'g_adminCount INTEGER,'\
                         'g_description TEXT,'\
                         'g_comment TEXT,'\
                         'g_cn TEXT,'\
                         'g_managedBy TEXT,'\
                         'g_whenChanged TEXT,'\
                         'g_whenCreated TEXT,'\
                         'g_GT_GROUP_CREATED_BY_SYSTEM INTEGER,'\
                         'g_GT_GROUP_SCOPE_GLOBAL INTEGER,'\
                         'g_GT_GROUP_SCOPE_LOCAL INTEGER,'\
                         'g_GT_GROUP_SCOPE_UNIVERSAL INTEGER,'\
                         'g_GT_GROUP_SAM_APP_BASIC INTEGER,'\
                         'g_GT_GROUP_SAM_APP_QUERY INTEGER,'\
                         'g_GT_GROUP_SECURITY INTEGER,'\
                         'g_GT_GROUP_DISTRIBUTION INTEGER,'\
                         'g_SAM_DOMAIN_OBJECT INTEGER,'\
                         'g_SAM_GROUP_OBJECT INTEGER,'\
                         'g_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
                         'g_SAM_ALIAS_OBJECT INTEGER,'\
                         'g_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
                         'g_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
                         'g_SAM_MACHINE_ACCOUNT INTEGER,'\
                         'g_SAM_TRUST_ACCOUNT INTEGER,'\
                         'g_SAM_APP_BASIC_GROUP INTEGER,'\
                         'g_SAM_APP_QUERY_GROUP INTEGER,'\
                         'g_SAM_ACCOUNT_TYPE_MAX INTEGER)'
    db.execute(sql_table_group)

    # Create the table for the AD Users
    db.execute('DROP TABLE IF EXISTS ad_users')
    sql_table_users = 'CREATE TABLE ad_users ('\
                         'u_rid INTEGER PRIMARY KEY NOT NULL,'\
                         'u_distinguishedName TEXT UNIQUE NOT NULL,'\
                         'u_description TEXT,'\
                         'u_displayName TEXT,'\
                         'u_sAMAccountType INTEGER,'\
                         'u_sAMAccountName TEXT,'\
                         'u_logonCount INTEGER,'\
                         'u_userAccountControl INTEGER,'\
                         'u_primaryGroupID INTEGER,'\
                         'u_cn TEXT,'\
                         'u_adminCount INTEGER,'\
                         'u_badPwdCount INTEGER,'\
                         'u_userPrincipalName TEXT UNIQUE,'\
                         'u_comment TEXT,'\
                         'u_title TEXT,'\
                         'u_manager TEXT,'\
                         'u_whenCreated TEXT,'\
                         'u_whenChanged TEXT,'\
                         'u_ADS_UF_SCRIPT INTEGER,'\
                         'u_ADS_UF_ACCOUNTDISABLE INTEGER,'\
                         'u_ADS_UF_HOMEDIR_REQUIRED INTEGER,'\
                         'u_ADS_UF_LOCKOUT INTEGER,'\
                         'u_ADS_UF_PASSWD_NOTREQD INTEGER,'\
                         'u_ADS_UF_PASSWD_CANT_CHANGE INTEGER,'\
                         'u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED INTEGER,'\
                         'u_ADS_UF_TEMP_DUPLICATE_ACCOUNT INTEGER,'\
                         'u_ADS_UF_NORMAL_ACCOUNT INTEGER,'\
                         'u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT INTEGER,'\
                         'u_ADS_UF_WORKSTATION_TRUST_ACCOUNT INTEGER,'\
                         'u_ADS_UF_SERVER_TRUST_ACCOUNT INTEGER,'\
                         'u_ADS_UF_DONT_EXPIRE_PASSWD INTEGER,'\
                         'u_ADS_UF_MNS_LOGON_ACCOUNT INTEGER,'\
                         'u_ADS_UF_SMARTCARD_REQUIRED INTEGER,'\
                         'u_ADS_UF_TRUSTED_FOR_DELEGATION INTEGER,'\
                         'u_ADS_UF_NOT_DELEGATED INTEGER,'\
                         'u_ADS_UF_USE_DES_KEY_ONLY INTEGER,'\
                         'u_ADS_UF_DONT_REQUIRE_PREAUTH INTEGER,'\
                         'u_ADS_UF_PASSWORD_EXPIRED INTEGER,'\
                         'u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION INTEGER,'\
                         'u_SAM_DOMAIN_OBJECT INTEGER,'\
                         'u_SAM_GROUP_OBJECT INTEGER,'\
                         'u_SAM_NON_SECURITY_GROUP_OBJECT INTEGER,'\
                         'u_SAM_ALIAS_OBJECT INTEGER,'\
                         'u_SAM_NON_SECURITY_ALIAS_OBJECT INTEGER,'\
                         'u_SAM_NORMAL_USER_ACCOUNT INTEGER,'\
                         'u_SAM_MACHINE_ACCOUNT INTEGER,'\
                         'u_SAM_TRUST_ACCOUNT INTEGER,'\
                         'u_SAM_APP_BASIC_GROUP INTEGER,'\
                         'u_SAM_APP_QUERY_GROUP INTEGER,'\
                         'u_SAM_ACCOUNT_TYPE_MAX INTEGER)'
    db.execute(sql_table_users)

    # Create the table for the mapping between the two (membership)
    db.execute('DROP TABLE IF EXISTS ad_mapping')
    sql_table_mapping = 'CREATE TABLE ad_mapping ('\
                         'user_rid INTEGER NOT NULL,' \
                         'group_rid INTEGER NOT NULL,'\
                         'PRIMARY KEY (user_rid, group_rid),'\
                         'FOREIGN KEY(user_rid) REFERENCES ad_users(u_rid)'\
                         'FOREIGN KEY(group_rid) REFERENCES ad_groups(g_rid))'
    db.execute(sql_table_mapping)

    # Create the view for the AD User/Group membership
    db.execute('DROP VIEW IF EXISTS view_mapping')
    sql_view_mapping = 'CREATE VIEW view_mapping AS SELECT ad_groups.*,ad_users.* FROM ad_mapping '\
                       'INNER JOIN ad_groups ON ad_groups.g_rid = ad_mapping.group_rid '\
                       'INNER JOIN ad_users ON ad_users.u_rid = ad_mapping.user_rid'
    db.execute(sql_view_mapping)

    return db, dbfile
  rescue SQLite3::Exception => e
    print_error("Error(Database): #{e.message}")
    return
  end

  def get_rid(data)
    sid = data.unpack('bbbbbbbbV*')[8..]
    sid[-1]
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Feb 2023 13:47Current
7.9High risk
Vulners AI Score7.9
65