Hunting for Fortinet CVE-2024-21762: Vulnerability Research for Detection Engineering

This article steps through the process of discovering CVE-2024-21762, a non-disclosed out-of-bounds write vulnerability in Fortinet FortiOS and FortiProxy.
fortinet
vulnerabilities
disclosure
cve-2024-21762
Author

h0wdy

Published

March 8, 2024

Introduction

This blog will cover some basic vulnerability discovery methods for developing detections. In early February, Fortinet published two reports warning users of CVE-2024-23113 and CVE-2024-21762. Within a day of their release, CISA added CVE-2024-21762 to its Known Exploited Vulnerability Catalogue. I wondered if we had seen it in our data but didn’t know what it would look like. It’s not every day I get to delve into vulnerability discovery, and this gave me an excellent opportunity to do so! These being unfamiliar waters, it would be nice to put together some documentation on things I learned along the way too.

The Vulnerability

As described in the report, CVE-2024-21762 is:

A out-of-bounds write vulnerability [CWE-787] in FortiOS and FortiProxy may allow a remote unauthenticated attacker to execute arbitrary code or command via specially crafted HTTP requests.
Workaround: disable SSL VPN (disable webmode is NOT a valid workaround)

Also, the report states that versions 7.4.0 to 7.4.2 are affected and should be updated to 7.4.3 or above. Given this information, I suspect that I’m looking for a new conditional statement doing some sort of size check to prevent the out-of-bounds write (oobw) and given the “Workaround,” I bet it has something to do with SSL VPN. This should be enough information to get started, so the question is: Where to start?

How to “Discovery”?

Getting the Software

Knowing both the affected and patched versions is going to mean a patch diff, but with Fortinet being proprietary software, finding these versions and decrypting them is going to be the first battle… which I was lucky enough to bypass via the power of friendship. However, when friendship is lacking one may also be able to get lucky and find the files they need on Grayhat Warfare (less luck needed with a subscription), and then get a nice start on decrypting them with this Bishop Fox writeup.

Diffing

Now that we have our decrypted file systems, we can start trying to find the changes between them by running:

$ diff -qr rootfs_7.4.2 rootfs_7.4.3

This reveals several differences, but the one that stands out to me is that Files rootfs_7.4.2/bin/init and rootfs_7.4.3/bin/init differ! These two versions of init can be patch diffed, and these differences will, hopefully, reveal where the vulnerability in the older version lies. BinDiff is a commonly used comparison tool for binary files, which have integrations for several decompilers.

I used the workflow for Ghidra, and once finished, I could see that only 3% had been altered.

Figure 1: BinDiff results between the init binary from FortiOS 7.4.2 and 7.4.3

Going through this diff to find changes that may be of interest was becoming a bit of a pain until it was brought to my awareness that the exported .BinDiff file is a sqlite3 database.

$ file init_7.4.2_vs_init_7.4.3.BinDiff
init_7.4.2_vs_init_7.4.3.BinDiff: SQLite 3.x database, last written using SQLite version 3040001

Knowing this, I just ran this query:

select * from function where similarity < 0.99 and confidence > 0.5 and basicblocks > 1;

which returned the following 25 results:

35497|11882560|sub_B55040|11882560|sub_B55040|0.989103161866654|0.990684040654933|2|3|0|0|8|10|46
36375|12006080|sub_B732C0|12007280|sub_B73770|0.6988424183407|0.947080659415396|51|15|0|0|25|22|102
36376|12007312|sub_B73790|12006096|sub_B732D0|0.651440577089454|0.921168719570564|19|14|0|0|23|22|97
36389|12012400|sub_B74B70|12012224|sub_B74AC0|0.986150683828588|0.992287288487088|43|4|0|0|118|192|617
59595|17316720|sub_1083B70|17316544|sub_1083AC0|0.583240276937669|0.987314705807547|3|4|0|0|109|164|588
59601|17324912|sub_1085B70|17324736|sub_1085AC0|0.983717611544164|0.988163468911019|3|4|0|0|145|242|780
59602|17328400|sub_1086910|17328224|sub_1086860|0.947545778506|0.990801622081806|35|4|0|0|77|110|498
59604|17330672|sub_10871F0|17330496|sub_1087140|0.860110145449666|0.986278840717481|19|4|0|0|159|261|923
65655|18868213|sub_11FE7F5|18867616|sub_11FE5A0|0.211233565872154|0.817574476193644|49|15|0|0|3|2|10
77682|20950192|sub_13FACB0|20950016|sub_13FAC00|0.921206422987814|0.989013057369407|3|4|0|0|7|8|100
77686|20953152|sub_13FB840|20952976|sub_13FB790|0.908689222856481|0.983752707554661|3|15|0|0|28|32|275
108145|26167680|sub_18F4980|26167504|sub_18F48D0|0.961323854179264|0.991178034552259|35|15|0|0|96|150|409
111760|26787104|sub_198BD20|26787040|sub_198BCE0|0.917600205660358|0.989151498170132|11|15|0|0|38|51|236
148827|33350752|sub_1FCE460|33350784|sub_1FCE480|0.896605870969271|0.971762527089316|18|4|0|0|12|13|50
148973|33360864|sub_1FD0BE0|33360896|sub_1FD0C00|0.96071134394723|0.991113411280912|3|4|0|0|13|18|126
149003|33365344|sub_1FD1D60|33365376|sub_1FD1D80|0.941131551276506|0.990684040654933|3|4|0|0|11|16|58
149170|33409616|sub_1FDCA50|33409648|sub_1FDCA70|0.967841456129521|0.99233004566813|3|4|0|0|50|77|220
150205|33617328|sub_200F5B0|33617360|sub_200F5D0|0.750430528014731|0.945686733867359|51|4|0|0|6|4|848
150206|33621472|sub_20105E0|33621504|sub_2010600|0.53921807745143|0.791391472673955|19|2|0|0|2|0|170
150208|33625520|sub_20115B0|33625552|sub_20115D0|0.784284397827447|0.930861579656653|35|4|0|0|29|17|1068
150211|33633072|sub_2013330|33633104|sub_2013350|0.70148887510363|0.970687769248644|19|4|0|0|3|1|297
150221|33637424|sub_2014430|33637456|sub_2014450|0.964744782906305|0.970687769248644|2|2|0|0|5|5|45
150223|33639792|sub_2014D70|33639824|sub_2014D90|0.749413415414218|0.984093608288185|3|4|0|0|7|7|348
161368|35530656|sub_21E27A0|35530688|sub_21E27C0|0.98443043296793|0.993240673509324|1|4|0|0|200|327|907
254289|57740728|sub_3710DB8|57740920|sub_3710E78|0.676914521717718|0.992927505746745|51|4|0|0|53|78|112

