CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/enum_ad_computers.rb
Views: 11655
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
'Compat' => {
46
'Meterpreter' => {
47
'Commands' => %w[
48
stdapi_net_resolve_hosts
49
]
50
}
51
}
52
)
53
)
54
55
register_options([
56
OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]),
57
OptBool.new('STORE_DB', [true, 'Store file in DB (performance hit resolving IPs).', false]),
58
OptString.new('FIELDS', [true, 'FIELDS to retrieve.', 'dNSHostName,distinguishedName,description,operatingSystem,operatingSystemServicePack']),
59
OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=computer)(operatingSystem=*server*))'])
60
])
61
end
62
63
def run
64
fields = datastore['FIELDS'].gsub(/\s+/, '').split(',')
65
search_filter = datastore['FILTER']
66
max_search = datastore['MAX_SEARCH']
67
68
begin
69
q = query(search_filter, max_search, fields)
70
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
71
print_error(e.message)
72
return
73
end
74
75
return if q.nil? || q[:results].empty?
76
77
# Results table holds raw string data
78
results_table = Rex::Text::Table.new(
79
'Header' => 'Domain Computers',
80
'Indent' => 1,
81
'SortIndex' => -1,
82
'Columns' => fields
83
)
84
85
# Hostnames holds DNS Names to Resolve
86
hostnames = []
87
# Reports are collections for easy database insertion
88
reports = []
89
q[:results].each do |result|
90
row = []
91
92
report = {}
93
0.upto(fields.length - 1) do |i|
94
field = result[i][:value] || ''
95
96
# Only perform these actions if the database is connected and we want
97
# to store in the DB.
98
if db && datastore['STORE_DB']
99
case fields[i]
100
when 'dNSHostName'
101
dns = field
102
report[:name] = dns
103
hostnames << dns
104
when 'operatingSystem'
105
report[:os_name] = field.gsub("\xAE", '')
106
when 'distinguishedName'
107
if field =~ /Domain Controllers/i
108
# TODO: Find another way to mark a host as being a domain controller
109
# The 'purpose' field should be server, client, device, printer, etc
110
# report[:purpose] = "DC"
111
report[:purpose] = 'server'
112
end
113
when 'operatingSystemServicePack'
114
# XXX: Does this take into account the leading 'SP' string?
115
116
if field.to_i > 0
117
report[:os_sp] = 'SP' + field
118
end
119
if field =~ /(Service Pack|SP)\s?(\d+)/
120
report[:os_sp] = 'SP' + ::Regexp.last_match(2)
121
end
122
123
when 'description'
124
report[:info] = field
125
end
126
end
127
128
row << field
129
end
130
131
reports << report
132
results_table << row
133
end
134
135
if db && datastore['STORE_DB']
136
print_status('Resolving IP addresses...')
137
ip_results = client.net.resolve.resolve_hosts(hostnames, AF_INET)
138
139
# Merge resolved array with reports
140
reports.each do |report|
141
ip_results.each do |ip_result|
142
next unless ip_result[:hostname] == report[:name]
143
144
report[:host] = ip_result[:ip]
145
vprint_good("Database report: #{report.inspect}")
146
report_host(report)
147
end
148
end
149
end
150
151
print_line results_table.to_s
152
if datastore['STORE_LOOT']
153
stored_path = store_loot('ad.computers', 'text/plain', session, results_table.to_csv)
154
print_good("Results saved to: #{stored_path}")
155
end
156
end
157
end
158
159