SBN

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

Fortinet is one of the largest players in the cybersecurity industry, known for its extensive range of security solutions. Their portfolio includes firewalls, endpoint security, and intrusion detection systems, among others, designed to protect networks, applications, and data. These solutions are utilized across diverse sectors such as healthcare, finance, and government, helping organizations of various sizes defend against cyber threats.

To advance our understanding of cybersecurity threats and the security posture of leading software providers, we conducted in-depth research into the security of Fortinet's FortiClient and FortiClient Endpoint Management Server (EMS). This research resulted in the discovery of multiple security vulnerabilities within Fortinet's product suite. To share our findings with the community, we are publishing a 3-part blog series. This series will illustrate a realistic attack scenario targeting an organization utilizing Fortinet products, highlighting the potential impact of these vulnerabilities, particularly when chained together.

Key Information

  • Sonar's security researchers found severe vulnerabilities in Fortinet products that allow attackers to take over organizations with minimal user interaction.
  • The vulnerabilities affecting FortiClient, FortiClient Endpoint Management Server (EMS), FortiOS, and FortiProxy. 
  • The vulnerabilities covered in this series have been fixed and are detailed in the Impact section.
  • According to Fortinet, they have not observed any exploitation of these vulnerabilities in the wild.
  • We believe the CVSS scores assigned by Fortinet do not fully reflect the potential severity of our findings, and we urge customers to treat these vulnerabilities with the highest priority and update to the fixed versions immediately.

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 first part of our blog post series, we will focus solely on CVE-2025-22855, which allows an attacker to execute arbitrary code on a victim's machine running FortiClient when a user opens a malicious link.

Watch the video

Background

While Fortinet offers a wide variety of products, our research was focused on two specific ones:

FortiClient

Serves as an endpoint security solution that safeguards devices. It offers a multi-layered defense mechanism, including antivirus, vulnerability remediation, VPN, web filtering, and more. 

FortiClient’s UI is built on the Electron framework, offering a cross-platform user interface. In this interface, users can perform various actions, such as connecting to a VPN and viewing scan results. While this architectural choice provides convenience and cross-platform compatibility, it also introduces potential vulnerabilities inherent to the framework and its underlying components, such as Chromium and Node.js.

FortiClient Endpoint Management Server (EMS)

EMS can be hosted either by Fortinet (FortiClient Cloud) or on-premise. This server is responsible for managing and securing FortiClient endpoints. It's where organizations’ administrators can perform actions such as changing endpoint configurations, viewing the states of the endpoints, and receiving an organizational overview via dashboards.

Organization diagram

EMS and FortiClient are designed for integrated deployment. Typically, organizations have a single EMS instance responsible for managing multiple endpoints:

For an attacker seeking to infiltrate an organization, targeting FortiClient endpoints offers several distinct advantages as a first point of entry. The greater number of FortiClient installations, one per endpoint, significantly expands the attack surface compared to one EMS. This further expands considering the variety in endpoint versions, OS, and potential patching inconsistency. 

But most importantly, users often represent the weakest link in security. Since many attacks rely on some degree of user interaction, the abundance of users translates to a greater number of potential targets, increasing the attacker's chances of successfully establishing an initial foothold within the organization.

Technical Details

FortiClient and EMS communicate using a proprietary line-based protocol. We will discuss the details of the protocol in the next blog post, but for now, the crucial element to understand is the authentication process that occurs when a client connects to the EMS.

Authentication

There are 4 authentication methods an EMS can require: None, LDAP, Local, or SAML.  

  • None, does not require any authentication, and continues with the connection immediately.
  • In both Local and LDAP flow, Forticlient will prompt the user with a basic login window as such:

    This will authenticate the user with the provided credentials.

  • SAML, opens the browser and goes through the organization’s Identity Provider (IdP) authentication process. Upon successful authentication, the browser opens back FortiClient with an auth_token.

But how can a browser go back to the FortiClient application? 

Electron’s application protocol handler

Electron offers developers a convenient way to register a protocol handler for specific URL schemes. This will tell the OS to redirect any URL with this scheme to the application. FortiClient registers the fabricagent:// scheme, meaning when a user clicks on such a link, FortiClient’s Electron app will automatically launch and process that URL. This can be considered as an entry point for attackers as it requires just a simple click from a user to initiate a specific logic within an application.

Forticlient’s new attack surface (CVE-2025-22855)

When researching the protocol handlers FortiClient offers, an interesting one caught our eye: the EMS invite link, which facilitates a convenient connection to a specified EMS through a simple link. 

