Bypassing the FortiGate Symlink Patch: The Double Slash Technique (CVE-2025-68686)

By Peter Gabaldon (X / LinkedIn)

TL;DR

In the previous analysis (https://pgj11.com/posts/FortiGate-Symlink-Attack/), we detailed the persistence method where Threat Actors (TA) used a symbolic link in the FortiGate SSL-VPN /lang/custom directory to access the root filesystem. Fortinet released a patch attempting to block this type of access.

However, we discovered that the patch relied on a weak string matching check (strstr) looking specifically for /lang/custom. By simply adding an extra slash to the path—requesting /lang//custom—the security check is skipped entirely while the web server still resolves the path correctly. This effectively bypasses the patch, restoring unauthorized read-only access to the root filesystem.

Background

In our previous post (https://pgj11.com/posts/FortiGate-Symlink-Attack/), we explored how attackers established persistence on compromised FortiGate units by creating a symlink in the SSL-VPN language directory. This allowed them to download sensitive files, such as the configuration, without authentication.

Fortinet responded by releasing a patch intended to prevent this behavior. We decided to analyze this patch to understand how they mitigated the issue so we could detect FortiGate devices that were compromised.

Analyzing the Patch

Upon reversing the patched firmware, we observed a new check introduced in the request handling logic. The patch utilizes the strstr function to inspect the requested URL path.

As documented in the Linux man pages, strstr is used to find the first occurrence of a substring within a string. The logic implemented by Fortinet was roughly:

  1. Check: Does the requested path contain the substring /lang/custom?
  2. Action: If yes, perform a validation check on the symlink to ensure it points to a safe location.
  3. Else: If the substring is not found, proceed normally (skipping the validation).

This logic relies on the assumption that to access the vulnerable directory, the request must strictly contain the string /lang/custom.

The Bypass: Double the Slash, Double the Fun

The weakness in using strstr for security boundaries on file paths is that file systems and web servers often normalize paths in ways that string matching does not account for.

In Linux (and most web servers), /lang/custom and /lang//custom (note the double slash) resolve to the exact same directory. However, to strstr, they are completely different strings.

  • Request:/lang/custom/file
    • strstr finds /lang/custom.
    • Result: Security check triggers. Blocked.
  • Request:/lang//custom/file
    • strstr searches for /lang/custom.
    • Result: Substring NOT found. Security check skipped.
    • Server Behavior: Resolves /lang//custom to the valid directory and serves the file.

Proof of Concept

We verified this theory on a patched appliance.

First, we attempted to access a JSON language file (which we had linked to the filesystem) using the standard path. As expected, the patch intercepted the request, resulting in a 403 Forbidden error. This confirms the security check was active and identified the /lang/custom in the request path.

Next, we sent the exact same request but modified the path to use a double slash: /lang//custom.

The result? A 200 OK status. The file was served successfully, completely bypassing the patch.

The Tool

ChatGPT(♥) and us, wrote a Python script to be able to automatically check if a FortiGate unit is running a version that fix the problem or not and to check if it is compromised.

The tool can be found here:

The tool can be run vs a single FortiGate specifying an IP and port, to multiple FortiGates specifying a file that contain IP:Port for each line or using a Shodan’s JSON file.

The tool checks for:

  1. Fixed Status: Verification if the patch (and potentially the bypass mitigation) is applied.
  2. Compromise Indicators: Checks if the malicious symlinks are accessible, indicating an active persistence mechanism.

It is highly recommended to run this against your edge devices to ensure the patch is effective and no remnants of the attack remain.

Internet Analysis

This study was performed just with statistics purposes. Any device was targeted or compromised, only checked if it was already compromised and the symlink persistence method could be leveraged.

We used Shodan to download about ~3500 FortiGate units exposed to internet and run the script vs all of that in order to know the magnitude of this persistence method.

The results showed that from 3503 randomly selected FortiGate units, 144 were still compromised (about a month later than the security advisory released by Fortinet). This represents a 4.11%.

Note that we examined 3,503 FortiGate units in total, not 3,503 SSL-VPN portals. Several of these firewalls publish only other services and do not have SSL-VPN enabled.

Of those 3,503 devices, 787 are already running the latest firmware that adds the symbolic-link integrity check.

Conclusion

The discovery of this bypass highlights a classic pitfall in software security: the discrepancy between how a security check views data and how the underlying system processes it. By relying on a simple string search (strstr) without canonicalizing the path first, the initial patch left a trivial gap that path normalization easily exploited.

This case serves as a reminder that patching is not always the end of the story. Security is an iterative process, and “shallow” patches often lead to a game of cat-and-mouse between defenders and attackers. For developers, it reinforces the importance of validating inputs in their canonical form. For administrators, it underscores that security cannot rely solely on updates; instead, they must continue monitoring the infrastructure, applying defense-in-depth strategies, and relying on robust detection, response, and mitigation efforts.

We strongly recommend all FortiGate administrators:

  1. Upgrade immediately to the latest firmware version where the path normalization logic has been hardened.
  2. Run the provided tool to verify your appliance is truly patched and to ensure no malicious symlinks were left behind by threat actors during the window of exposure.
  3. Monitor logs for unusual requests containing double slashes or unexpected access to the language directories.

See you in next posts!

Disclosure Timeline

We followed a responsible disclosure process with Fortinet PSIRT to ensure this bypass was addressed before public release.

  • 2025-04-26: Bypass discovered during patch analysis.
  • 2025-10-09: Vulnerability and Proof of Concept reported to Fortinet PSIRT.
  • 2025-10-10: Fortinet acknowledged the issue.
  • 2026-02-10: Fortinet released an updated patch to address the path normalization issue.
  • 2026-02-11: Public disclosure and blog post release.