I was worried that there would be a lot more, given the size of this binary! Now the only thing to do was to comb through these and see if any of them are relevant to the known vulnerability (oobw in SSL VPN).

Analysis

So I did just that: I stepped through each of the 25 methods and took notes on what they appeared to do. Here’s a summarized version:

Figure 2: Brief notes on the 25 BinDiff results

I noticed that several of the changed functions involved
FGFM, and I suspect this has to do with CVE-2024-23113, which is not the topic of this blog but is worth noting regardless. The functions at 0x18f4980 and 0x198bd20 piqued my interest for many reasons. At first glance, I noticed the string, "Chunked request body is larger than limit of %d", which suggests to me that there may have been the potential for an overflow vulnerability in chunked requests in the past. It also stood out to me that 0x198bd20 calls 0x18f4980, and seems to handle web requests. Then, tracing the call tree back I can see that the original function call that leads to 0x18f4980 being executed is FUN_01a29a20, and one of the first lines of code in this function is:

__stream = fopen("/var/run/sslvpnd.pid","w");

Figure 3: Call Graph displaying the control flow from sslvpnd.pid caller function → suspected vulnerable function

Now that the scope is limited to the function at 0x18f4980 — which has been renamed to chunk_overflow_check in the diagram above — returning to BinDiff will help clarify where exactly the code has changed. Only two areas of change can be seen in the graph view:

Figure 4: Graph view of suspected vulnerable function with changes highlighted

After analysis, the change added to 7.4.3 at 0x18f4c01 stands out. It contains a new conditional statement, and there’s a string stating, “chunk trailer: too long.”

Figure 5: New conditional added in init version 7.4.3

This looks promising! My best guess at this point would be that the oobw exists in the handling of requests with chunked Trailer headers.

It makes one wonder if you could just throw a request with a chunked trailer header at an SSL VPN endpoint and get a crash…

Conclusion

I had some difficulty finding which endpoints to target exactly, but looking at FortiOS SSL VPN vulns from the past gave me the sense that the /remote endpoint is related. I searched for this endpoint as a string in Ghidra and came up with a list of possibilities. Having these paired with the Trailer spec, as well as getting the general size of what a chunk encoded trailer would be, should be enough to come up with a tentative detection or watcher to see if we can catch a PoC. Something like

alert http any any -> any any (msg:"Fortinet FortiOS Fortiproxy RCE CVE-2024-21762 Attempt"; flow:to_server,established; \
                                http.uri.raw; content: "/remote/"; fast_pattern; \
                                http.header_names; content: "transfer-encoding"; nocase; \
                                http.header.raw; content: "chunked"; nocase; isdataat:200;\
                                http.request_body; content: "0|0D 0A|"; \
                                http.request_body; content: "|3A|"; \
                                classtype:malicious; sid:123; rev:1;)

should work!

This is my first deep foray into tracking down a vulnerability in a binary of this size, so I’m pretty happy with this. You too can watch for this exploit in our data!

Steps Forward

While waiting for this detection to alert, I’ll be digging back into the RE work to track down specific vulnerable endpoints. With these in hand, the next step will be spinning up a target VM and throwing some chunk encoded Trailer packets at it.

References