Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/enum_ad_computers.rb
19669 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Auxiliary::Report
8
include Msf::Post::Windows::LDAP
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Windows Gather Active Directory Computers',
15
'Description' => %q{
16
This module will enumerate computers in the default AD directory.
17
18
Optional Attributes to use in ATTRIBS:
19
objectClass, cn, description, distinguishedName, instanceType, whenCreated,
20
whenChanged, uSNCreated, uSNChanged, name, objectGUID,
21
userAccountControl, badPwdCount, codePage, countryCode,
22
badPasswordTime, lastLogoff, lastLogon, localPolicyFlags,
23
pwdLastSet, primaryGroupID, objectSid, accountExpires,
24
logonCount, sAMAccountName, sAMAccountType, operatingSystem,
25
operatingSystemVersion, operatingSystemServicePack, serverReferenceBL,
26
dNSHostName, rIDSetPreferences, servicePrincipalName, objectCategory,
27
netbootSCPBL, isCriticalSystemObject, frsComputerReferenceBL,
28
lastLogonTimestamp, msDS-SupportedEncryptionTypes
29
30
ActiveDirectory has a MAX_SEARCH limit of 1000 by default. Split search up
31
if you hit that limit.
32
33
Possible filters:
34
(objectClass=computer) # All Computers
35
(primaryGroupID=516) # All Domain Controllers
36
(&(objectCategory=computer)(operatingSystem=*server*)) # All Servers
37
},
38
'License' => MSF_LICENSE,
39
'Author' => [ 'Ben Campbell' ],
40
'Platform' => [ 'win' ],
41
'SessionTypes' => [ 'meterpreter' ],
42
'References' => [
43
['URL', 'http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx'],
44
],
45
'Notes' => {
46
'Stability' => [CRASH_SAFE],
47
'SideEffects' => [],
48
'Reliability' => []
49
},
50
'Compat' => {
51
'Meterpreter' => {
52
'Commands' => %w[
53
stdapi_net_resolve_hosts
54
]
55
}
56
}
57
)
58
)
59
60
register_options([
61
OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]),
62
OptBool.new('STORE_DB', [true, 'Store file in DB (performance hit resolving IPs).', false]),
63
OptString.new('FIELDS', [true, 'FIELDS to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem,operatingSystemServicePack']),
64
OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=computer)(operatingSystem=*server*))'])
65
])
66
end
67
68
def run
69
fields = datastore['FIELDS'].gsub(/\s+/, '').split(',')
70
search_filter = datastore['FILTER']
71
max_search = datastore['MAX_SEARCH']
72
73
begin
74
q = query(search_filter, max_search, fields)
75
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
76
print_error(e.message)
77
return
78
end
79
80
return if q.nil? || q[:results].empty?
81
82
# Results table holds raw string data
83
results_table = Rex::Text::Table.new(
84
'Header' => 'Domain Computers',
85
'Indent' => 1,
86
'SortIndex' => -1,
87
'Columns' => fields
88
)
89
90
# Hostnames holds DNS Names to Resolve
91
hostnames = []
92
# Reports are collections for easy database insertion
93
reports = []
94
q[:results].each do |result|
95
row = []
96
97
report = {}
98
0.upto(fields.length - 1) do |i|
99
field = result[i][:value] || ''
100
101
# Only perform these actions if the database is connected and we want
102
# to store in the DB.
103
if db && datastore['STORE_DB']
104
case fields[i]
105
when 'dNSHostName'
106
dns = field
107
report[:name] = dns
108
hostnames << dns
109
when 'operatingSystem'
110
report[:os_name] = field.gsub("\xAE", '')
111
when 'distinguishedName'
112
if field =~ /Domain Controllers/i
113
# TODO: Find another way to mark a host as being a domain controller
114
# The 'purpose' field should be server, client, device, printer, etc
115
# report[:purpose] = "DC"
116
report[:purpose] = 'server'
117
end
118
when 'operatingSystemServicePack'
119
# XXX: Does this take into account the leading 'SP' string?
120
121
if field.to_i > 0
122
report[:os_sp] = 'SP' + field
123
end
124
if field =~ /(Service Pack|SP)\s?(\d+)/
125
report[:os_sp] = 'SP' + ::Regexp.last_match(2)
126
end
127
128
when 'description'
129
report[:info] = field
130
end
131
end
132
133
row << field
134
end
135
136
reports << report
137
results_table << row
138
end
139
140
if db && datastore['STORE_DB']
141
print_status('Resolving IP addresses...')
142
ip_results = client.net.resolve.resolve_hosts(hostnames, AF_INET)
143
144
# Merge resolved array with reports
145
reports.each do |report|
146
ip_results.each do |ip_result|
147
next unless ip_result[:hostname] == report[:name]
148
149
report[:host] = ip_result[:ip]
150
vprint_good("Database report: #{report.inspect}")
151
report_host(report)
152
end
153
end
154
end
155
156
print_line results_table.to_s
157
if datastore['STORE_LOOT']
158
stored_path = store_loot('ad.computers', 'text/plain', session, results_table.to_csv)
159
print_good("Results saved to: #{stored_path}")
160
end
161
end
162
end
163
164