150 lines
5.1 KiB
Ruby
150 lines
5.1 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'rex'
|
|
require 'msf/core'
|
|
require 'msf/core/auxiliary/report'
|
|
|
|
class Metasploit3 < Msf::Post
|
|
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Post::Windows::LDAP
|
|
|
|
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' ],
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'References' =>
|
|
[
|
|
['URL', 'http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx'],
|
|
]
|
|
))
|
|
|
|
register_options([
|
|
OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]),
|
|
OptBool.new('STORE_DB', [true, 'Store file in DB (performance hit resolving IPs).', false]),
|
|
OptString.new('FIELDS', [true, 'FIELDS to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem,operatingSystemServicePack']),
|
|
OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=computer)(operatingSystem=*server*))'])
|
|
], self.class)
|
|
end
|
|
|
|
def run
|
|
fields = datastore['FIELDS'].gsub(/\s+/,"").split(',')
|
|
search_filter = datastore['FILTER']
|
|
max_search = datastore['MAX_SEARCH']
|
|
q = query(search_filter, max_search, fields)
|
|
|
|
return if q.nil? or q[:results].empty?
|
|
|
|
# Results table holds raw string data
|
|
results_table = Rex::Ui::Text::Table.new(
|
|
'Header' => "Domain Computers",
|
|
'Indent' => 1,
|
|
'SortIndex' => -1,
|
|
'Columns' => fields
|
|
)
|
|
|
|
# Hostnames holds DNS Names to Resolve
|
|
hostnames = []
|
|
# Reports are collections for easy database insertion
|
|
reports = []
|
|
q[:results].each do |result|
|
|
row = []
|
|
|
|
report = {}
|
|
0.upto(fields.length-1) do |i|
|
|
field = result[i][:value] || ""
|
|
|
|
# Only perform these actions if the database is connected and we want
|
|
# to store in the DB.
|
|
if db && datastore['STORE_DB']
|
|
case fields[i]
|
|
when 'dNSHostName'
|
|
dns = field
|
|
report[:name] = dns
|
|
hostnames << dns
|
|
when 'operatingSystem'
|
|
report[:os_name] = field.gsub("\xAE",'')
|
|
when 'distinguishedName'
|
|
if field =~ /Domain Controllers/i
|
|
# TODO: Find another way to mark a host as being a domain controller
|
|
# The 'purpose' field should be server, client, device, printer, etc
|
|
#report[:purpose] = "DC"
|
|
report[:purpose] = "server"
|
|
end
|
|
when 'operatingSystemServicePack'
|
|
# XXX: Does this take into account the leading 'SP' string?
|
|
|
|
if field.to_i > 0
|
|
report[:os_sp] = 'SP' + field
|
|
end
|
|
if field =~ /(Service Pack|SP)\s?(\d+)/
|
|
report[:os_sp] = 'SP' + $2
|
|
end
|
|
|
|
when 'description'
|
|
report[:info] = field
|
|
end
|
|
end
|
|
|
|
row << field
|
|
end
|
|
|
|
reports << report
|
|
results_table << row
|
|
end
|
|
|
|
if db && 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
|
|
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
|
|
|
|
end
|
|
|