Dissecting FortiGate Images for fun and non-profit

Dissecting FortiGate Images for fun and non-profit

26 December 2022, by Axel Boesenach

Like many other researchers we recently dived into the new FortiGate SSLVPN vulnerability (CVE-2022-42475) which consists of a heap overflow vulnerability in the SSLVPN service.

From the FortiGuard PSIRT advisory:

A heap-based buffer overflow vulnerability [CWE-122] in FortiOS SSL-VPN may allow a remote unauthenticated attacker to execute arbitrary code or commands via specifically crafted requests.

We quickly jumped on it to see if we could find a method to accurately fingerprint the versions of the devices connected to the internet that are running the SSLVPN service. Luckily we have some volunteers that were able to quickly grab images of the vulnerable version, as well as the patched version so we could do some differential checks and maybe catch some low hanging fruit that would reveal the specific version of the device.

Researching

In this section we will dive into the research process as it was executed by one of the researchers. We will go into the tool that was used, as well as the thought process of the researchers involved.

Peering into the FortiGate images

As a start we checked the files that were present in the FortiGate images that we received, the quick and easy way is by leveraging the newly released Dissect project to open the images using the .VHD format of Hyper-V. The easiest way to open these virtual disks without mounting them in the traditional way is to use target-shell which is build upon the dissect.target module and consists of a set of loaders that can be used to open different image formats.

When using target-shell we are actually dropped inside of a shell-like environment that even has a handy help message showing the options available to us:

$ target-shell fortios.vhd
fortios.vhd /> help

Documented commands (type help <topic>):
========================================
cat    disks  filesystems  help     less  python    save
cd     exit   find         hexdump  ls    readlink  stat
clear  file   hash         info     pwd   registry  volumes

The virtual disk images hold the data needed for booting the device, as well as the filesystem that is loaded. To display the contents of the .VHD we can use the ls command:

fortios.vhd /> ls
.fgtsum
boot.msg
datafs.tar.gz
extlinux.conf
filechecksum
flatkc
flatkc.chk
ldlinux.c32
ldlinux.sys
lost+found
rootfs.gz
rootfs.gz.chk

The file we’re interested in is the rootfs.gz file, this file holds the actual filesystem that is loaded by the device like the binaries and libraries used to start the different services, as well as the web pages that are used by the services exposing different panels to the outside world, so let’s save this file:

fortios.vhd /> save rootfs.gz

We’re specifically looking for any kind of files that are showing us the exact version running on the device so we can differentiate patched devices from vulnerable devices, only notifying the owners of devices which are displaying the vulnerable versions.

It is advised to execute the next steps inside of a virtual machine as we’re rebuilding a file system that contains different kind of symbolic links that will be symlinked when extracted.

Before we can actually look at the files we’re interested in we have to extract the rootfs.gz file and decompress it, revealing the rootfs:

$ file rootfs                 
rootfs: ASCII cpio archive (SVR4 with no CRC)

The rootfs file is actually another kind of archive of the CPIO format. To extract the CPIO archive we can use a built-in GNU tool aptly called cpio:

# cat rootfs | cpio -mivd

The above output will be pretty verbose and display some linking errors when run as a non-root user on the system. So now we have almost rebuilded the filesystem of the FortiGate device so we can start our actual research. There are a couple more files that we need to extract before we can continue our journey, the files we’re really interested in are actually compressed in another archive using modified versions of xz and tar.

The migadmin.tar.xz archive is one of the archives we’re interested in as this archive is holding all of the files that belong to the webpanels and services exposed to the outside world. To extract this archive we can leverage the binaries within the sbin/ directory and using the chroot command to make sure the right libraries are used:

# chroot /tmp/forti sbin/xz -d migadmin.tar.xz
# chroot /tmp/forti sbin/ftar -xf migadmin.tar

After extracting the migadmin archive we can see that there are actually a lot of .gz files in there, for the sake of this post we will not look into every file but instead continue laying down our thought process for coming up with a fingerprint.

Looking for fingerprints

While one of our researchers was busy peering into the FortiGate images, other researchers were checking running FortiGate devices to see what files are exposed and if the files are containing any kind of unique values which could be used to identify versions. Early in the investigation it was observed that a lot of files contain hash values, this looked interesting and used as a first lead to see if we could match different hashes for the versions.

