A Brief Look at FortiJump (FortiManager CVE-2024-47575)
2024-11-2 00:15:0 Author: bishopfox.com(查看原文) 阅读量:34 收藏

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.

Desoldered the SPI flash chip connected it to a cheap TL866II universal programmer.
Figure 1: EMMC (bottom) and SPI flash (top) inside of our FG60-E

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

Call to system() with externally controlled contents was removed
Figure 2: Call to system() with externally controlled contents was removed

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.


文章来源: https://bishopfox.com/blog/a-look-at-fortijump-cve-2024-47575
如有侵权请联系:admin#unsafe.sh