Blog post

Caught in the FortiNet: How Attackers Can Exploit FortiClient to Compromise Organizations (2/3)

Yaniv Nizry photo

Yaniv Nizry

Vulnerability Researcher

Date

  • Code Security

Fortinet, a global leader in cybersecurity solutions, provides a wide array of products designed to safeguard organizations from increasingly sophisticated threats. However, the very nature of these critical security tools makes them prime targets for malicious actors. What happens when the tool designed to protect an organization becomes a vulnerability?

Continuing our exploration of the severe vulnerabilities we uncovered in Fortinet's FortiClient and EMS, we move beyond the initial compromise. In our previous post, we showed how an attacker gains an initial foothold by manipulating an endpoint victim to click on a link. Now, we follow the attacker's path, outlining the steps of lateral movement and an EMS vulnerability that can lead to full organizational compromise.

Impact

Though each vulnerability's impact differs, when chained together, they form a severe threat capable of granting an attacker complete organizational control with minimal user interaction.
The vulnerabilities are tracked as:

  • CVE-2025-25251: fixed in FortiClientMac 7.4.3 and 7.2.9. Fix is also being backported to 7.0.
  • CVE-2025-31365: fixed in FortiClientMac 7.4.4 and 7.2.9
  • CVE-2025-22855: fixed in FortiClient EMS 7.4.3
  • CVE-2025-22859: fixed in FortiClient EMS 7.4.3; only EMS 7.4 (Linux-based) is affected by this issue. 
  • CVE-2025-31366: fixed in FortiOS and FortiProxy versions 7.6.3 and 7.4.8

In this part of the blog series, we will focus on CVE-2025-22859, which enables an authenticated attacker to upload a stored XSS payload to a Linux-based EMS server. Exploiting this vulnerability, an attacker can manipulate an EMS user into clicking a malicious link, forcing all registered endpoints to switch connection to a malicious EMS server without any interaction from the clients. This makes them susceptible to arbitrary code execution, as showcased in the previous blog. 

Technical Details

In the previous blog, we showcased how an attacker can execute arbitrary code on a machine running FortiClient by manipulating a victim to click on a link. When doing so, FortiClient connects to a malicious EMS server, which then sends a malicious HTML message that is rendered in an outdated isolated Electron window.

As mentioned in the previous blog, in addition to the outdated Chromium, the attacker’s controlled content window is rendered under the file:// protocol. Since the main Electron window of FortiClient also uses this scheme, certain things are shared. We noticed that in the localStorage, FortiClient saves information regarding the last connected EMS (invitation code for Fortinet Cloud or an IP/domain of an on-premise EMS). Considering a scenario where an attacker compromises an endpoint within an organization, the “previous EMS” will most likely point to the organization's legitimate EMS. Using this information, the attacker can now reconnect to the legitimate organizational EMS, becoming a malicious authenticated client. 

As an attacker advances, new attack surfaces are unveiled. To understand the potential risk of a malicious client to an EMS, we first need to understand the basics of how FortiClient and EMS are communicating:

Communication

FortiClient and the EMS communicate using a custom, line-based protocol. The client's request consists of key-value headers separated by the equal character (=). A body starts with the request type, followed by key-value pair data separated by the pipe ("|") character. To finalize the request, the end type sequence is present.

MSG_HEADER: FCTUID=C511A8F3ACBE5FA4ADD13F12E77647F9
FCTVER=7.2.4.0850
PROTO_VER=1.0.0
KEY=VALUE
KEY2=VALUE2
...

X-FCCK-PROBE: PROBE_FEATURE_BITMAP|1|KEY|VALUE|KEY2|VALUE2|....
X-FCCK-PROBE-END\r\n

The response message consists only of the type and body data: 

FCPROBERPLY: FGT|FCTEMS000000000:i-0fe6110e2e9410000|FEATURE_BITMAP|7|EMSVER|7004000|\r\n

The communication sequence, initially starts with a probe request, meant to verify that the server is an EMS and running a compatible version. Followed by the registration flow, which we covered in the previous blog.

Upon successful authentication, the connection is maintained via the client’s keep-alive messages every X seconds, which is defined by the server. These keep-alive messages are meant to ensure that the client is still connected to the EMS but also update information on the client, for example, if the IP is changed. The EMS utilizes the response message to perform actions from the client, such as showing a message window or requesting logs.

Lastly, FortiClient can upload data to the EMS using a data request (DATA_HEADER). This can be followed by an upload request from the EMS (such as diagnostic results), but can also be initiated purely by the client, for example, when the user updates their profile image.

The DATA message is built similarly to that of other client requests, but also consists of a TYPE header that is an enum referencing which kind of data is being sent.

We noticed that certain DATA uploads are saved into files under the /opt/forticlientems/data/fctuploads/ directory (Linux-based EMS) with the following format:

  1. Type 1: UID_HOSTNAME_log.log
  2. Type 2: UID_HOSTNAME_Diagnostic_Result.cab
  3. Type 4: UID_HOSTNAME_log.gz
  4. Type 5: ​​UID_HOSTNAME_Diagnostic_Result.gz
  5. Type 8: UID_HOSTNAME_log.zip
  6. Type 10: ./snapshots/UID.json
  7. Unknown type: UID.PROVIDED_TYPE.HASH.upload