<script src="/c722dce07f115f2f4b4a029cca6503d6/sslvpn/js/common.js"></script>
<script src="/c722dce07f115f2f4b4a029cca6503d6/sslvpn/js/sslvpn_util.js"></script>
<script src="/c722dce07f115f2f4b4a029cca6503d6/sslvpn/js/aes.js"></script>

We started downloading a couple of images and indeed found that the hashes in the migadmin/sslvpn/portal.html file were unique for different versions, we thought this was great because this HTML is actually exposed when the SSLVPN service is running on the device.

Within a couple hours a list of hashes of the devices connected to the internet was made and we received more images of different versions, there was only one little problem… There were over 1600 (!!) different hashes in the list. In hindsight we could’ve concluded then and there (and some of us did) that these hashes were not the way to go, but because we found out there are actually hundreds of different versions of the FortiGate images, some of us were still on the fence of it being possible that these hashes could give us an insight into the versioning of the devices.

We decided to give it a go and downloaded a bunch of different images:

/fortigate_images# find . -name "*.zip" | wc -l
303

Going through the process described in the chapter before by hand was not an option, so we came up with a way to automate it.

Dissect

Next step of course is to automate the process of checking the 303 different FortiGate images for the portal.html files containing the hash using Dissect. It’s as easy as performing the steps described earlier, but using Python to automate the whole process and storing the different hashes of each version in a .json file.

>>> import io
>>> from dissect.target import Target
>>> from dissect.util import cpio
>>> # Get the rootfs.gz file and decompress it, resulting in the CPIO archive
>>> forti_target = Target.open("fortios.vhd")
>>> path = t.fs.path("/rootfs.gz")
>>> cpio_file = cpio.open(fileobj=path.open())

We now have a CpioFile object containing the root filesystem of the FortiGate image and can actually already peek inside of the structure of the filesystem by listing the files:

>>> cpio_file.list()
?rwxr-xr-x 0/0          0 2022-11-02 23:34:31 ./ 
?rwxr-xr-x 0/0          0 2022-11-02 23:32:15 tmp/ 
?rwxr-xr-x 0/0          0 2022-11-02 23:32:15 data2/ 
?rwxr-xr-x 0/0          0 2022-11-02 23:34:30 lib/ 
?rwxr-xr-x 0/0     113832 2022-11-02 23:32:19 lib/libvncclient.so.1 
?rwxr-xr-x 0/0     106032 2022-11-02 23:32:20 lib/libpthread.so.0 
?rwxr-xr-x 0/0      10160 2022-11-02 23:32:20 lib/libvmtools.so 
?rwxr-xr-x 0/0    1447184 2022-11-02 23:32:20 lib/libglib-2.0.so.0 
?rwxr-xr-x 0/0      39080 2022-11-02 23:32:20 lib/libcrypt.so.1 
?rwxr-xr-x 0/0    1586112 2022-11-02 23:32:20 lib/libstdc++.so.6

With this we can freely extract single files, or extract all of the files and directories much like a TAR file. From here on it’s pretty straightforward, we grab the files we’re interested in and can do our analysis in an automated fashion leveraging Python to do the heavy lifting for us so we can grab some coffee while it is happily parsing the image files.

if you’re interested in the script that was used you can find it at: https://gist.github.com/sud0woodo/36a3177de819621ed162b222c6f861e7

Conclusion

Analyzing any kind of data by hand is not efficient and error-prone, knowing which toolsets are at your disposal is just as important as knowing how to use these tools efficiently.

So what did we get out of this? Other than a nice overview of hashes per image, the gathered information is absolutely worthless for fingerprinting. But this blogpost wasn’t meant to deep dive into how a fingerprint was created as we hope that this post is insightful for security practitioners just starting to get their feet wet when it comes to automating the process when a hypothesis can’t be ruled out with just a couple of files. In the end we resorted to the partial fingerprint which was created by Edwin van Vliet during the DIVD-2022-00056 case.

Next to the above lesson we hope that this post was fun to read and maybe sparks some inspiration for other individuals to leverage these kind of techniques to automate the process when looking for fingerprints, or is just reading about Dissect for the first time and sees the potential in a current project or pipeline :)


Last modified: 27 Dec 2022 12:01