handlePossibleProtocolLauncherArgs(argv) {
   // ...
    if (arg.includes('fabricagent://ems?inviteCode')) { 
      this.handleEMSInviteCodeScheme(arg);
    } else if (arg.includes('fabricagent://vpn')) {
      this.handleVPNUriScheme(arg);
    } else if (arg.includes('fabricagent://ztna')) {
      this.handleZtnaAuthentication(arg);
    } else if (arg.includes('fabricagent://ems/onboarding')) {
      this.handleEMSOnboardingResponse(arg); 
    } else if (arg.includes('fabricagent://ems/msg')) {
      this.handleEMSOnewayMsgScheme(arg);
    } else {
      this.handleCreateMainWindow();
    }
  }

A significant security concern arose from the client's behavior: When a user clicks on an invite link, FortiClient will try to connect to the EMS defined in the link, without any limitation or further interaction from the user. Additionally, disregarding any existing EMS connection. Even if the current EMS enforces password-protected disconnection to prevent unauthorized removal (a security measure intended to protect the endpoint), FortiClient will still attempt to connect to the new EMS, essentially circumventing this safeguard. 

While connecting to an EMS via a link offers user convenience, it also presents an opportunity for attackers to lure users into connecting to malicious servers, opening up a whole new attack surface. 

But what can a malicious EMS do? 

Malicious EMS

After a FortiClient connects to an EMS, the server gains access to various management capabilities, including log requests, certificate revocation, and more. While those intended capabilities could be attractive for attackers, they are limited. To execute arbitrary code and fully compromise the client machine, an attacker would need to exploit another vulnerability in the client.

Looking for weaknesses in the application, we were particularly interested in the "send message" feature. This feature allows admins to show custom messages to users.

A message can be either plaintext or in HTML format.

Upon receiving a message, FortiClient creates a separate window from the main Electron one using the following webPreferences:

webPreferences: {
  webviewTag: true,
  contextIsolation: true,
  nodeIntegration: false,
  preload: path.join(__dirname, '../../', 'src', 'main', 'message-window', 'preload.js'),
},

These settings, particularly contextIsolation and disabled nodeIntegration, are intended to enhance security by isolating the window's context. For plaintext messages, the content is directly injected as text using the textContent property, preventing any HTML rendering. However, HTML messages are saved locally, in the file:///tmp/fct_endpoint_message.html file, and rendered within a webview

A webview is similar to an iframe but rendered using a different process, meaning that there are no handles to the main window, nor any exported objects from preload.js. Despite it being an isolated environment for the main window, Fortinet introduced some critical vulnerabilities by deviating from Electron's recommended security practices (covered by Sonar in rule S7076).

The file:// protocol scheme

Because the webview is being loaded under the file:// protocol scheme, and according to the Same-Origin Policy, the HTML page might be able to embed a different local file using the same scheme and read its content. Granting access to read and leak arbitrary files from the machine.


In addition to the webview being loaded under the file:// protocol scheme, all other windows were using the same scheme.

This means that there might be shared data between those windows! We found out that a parameter pointing to the previous EMS is stored in the localStorage (a data storage per origin):

An attacker can use this later in the attack to connect back to the legitimate organization's EMS. Considering a scenario where the legitimate EMS is using SAML authentication and the victim is already logged in to the IdP on a normal day of work, the reconnection to the original EMS wouldn’t even require any additional interaction. 

But beforehand, an attacker would need code execution on the victim machine. How can they do that?

Arbitrary code execution due to outdated Electron

More critically than using the file:// scheme, FortiClient was built using an outdated Electron version. By running navigator.userAgent in the dev tools console (FortiClient version 7.2.4.0850 on macOS), we noticed that the Electron version used was 11.1.1 and Chromium 87.0.4280.88. Which is before Electron had process sandboxing by default, and is susceptible to many known vulnerabilities, such as CVE-2021-21224. While we developed a PoC for this specific version on macOS, FortiClient on other operating systems used other, but still outdated Electron builds (such as Chromium version 120.0.6099.56 on Linux FortiClient 7.4.0.1636). 

Adapting CVE-2021-21224

From here to execute code, an attacker would need to adapt an n-day vulnerability to the specific operating system and Chrome version. This was not straightforward as the original proof-of-concept (PoC) exploit we used as a reference was written for Linux, but our target was running on macOS. Since macOS employs additional mitigations, an attacker has to get around them as well.

