metasploit-framework/modules/post/windows/gather/enum_ad_computers.rb

172 lines
6.0 KiB
Ruby
Raw Normal View History

2012-12-17 14:07:35 +00:00
##
2013-10-15 19:52:12 +00:00
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
2012-12-17 14:07:35 +00:00
##
require 'rex'
2012-12-19 09:06:58 +00:00
require 'msf/core'
require 'msf/core/auxiliary/report'
2012-12-17 14:07:35 +00:00
class Metasploit3 < Msf::Post
2013-09-05 21:19:05 +00:00
include Msf::Auxiliary::Report
include Msf::Post::Windows::LDAP
2013-09-05 21:19:05 +00:00
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Gather Active Directory Computers',
'Description' => %Q{
This module will enumerate computers in the default AD directory.
Optional Attributes to use in ATTRIBS:
objectClass, cn, description, distinguishedName, instanceType, whenCreated,
whenChanged, uSNCreated, uSNChanged, name, objectGUID,
userAccountControl, badPwdCount, codePage, countryCode,
badPasswordTime, lastLogoff, lastLogon, localPolicyFlags,
pwdLastSet, primaryGroupID, objectSid, accountExpires,
logonCount, sAMAccountName, sAMAccountType, operatingSystem,
operatingSystemVersion, operatingSystemServicePack, serverReferenceBL,
dNSHostName, rIDSetPreferences, servicePrincipalName, objectCategory,
netbootSCPBL, isCriticalSystemObject, frsComputerReferenceBL,
lastLogonTimestamp, msDS-SupportedEncryptionTypes
ActiveDirectory has a MAX_SEARCH limit of 1000 by default. Split search up
if you hit that limit.
Possible filters:
(objectClass=computer) # All Computers
(primaryGroupID=516) # All Domain Controllers
(&(objectCategory=computer)(operatingSystem=*server*)) # All Servers
},
'License' => MSF_LICENSE,
'Author' => [ 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' ],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'References' =>
[
['URL', 'http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx'],
]
))
register_options([
OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 50]),
OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]),
OptBool.new('STORE_DB', [true, 'Store file in DB (performance hit resolving IPs).', true]),
OptString.new('ATTRIBS', [true, 'Attributes to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem,operatingSystemServicePack']),
OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=computer)(operatingSystem=*server*))'])
], self.class)
end
def run
print_status("Connecting to default LDAP server")
session_handle = bind_default_ldap_server(datastore['MAX_SEARCH'])
return false unless session_handle
print_status("Querying default naming context")
query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"])
first_entry_attributes = query_result[0]['attributes']
2013-11-23 22:11:46 +00:00
# Value from First Attribute of First Entry
defaultNamingContext = first_entry_attributes[0]['values']
2013-09-05 21:19:05 +00:00
print_status("Default Naming Context #{defaultNamingContext}")
attributes = datastore['ATTRIBS'].gsub(/\s+/,"").split(',')
search_filter = datastore['FILTER']
print_status("Querying #{search_filter} - Please wait...")
results = query_ldap(session_handle, defaultNamingContext, 2, search_filter, attributes)
print_status("Unbinding from LDAP service.")
wldap32.ldap_unbind(session_handle)
if results.nil? or results.empty?
return
end
2013-09-24 18:58:09 +00:00
# Results table holds raw string data
2013-09-05 21:19:05 +00:00
results_table = Rex::Ui::Text::Table.new(
'Header' => "#{defaultNamingContext} Domain Computers",
'Indent' => 1,
'SortIndex' => -1,
'Columns' => attributes
)
2013-09-24 18:58:09 +00:00
# Hostnames holds DNS Names to Resolve
hostnames = []
# Reports are collections for easy database insertion
reports = []
2013-09-05 21:19:05 +00:00
results.each do |result|
row = []
report = {}
result['attributes'].each do |attr|
if attr['values'].nil?
row << ""
else
row << attr['values']
# Only perform these actions if the database is connected and we want
# to store in the DB.
if db and datastore['STORE_DB']
case attr['name']
when 'dNSHostName'
dns = attr['values']
2013-09-24 18:58:09 +00:00
report[:name] = dns
hostnames << dns
2013-09-05 21:19:05 +00:00
when 'operatingSystem'
os = attr['values']
index = os.index(/windows/i)
2013-11-23 19:42:33 +00:00
if index
2013-09-05 21:19:05 +00:00
name = 'Microsoft Windows'
flavour = os[index..-1]
2013-09-24 18:58:09 +00:00
report[:os_name] = name
report[:os_flavor] = flavour
2013-09-05 21:19:05 +00:00
else
# Incase there are non-windows domain computers?!
2013-09-24 18:58:09 +00:00
report[:os_name] = os
2013-09-05 21:19:05 +00:00
end
when 'distinguishedName'
if attr['values'] =~ /Domain Controllers/i
2013-09-24 18:58:09 +00:00
report[:purpose] = "DC"
2013-09-05 21:19:05 +00:00
end
when 'operatingSystemServicePack'
2013-09-24 18:58:09 +00:00
report[:os_sp] = attr['values']
2013-09-05 21:19:05 +00:00
when 'description'
2013-09-24 18:58:09 +00:00
report[:info] = attr['values']
2013-09-05 21:19:05 +00:00
end
end
end
end
2013-09-24 18:58:09 +00:00
reports << report
2013-09-05 21:19:05 +00:00
results_table << row
2013-09-24 18:58:09 +00:00
end
2013-09-05 21:19:05 +00:00
2013-09-24 18:58:09 +00:00
if db and datastore['STORE_DB']
print_status("Resolving IP addresses...")
ip_results = client.net.resolve.resolve_hosts(hostnames, AF_INET)
# Merge resolved array with reports
reports.each do |report|
ip_results.each do |ip_result|
if ip_result[:hostname] == report[:name]
report[:host] = ip_result[:ip]
vprint_good("Database report: #{report.inspect}")
report_host(report)
end
end
end
2013-09-05 21:19:05 +00:00
end
print_line results_table.to_s
if datastore['STORE_LOOT']
stored_path = store_loot('ad.computers', 'text/plain', session, results_table.to_csv)
print_status("Results saved to: #{stored_path}")
end
end
2012-12-17 14:07:35 +00:00
end
2013-02-10 17:03:32 +00:00