Path: blob/master/modules/post/windows/gather/enum_ad_computers.rb
19669 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Auxiliary::Report7include Msf::Post::Windows::LDAP89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Windows Gather Active Directory Computers',14'Description' => %q{15This module will enumerate computers in the default AD directory.1617Optional Attributes to use in ATTRIBS:18objectClass, cn, description, distinguishedName, instanceType, whenCreated,19whenChanged, uSNCreated, uSNChanged, name, objectGUID,20userAccountControl, badPwdCount, codePage, countryCode,21badPasswordTime, lastLogoff, lastLogon, localPolicyFlags,22pwdLastSet, primaryGroupID, objectSid, accountExpires,23logonCount, sAMAccountName, sAMAccountType, operatingSystem,24operatingSystemVersion, operatingSystemServicePack, serverReferenceBL,25dNSHostName, rIDSetPreferences, servicePrincipalName, objectCategory,26netbootSCPBL, isCriticalSystemObject, frsComputerReferenceBL,27lastLogonTimestamp, msDS-SupportedEncryptionTypes2829ActiveDirectory has a MAX_SEARCH limit of 1000 by default. Split search up30if you hit that limit.3132Possible filters:33(objectClass=computer) # All Computers34(primaryGroupID=516) # All Domain Controllers35(&(objectCategory=computer)(operatingSystem=*server*)) # All Servers36},37'License' => MSF_LICENSE,38'Author' => [ 'Ben Campbell' ],39'Platform' => [ 'win' ],40'SessionTypes' => [ 'meterpreter' ],41'References' => [42['URL', 'http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx'],43],44'Notes' => {45'Stability' => [CRASH_SAFE],46'SideEffects' => [],47'Reliability' => []48},49'Compat' => {50'Meterpreter' => {51'Commands' => %w[52stdapi_net_resolve_hosts53]54}55}56)57)5859register_options([60OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]),61OptBool.new('STORE_DB', [true, 'Store file in DB (performance hit resolving IPs).', false]),62OptString.new('FIELDS', [true, 'FIELDS to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem,operatingSystemServicePack']),63OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=computer)(operatingSystem=*server*))'])64])65end6667def run68fields = datastore['FIELDS'].gsub(/\s+/, '').split(',')69search_filter = datastore['FILTER']70max_search = datastore['MAX_SEARCH']7172begin73q = query(search_filter, max_search, fields)74rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e75print_error(e.message)76return77end7879return if q.nil? || q[:results].empty?8081# Results table holds raw string data82results_table = Rex::Text::Table.new(83'Header' => 'Domain Computers',84'Indent' => 1,85'SortIndex' => -1,86'Columns' => fields87)8889# Hostnames holds DNS Names to Resolve90hostnames = []91# Reports are collections for easy database insertion92reports = []93q[:results].each do |result|94row = []9596report = {}970.upto(fields.length - 1) do |i|98field = result[i][:value] || ''99100# Only perform these actions if the database is connected and we want101# to store in the DB.102if db && datastore['STORE_DB']103case fields[i]104when 'dNSHostName'105dns = field106report[:name] = dns107hostnames << dns108when 'operatingSystem'109report[:os_name] = field.gsub("\xAE", '')110when 'distinguishedName'111if field =~ /Domain Controllers/i112# TODO: Find another way to mark a host as being a domain controller113# The 'purpose' field should be server, client, device, printer, etc114# report[:purpose] = "DC"115report[:purpose] = 'server'116end117when 'operatingSystemServicePack'118# XXX: Does this take into account the leading 'SP' string?119120if field.to_i > 0121report[:os_sp] = 'SP' + field122end123if field =~ /(Service Pack|SP)\s?(\d+)/124report[:os_sp] = 'SP' + ::Regexp.last_match(2)125end126127when 'description'128report[:info] = field129end130end131132row << field133end134135reports << report136results_table << row137end138139if db && datastore['STORE_DB']140print_status('Resolving IP addresses...')141ip_results = client.net.resolve.resolve_hosts(hostnames, AF_INET)142143# Merge resolved array with reports144reports.each do |report|145ip_results.each do |ip_result|146next unless ip_result[:hostname] == report[:name]147148report[:host] = ip_result[:ip]149vprint_good("Database report: #{report.inspect}")150report_host(report)151end152end153end154155print_line results_table.to_s156if datastore['STORE_LOOT']157stored_path = store_loot('ad.computers', 'text/plain', session, results_table.to_csv)158print_good("Results saved to: #{stored_path}")159end160end161end162163164