To start, we looked through the chrome_v8_ndays repo for a suitable exploit that fits our Chrome version, 87.0.4280.88. We found CVE-2021-21224 to be fitting, as it was marked as exploitable in versions <90.0.4430.85, and the PoC did indeed crash our target. This CVE is a type confusion vulnerability caused by a TurboFan speculative optimisation bug when it assumed that it was safe to convert signed 32-bit integers to unsigned 32-bit integers
(missing a speculation guard). For more details, @S1r1u5_ made a great root cause analysis.

We then set up a debugging environment to see where the exploit crashes instead of finishing properly. After adjusting some of the hardcoded offsets, we hit another roadblock. The original PoC used a WebAssembly object to allocate a page with RWX permissions, write shellcode to that page, and then execute it. However, when trying to write our shellcode, the process crashed. Even vmmap showed the page to have RWX permissions, so it should clearly be writable. What was happening?

It turns out that macOS comes with a security hardening against RWX pages. Such pages are dangerous because they allow attackers to write code to them and then cause it to be executed. To limit this, Apple introduced the write XOR execute (W^X) restriction, which adds additional permission bits to make a page either writable or executable, but never both at the same time. These bits are set per thread, so each thread can toggle its page access between writable and executable.

However JIT compilers frequently rely on memory being both writable and executable. To address that, Apple added a new API (per thread) to toggle pages from executable to writable. To access this API the com.apple.security.cs.allow-jit entitlement is needed to be set (which is the case in Electron as it is using a JIT compiler). 

Since the page to overwrite was in the executable mode, the attacker has to find a way to flip the permission to writable first, then write the shellcode, flip the permission back, and execute it. We noticed that the WebAssembly technique gives the attacker a function-call primitive because the pointer to the RWX page inside the WebAssembly object can be overwritten to point to an arbitrary function. When calling a WebAssembly function from JavaScript, the engine will call this pointer, which is under the attacker's control.

The attacker can use this to call the _pthread_jit_write_protect_np() function that regular programs use to toggle a page's mode. It receives a boolean argument that specifies if the page should be executable or not. We noticed that the first argument of a function call into WebAssembly is passed in the x0 register, which is also where _pthread_jit_write_protect_np expects its boolean argument. This means that the attacker can toggle the RWX page's mode at will.

With this new gadget, the attacker can now unprotect the page, write a shellcode to it, protect it again (making it executable), and finally execute it. For our purpose, we only wrote a small shellcode stub that allowed us to call arbitrary functions with more controlled arguments:

mov x4, x0
mov x0, x2
mov x1, x3
br x4

With this, an attacker can call the dlopen() and dlsym() functions, which were already resolved in Chrome's Global Offset Table (GOT), to look up the address of the system() function, which was not yet resolved. Finally, the attacker can use system() to execute arbitrary OS commands, such as open -a Calculator.

First Stage Overview

In this first stage of the attack covered in this blog post, we demonstrated how an attacker can force users to connect to a rogue EMS by a link, then by sending a message window that contains an HTML code with a v8 exploit, the attacker can fully compromise the machine. After the machine is compromised, the attacker can get a reference to the previously connected EMS and connect back to it, essentially acting now as a malicious client.

CVSS Discrepancy

CVE-2025-22855, the vulnerability covered in this blog post, was rated as CVSS AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N Low (2.6) by Fortinet based on the premise of a malicious administrator executing JavaScript. However, our research demonstrates a more significant risk: the capability for arbitrary code execution on the machine, and the fact that a user can be tricked into connecting to a malicious EMS with just one click.

This was not the only case where our impact assessment differed. We have shared these concerns with Fortinet, but it seems our feedback has not led to a revision of the CVSS scores. We urge customers to install a patched version immediately, even though the CVSS scores assigned by Fortinet might make it look less urgent.

Patch

While our research uncovered several FortiClient code execution vulnerabilities. We decided to focus on the simplest, 'one-click outdated Electron' method, due to its simplicity and minimal user interaction. All discovered methods ultimately lead to the same result.

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've broken down a critical FortClient vulnerability that gives an attacker the power to execute arbitrary code with just a single click from the user. We also briefly mentioned how a compromised client could reconnect to the legitimate EMS server by leveraging a parameter stored in the localStorage, essentially turning a trusted client into a malicious one. 

This sets the stage for our next article, where we'll dive deeper into this attack path and explore what a malicious client can achieve once connected to the EMS.

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

Related Blog Posts

*** This is a Security Bloggers Network syndicated blog from Blog RSS feed authored by Yaniv Nizry. Read the original post at: https://www.sonarsource.com/blog/caught-in-the-fortinet-how-attackers-can-exploit-forticlient-to-compromise-organizations-1-3/