CVE-2024-47575, also known as FortiJump, recently gained widespread attention after news of in-the-wild exploitation leaked prior to any security advisory. According to Mandiant, a threat actor has been exploiting this vulnerability since at least June 2024. Given the highly sensitive nature of centralized management devices, we decided to take a deeper look.
Connecting a Device to FortiManager
The advisory describes CVE-2024-47575 as “missing authentication in fgfmsd”. Combined with the list of IoCs and mitigations, we can assume the first step of the exploit is registering a device.
We started by setting up a lab environment with a FortiManager VM and a FortiGate firewall, then configured the FortiGate firewall to connect to the FortiManager instance:
config system central-management set type fortimanager set fmg “192.168.250.103” end
We were able to confirm that the device shows up in our FortiManager
# diagnose dvm device list --- There are currently 1 devices/vdoms managed --- --- There are currently 1 devices/vdoms count for license --- TYPE OID SN HA IP NAME ADOM IPS FIRMWARE HW_GenX unregistered 166 FGTXXXXXXXXXXXXX - 192.168.250.124 FGTXXXXXXXXXXXXX root N/A 7.0 MR2 (1396) N/A
From our understanding of the vulnerability, getting a device in this unregistered state should be the first step to exploiting CVE-2024-47575. The next step is to replicate the requests in Python.
We know the FortiGate to FortiManager (FGFM) protocol uses TLS on TCP port 541, so we started a TLS server and configured our FortiGate firewall to use that as its FortiManager IP. Unfortunately, the FortiGate refused to connect
# ncat --ssl -nlvp 541 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Generating a temporary 2048-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one. Ncat: SHA-1 fingerprint: F74F 629E 3757 3845 EC74 7EA6 ED47 B899 598A 2364 Ncat: Listening on :::541 Ncat: Listening on 0.0.0.0:541 Ncat: Connection from 192.168.250.124. Ncat: Connection from 192.168.250.124:8394. NCAT DEBUG: SSL_read error on 5: error:00000005:lib(0):func(0):DH lib
After examining logs on our FortiGate, we realized it was rejecting the auto-generated server certificate. Luckily, during prior research for CVE-2024-23113, we discovered that the FGFM client accepts the Fortinet factory certificate that is included on all FortiGate VMs (/data/etc/cert/local/root_Fortinet_Factory.cer
). The private key for this certificate is encrypted with a static AES key that can be extracted from the init binary. This time the FortiGate doesn’t immediately close the connection when it connects
# ncat --ssl --ssl-cert root_Fortinet_Factory.cer --ssl-key root_Fortinet_Factory.key -nlvp 541 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Listening on :::541 Ncat: Listening on 0.0.0.0:541 Ncat: Connection from 192.168.250.124. Ncat: Connection from 192.168.250.124:11554. 6get auth serialno=FGTXXXXXXXXXXXXX mgmtid=00000000-0000-0000-0000-000000000000 platform=FortiGate-60E fos_ver=700 minor=2 patch=4 build=1396 branch=1396 maxvdom=2 fg_ip=192.168.250.124 hostname=FGTXXXXXXXXXXXXX harddisk=yes biover=04000002 harddisk_size=30720 logdisk_size=30107 mgmt_mode=normal enc_flags=0 mgmtip=192.168.250.124 mgmtport=443
As described by Watchtowr (and shown in less detail in Phrack), FGFM messages begin with an 8 byte header consisting of a magic number (\x36\xe0\x11\x00
) and a size field. Each line ends with a CRLF, and the last line is empty and ends with a null byte. The first line is a request method/action, and the rest of the lines are key/value pairs. With this knowledge of the protocol, let’s write a Python script to send this data to FortiManager.
import socket, struct, ssl request=b"""get auth serialno=FGTXXXXXXXXXXXXX mgmtid=00000000-0000-0000-0000-000000000000 platform=FortiGate-60E fos_ver=700 minor=2 patch=4 build=1396 branch=1396 maxvdom=2 fg_ip=192.168.250.124 hostname=FGTXXXXXXXXXXXXX harddisk=yes biover=04000002 harddisk_size=30720 logdisk_size=30107 mgmt_mode=normal enc_flags=0 mgmtip=192.168.250.124 mgmtport=443 \0""".replace(b"\n",b"\r\n") def sendmsg(socket, request): message=struct.pack(">II", 0x36e01100, len(request)+8)+request socket.send(message) hdr=socket.read(8) if len(hdr)!=8: return hdr magic, size=struct.unpack(">II", socket.read(8)) return socket.read(size) host=("192.168.250.103","541") context=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.check_hostname=False context.verify_mode=ssl.CERT_NONE s=socket.create_connection(host, 3) ssl_sock=context.wrap_socket(s) response=sendmsg(ssl_sock, request) print(response)
Upon running this, we’re met with an exception:
$ python3 test-47575.py Traceback (most recent call last): File "test-47575.py", line 41, in <module> response=sendmsg(ssl_sock, request) File "test-47575.py", line 30, in sendmsg magic, size=struct.unpack(">II", socket.read(8)) File "/usr/lib/python3.8/ssl.py", line 1130, in read return self._sslobj.read(len) ssl.SSLError: [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2649)
The server is requesting a client certificate. Let's try that factory certificate from earlier.
+ context.load_cert_chain(certfile="root_Fortinet_Factory.cer ",keyfile="root_Fortinet_Factory.key") $ python3 test-47575.py b‘’
This time we got an empty response, so at least it looks like our certificate was accepted. Despite that, when we looked at the FortiManager logs, we noticed that the request was still being rejected
__get_handler: SNs don’t match <FortiGate> < FGTXXXXXXXXXXXXX >
We realized the first value (“FortiGate”) was pulled from the client certificate common name, and the second value was the serialno parameter in the request. After changing the serialno parameter to “FortiGate”, we were met with a new error message:
Serial number does not match device model
The serial number begins with a prefix that identifies the product. Unfortunately, "FortiGate” doesn’t match the prefix of any valid device, which means we couldn’t use this certificate after all. That said, we already happened to have a device certificate that we extracted from a FortiGate appliance during prior research.
Accidentally Extracting Device Certificates, The Hard Way
We typically perform our research on FortiGate VM appliances out of convenience, but many companies use hardware appliances. As a result, we purchased a secondhand FortiGate 60E for research purposes last year. Our main goal was to identify any important differences between hardware and VM appliances, and part of that included looking at FortiGate’s modified U-Boot bootloader.
The FG60E appliance has two storage locations. The main storage is an 8GB EMMC chip, which stores the FortiOS system and configuration data. The second location is a 2MiB SPI flash chip. This flash chip stores the bootloader as well as some additional identifiers that are persistent across factory resets.
To dump the bootloader, we desoldered the SPI flash chip and connected it to a cheap TL866II universal programmer. The output was a raw dump of the flash contents, and our first step in analyzing this file was to run binwalk.
$ binwalk spi.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 280184 0x44678 CRC32 polynomial table, little endian 325409 0x4F721 Certificate in DER format (x509 v3), header length: 4, sequence length: 76 325413 0x4F725 Certificate in DER format (x509 v3), header length: 4, sequence length: 144 325461 0x4F755 Certificate in DER format (x509 v3), header length: 4, sequence length: 184 325509 0x4F785 Certificate in DER format (x509 v3), header length: 4, sequence length: 176 325557 0x4F7B5 Certificate in DER format (x509 v3), header length: 4, sequence length: 168 325601 0x4F7E1 Certificate in DER format (x509 v3), header length: 4, sequence length: 92 325605 0x4F7E5 Certificate in DER format (x509 v3), header length: 4, sequence length: 160 325649 0x4F811 Certificate in DER format (x509 v3), header length: 4, sequence length: 84 325653 0x4F815 Certificate in DER format (x509 v3), header length: 4, sequence length: 152 2088962 0x1FE002 PEM certificate 2093584 0x1FF210 PEM certificate 2095106 0x1FF802 PEM RSA private key
Binwalk identified a “PEM RSA private key” as well as several certificates. After extracting the certificate and private key at the very end of the flash dump, we realized that this was the device certificate and associated private key. Although we didn't have any use for this certificate at the time, the fact that we already had this certificate from prior research saved us a lot of time when analyzing CVE-2024-47575.
After plugging the FG60E device certificate and key into our script, we receive a response and see our device in the FortiManager device list.
b'0\r\nrequest=auth\r\nserialno=FMG-VM0000000000\r\nuser=\r\npasswd=\r\nmgmtport=443\r\nkeepalive_interval=120\r\nchan_window_sz=32768\r\nsock_timeout=360\r\nmgmtid=3939167a-975d-51df-4d9a-046004f6d298\r\n\r\n\x00'
Firmware Decryption
Now that we can talk to the FortiManager intance via FGFM, we need to know what to tell it. The advisory doesn’t provide many hints, so we turned to patch diffing. Unfortunately, we immediately hit a snag since, much like FortiGate devices, the FortiManager rootfs files are encrypted. Even worse, the firmware encryption seemed to be completely different from FortiGate firmware encryption.
We began by unpacking the firmware update file, which initially looks similar to FortiGate. It contains a rootfs.gz
and a vmlinuz
, which we expect to correspond to the initramfs and kernel image. As noted, the rootfs is encrypted, so we planned to start our analysis with the kernel image. We tried to use vmlinux-to-elf to convert the kernel image into a format that we could load into Ghidra, but it failed to find the kernel version and symbol table.
We quickly realized that not only was the rootfs encrypted, but the kernel itself was also obfuscated. From a hexdump, we could clearly see enough structure to indicate that parts of the file were unencrypted, and we could even see some strings related to decompressing the gzip-compressed kernel image. Despite seeing these strings, we didn’t see any gzip header in the binary. This led us down the path of reverse engineering the stub decompressor and finding the custom code responsible for the obfuscation.
Before decompressing the kernel, the decompression function XORs a large range of memory with a 32-byte key. We wrote a script to XOR this data in-place, and after doing so we were able to successfully run vmlinux-to-elf and load a deobfuscated kernel image into Ghidra.
Drawing on experience from breaking FortiGate encryption, we traced the functions responsible for loading the initramfs and were able to find a custom function that loads a static AES-CTR key and IV. We extracted these values and were able to successfully decrypt the rootfs images, which finally gave us access to the firmware running on the FortiManager.
Patch Diffing
Unlike FortiGate, where nearly all functionality is combined into the /bin/init
binary, FortiManager is organized more like a typical Linux system with many binaries, libraries, and scripts. We used pkgdiff to narrow these files down, and we found 5 libraries and 10 binaries had changed between versions 7.6.0 and 7.6.1. Our eyes were immediately drawn to fgfmsd, which is the service mentioned in the advisory, but we didn’t find anything that looked like a vulnerability fix in the dozens of changed functions. This led us to look at other changed components, including the shared libraries. libdmserver.so
contained what looks like a fix for a command injection vulnerability
The patched function seemed to be called by a handler for an “rcs/checkout
” operation, and the function seemed to be checking out a database from an internal RCS repository. This RCS repository seems to be used for configuration management, and the database corresponds to a device configuration. We suspect that an exploit would involve sending some sort of FGFM request for a device configuration, and that a parameter in that request would be passed to the /bin/cp command. Unfortunately, we were unable to identify a request or parameter that could be used to exploit this vulnerability.
Conclusion
Although we weren’t able to create a full proof-of-concept exploit, CVE-2024-47575 appears to be a command injection vulnerability. Command injections are a very highly exploitable class of bug since the same payload can generally be used for all vulnerable devices and versions. As such, we recommend patching as soon as possible. Whether patched or not, we also recommend restricting access to the FGFM port as much as possible by following the mitigation steps in the CVE-2024-47575 advisory. Given that this is the third vulnerability related to the FGFM, it seems unwise to leave the service publicly accessible on the internet.
As always, Bishop Fox used this research to scan and alert affected Cosmos customers.
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.
Recommended Posts
You might be interested in these related posts.
Sep 24, 2024
Broken Hill: A Productionized Greedy Coordinate Gradient Attack Tool for Use Against Large Language Models
Sep 11, 2024
Exploring Large Language Models: Local LLM CTF & Lab
Jul 02, 2024
Product Security Review Methodology for Traeger Grill Hack
Jul 02, 2024
Traeger Grill D2 Wi-Fi Controller, Version 2.02.04