1Day vulnerability research by Hakai - Research Team
This post is about the journey to create a Proof-of-concept about CVE-2022-40684, this vulnerability has been assigned by Fortinet as an authentication bypass using an alternate path or channel vulnerability [CWE-288].
07/10/2022 - First news about the vulnerability, on 10/07/2022 Gi7w0rm tweeted that Fortinet was contacting some of vip customer warning about a critical vulnerability affecting their FortiGate firewall.
07/10/2022 - Our team started monitoring the internet to explore this type of vulnerability, and also started the intelligence process to identify the level of exposure of our customers. Looking for possible functional exploits, but we did not identify any type of exploitation in the wild and it was also not possible to identify any public exploit.
08/10/2022 - On this day our SRT (Security Research Team) started the process of analyzing the patch provided by Fortinet, the idea was understand how the patch works, so we could get the behavior of authentication flow and created an exploit for the vulnerability.
On the same day, our team found that it was a problem related to the API.
Knowing that the patch was directly related to the API, we started the discovery process to understand how the FortiGate API works, this analysis was done dynamically (Accessing the firewall and intercepting requests via a proxy like burp).
The first step was: download two versions of the firewall firmware, one vulnerable version and the other with the patch applied by Fortinet.
In the customers portal , we downloaded the two versions, 7.2.1 (vulnerable) and version 7.2.2 (with the Fortinet patch).
On the website, it is important to pay attention to downloading the firmware for the FortigateVM (FGT_VM64-v7.2.x.XXX-FORTINET.out.ovf.zip)
Now that we've downloaded the two firmware, let's separate them into different folders, "patched" and "old".
Now it is easier to know which firmware represents which version.
Initially, we can notice that the firmware is compressed with the zip format and inside it an OVF (Open Virtualization Format).
We have several files, but of the most important ones are the "VMDK" disk files.
The biggest disk is fortios.vmdk, which probably contains the entire firewall operating system, let's mount it and check the contents.
Let's do the same with the disk already with the Fortinet patch, this way it will be easier to make a "diff" between versions.
To mount the vmdk disk, we will need to install qemu-utils, let's run the following command:
sudo apt install qemu-utils
And after installing we will enable the ndb kernel module.
sudo modprobe nbd
After enabling the module, we can use qemu-nbd to mount the disks on our system, thus having access to the firewall disk in both versions, old and patched.
We can start with a simple comparison, between a file that exists between the two versions, for example, the file "boot.msg".
In the screenshot, we can see that there was no change in the "boot.msg" file between the two versions.
In this way, we can analyze other files and check which of them have changed.
We can identify that the next file in the sequence "datafs.tar.gz", is different in the two versions.
Another file that is also different in the new version is the "rootfs.gz" file, which apparently is the entire structure of the firewall's internal applications (including the API).
The current folders /mnt/fortigate-old and /mnt/fortigate-patched are in read-only format, so I will create two new folders and copy the two files to the respective folders.
Let's start unzipping the files:
After unzipping the files, I wasted a lot of time trying to figure out what the cpio format was.
Cpio comes from "Copy in and Copy out", this is a file compression format too, we can use the cpio command to extract this file.
After extracting the files we will have something like this:
Now we have several folders and files to analyze :)
I'll do the same with the "patched" version.
Now let's run the diff command to check the difference between the two folders:
We can see that the part responsible for the node API has changed.
The files compressed with .tar.xz were corrupted (this is what our uncompressed ones scored).
At this point, we thought about what could cause this, and when we couldn't solve this problem, so now we were sure that the problem was related to an API, we discussed it from the dynamic test (having directly on the firewall and going as requests).
09/10/2022 - On the 9th, the Horizon3 team shared a screenshot on Twitter with a possible exploit of the vulnerability, where it was possible to change the ssh key of the firewall administrator user, but they did not provide the source code of the exploit.
10/10/2022 - On this day, our team was able to successfully reproduce the exploit of the vulnerability, thus gaining full access.
The vulnerability is a misconfiguration flaw in the authentication of the FortiOS API, on the same day, our team posted about this vulnerability on Twitter, stating that it was not just a simple "auth bypass" vulnerability but a vulnerability that could lead to a complete firewall takeover.
Dynamic analysis process
We installed the firewall on our VMware, and we started the analysis in two ways the first is to understand how the frontend makes the requests and the second is to understand how these requests are processed by the firewall.
The first analysis was done using burp, and the second using the firewall's debug mode and also sniffing the loopback interface of the firewall.
We can see that one of the endpoints most accessed by the frontend is /api/v2/*
Now we can use the fortiOS CLI to understand what these requests are about.
FW-EXT # diagnose debug application httpsd 1 Debug messages will be on for 30 minutes. FW-EXT # diagnose debug enable
With the command "diagnose debug application httpsd 1" we are indicating a debug filter for the firewall, informing that we want to read the logs in real-time of this process, thus allowing us to understand what happens with each request.
Attention: We don't recommend doing this in a production environment, this type of activity consumes a lot of CPU and appliance memory!
The command "diagnose debug enable" actually enables debug mode.
Now we can track every request!
For testing purposes, let's make a simple request to the API "using the repeater" and check the behavior internally.
Now let's see how it looks in the CLI.
Apparently, Fortinet is adopting a service-based model in its backend, we can see several calls for different services like "api_cmdb_v2-handler", "api_store_parameter", "handle_cli_req_v2"
Another important thing during this process was sniffing the loopback interface of the firewall.
To sniff the requests we can go to.
- Network Tab
- select "Diagnostics"
- than "Packet Capture"
- choose interface as "any"
- turn on the filter
- add 127.0.0.1 on the host input
Then just click on "Start capture".
During the process, we left the sniffing process for 5 minutes while we used the application.
This helped a lot to understand how it works.
After you finish sniffing just click on stop and then on "Save as pcap".
We can use the "http.request" filter to catch only intercepted HTTP requests.
Now we can see that there are several HTTP requests that go from 127.0.0.1 to 127.0.0.1.
Initially, we understand that this will refer to a reverse proxy.
An example request intercepted by sniffing:
Do you notice something strange?
I've never seen this header in my life. And you ?
We have 4 fields inside this header separated by ";"
This somewhat resembles "X-Forwarded-For", normally added by proxies, but with additional fields.
This was the first thing we noticed weird about this request.
After a long time analyzing the pcaps, we identified a request made with the User Agent "Node.js"
It's interesting to note that in debug mode we got a different path than the others.
In this request, a call is made to a service "api_access_check_for_trusted_access", we can also notice that the request was made by the loopback itself.
After some time trying to understand what this all meant, our team was finally able to reproduce this firewall authentication bypass!
GET /api/v2/monitor/system/firmware?vdom=root HTTP/1.1 Host: 127.0.0.1 User-Agent: Node.js forwarded: by="[127.0.0.1]:1337";for="[127.0.0.1]:1337";proto=http;host=127.0.0.1 Connection: close
We can see that the request was made successfully without any kind of restriction!
In conclusion, we can see that Fortinet only relied on its custom header to accept or not a given request.
Relying only on User-Agent and Header forwarded.
10/10/2022 - That same day, Fortinet's CTI team got in touch to find out how our patch review process went and if we could help with possible ways to identify the vulnerability exploitation path.
10/10/2022 - Just on this day, after several external requests, Fortinet posted on its official incident response website a notice informing you of what the workaround was and how you can protect the use of "workaround".
Fortinet assigned CVSS 9.6 to this vulnerability, being the most critical vulnerability ever identified in the firewall this year.
11/10/2022 - Our team created a python script and nuclei template to automate the process of identifying this vulnerability, to help our customers, thus identifying in advance whether or not they were vulnerable to the new vulnerability.
13/10/2022 - On the 13th, our team decided to publish the code on GitHub, thus helping the security community to identify vulnerable devices and helping to keep companies safe.
Important: our team made available the scripts that didn't do any damage to the firewall, doing only "read-only" actions, thus helping to identify and not cause any kind of harm to customers.
13/10/2022 - On the same day, GreyNoise identified that the vulnerability is being exploited in the wild, so patch ASAP.
The exploit code and the nuclei template are available here: