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:
- Type 1:
UID
_HOSTNAME
_log.log - Type 2:
UID
_HOSTNAME
_Diagnostic_Result.cab - Type 4:
UID
_HOSTNAME
_log.gz - Type 5:
UID
_HOSTNAME
_Diagnostic_Result.gz - Type 8:
UID
_HOSTNAME
_log.zip - Type 10: ./snapshots/
UID
.json - 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 Extension | mod_mime Content-Type |
Filename.html | text/html |
Filename.gif | image/gif |
Filename.gif.html | text/html |
Filename.unknown | |
Filename.unknown.html | text/html |
Filename.html.unknown | text/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
Date | Action |
2024-11-20 | We report all issues to Fortinet |
2024-11-29 | Fortinet acknowledges the receipt of the report |
2024-12-18 | Fortinet confirms the issues are being worked on |
2025-01-28 | CVE-2025-22855 and CVE-2025-22859 are assigned |
2025-03-05 | CVE-2025-25251 is assigned |
2025-03-28 | CVE-2025-31366 and CVE-2025-31365 are assigned |
2025-04-08 | CVE-2025-22855 is published |
2025-04-08 | Fortinet shares the CVSS scoring with us |
2025-04-08 | We request further clarification about the scoring |
2025-04-10 | Fortinet shares further CVSS details with us |
2025-04-11 | We provide our feedback regarding the CVSS scoring |
2025-05-13 | CVE-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
- Caught in the FortiNet: How Attackers Can Exploit FortiClient to Compromise Organizations (1/3)
- Data in Danger: Detecting Cross-Site Scripting in Grafana
- Beware the Cookie Monster: Cyberhaven Extension Vulnerability Allowed Cookie Theft