Interestingly, the UID and HOSTNAME values are controlled by the client during the registration, and the HASH/PROVIDED_TYPE values are defined in the DATA upload request. This makes each parameter used to construct the filename attacker-controlled. When creating the file, the EMS doesn’t normalize the user input, allowing path traversal sequences and therefore leading to a limited arbitrary file write vulnerability. However, exploiting this primitive isn’t straightforward, specifically because an attacker cannot control the extensions or suffixes of the filename. Essentially, it blocks attackers from elevating this primitive to execute arbitrary code on the server. To further evaluate what impact this vulnerability can have, we tried to identify other ways an attacker could use it.

Looking at the EMS features, there was one that seemed very interesting for an attacker: 

“Switch EMS” tells an endpoint to connect to a different EMS by IP. Meaning that if an attacker can leverage this limited file write to execute arbitrary JavaScript as an EMS administrator (XSS) then they can switch every endpoint in the organization to connect to a malicious EMS and subsequently exploit the vulnerability covered in the first blog post, which will potentially grant full code execution on every machine within the organization!

From Limited File Write to XSS (CVE-2025-22859)

The Linux version of EMS is running the web server using Apache httpd , which uses the mod_mime component to guess the content type of the file served by its extension and set the Content-Type header accordingly. We have already covered a cool technique in the past that enables XSS when an attacker cannot control the extension, by using only dots or nothing as a filename. This happened because in those cases, mod_mime doesn’t add a Content-Type header, making the browser sniff the type of the file according to the content, not the file extension.

However, this trick doesn’t work in the case of Fortinet EMS because the Apache httpd server is configured to serve the header “x-content-type-options: nosniff”, which tells the browser not to sniff the content type, and it will default to text/plain. But looking into the documentation of mod_mime, we stumble upon an interesting case:

A file can have multiple extensions, with a priority given to the last one. For example, these file extensions will correspond to the following content-types:

File Extensionmod_mime Content-Type
Filename.htmltext/html
Filename.gifimage/gif
Filename.gif.htmltext/html
Filename.unknown
Filename.unknown.htmltext/html
Filename.html.unknowntext/html

Using this knowledge, an attacker can choose a file type to upload that has an unknown extension (.cab or .upload in our case), traverse the upload destination to the static folder of the website, and simply add .html to the file name. The file will then be served as text/html, resulting in stored XSS. 

Second Stage Overview

After the first stage, shown last week, the attacker has compromised a FortiClient endpoint and connected back to the an organization's legitimate EMS. In the second part of the attack, a compromised client can upload a stored XSS payload to the EMS. When viewed by an administrator, arbitrary JavaScript is executed, forcing every FortiClient endpoint connected to this EMS to change the management server to an attacker-controlled one. From here, the attacker can exploit the vulnerability covered in the first part of the series again. This leads to the the worst case scenario of a fully compromised organization.

Patch

The vulnerabilities we discovered are fixed in the following versions:

  • CVE-2025-25251: fixed in FortiClientMac 7.4.3 and 7.2.9. Fix is also being backported to 7.0.
  • CVE-2025-31365: fixed in FortiClientMac 7.4.4 and 7.2.9
  • CVE-2025-22855: fixed in FortiClient EMS 7.4.3
  • CVE-2025-22859: fixed in FortiClient EMS 7.4.3; only EMS 7.4 (Linux-based) is affected by this issue. 
  • CVE-2025-31366: fixed in FortiOS and FortiProxy versions 7.6.3 and 7.4.8

We urge customers to update their affected Fortinet products to the fixed versions.

Timeline

DateAction
2024-11-20We report all issues to Fortinet
2024-11-29Fortinet acknowledges the receipt of the report
2024-12-18Fortinet confirms the issues are being worked on
2025-01-28CVE-2025-22855 and CVE-2025-22859 are assigned
2025-03-05CVE-2025-25251 is assigned
2025-03-28CVE-2025-31366 and CVE-2025-31365 are assigned
2025-04-08CVE-2025-22855 is published
2025-04-08Fortinet shares the CVSS scoring with us
2025-04-08We request further clarification about the scoring
2025-04-10Fortinet shares further CVSS details with us
2025-04-11We provide our feedback regarding the CVSS scoring
2025-05-13CVE-2025-22859 and CVE-2025-25251 are published

Summary

In this post, we have taken a deeper look into the inner workings of FortiClient and EMS, how they communicate, and what a malicious client could exploit. Using the vulnerability covered in this article, attackers who are authenticated to an EMS can traverse back the upload directory and create arbitrary files on the server with a limited name. We covered a technique attackers can use to overcome this limitation and achieve stored XSS in Apache httpd. 

The impact of this vulnerability, when exploited, is the ability to force all the endpoints managed by the EMS to connect to a malicious EMS. This, combined with other vulnerabilities we uncovered, could potentially lead to remote code execution on every endpoint machine within an organization.

In the next blog post, we will go back to focusing on FortiClient and understand more details about its inner workings and what an attacker can exploit further.

We would like to thank the Fortinet PSIRT for their collaboration and responsiveness in addressing these findings.

Related Blog Posts


Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

I do not wish to receive promotional emails about upcoming SonarQube updates, new releases, news and events.

By submitting this form, you agree to the storing and processing of your personal data as described in the Privacy Policy and Cookie Policy. You can withdraw your consent by unsubscribing at any time.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.