
Detection toolkit for CVE-2026-35616, a pre-authentication API bypass in FortiClient EMS. Includes Python scanner and Nmap NSE script for identifying vulnerable versions and providing remediation guidance.
A critical authentication bypass in Fortinet FortiClient EMS 7.4.5 and 7.4.6 allows a completely unauthenticated, remote attacker to bypass API authentication by spoofing a single HTTP header (X-SSL-CLIENT-VERIFY). The flaw exists because the Django middleware trusts client certificate metadata from user-controlled headers, not just from the trusted reverse proxy. This gives attackers full administrative API access - and from there, arbitrary code execution on managed endpoints across the enterprise.
Actively exploited since March 31, 2026. Added to CISA KEV on April 6, 2026.
| Field | Detail |
|---|---|
| CVE ID | CVE-2026-35616 |
| Vendor | Fortinet |
| Product | FortiClient Enterprise Management Server (EMS) |
| Affected Versions | 7.4.5, 7.4.6 |
| Not Affected | 7.2.x branch, 7.4.4 and earlier |
| CVSS v3.1 | 9.1 (Critical) |
| CWE | CWE-284 - Improper Access Control |
| Attack Vector | Network |
| Authentication | None required |
| User Interaction | None |
| Exploit Maturity | Exploited in the wild |
| CISA KEV | Added April 6, 2026 (deadline: April 9, 2026) |
| Patch | Hotfix available; full fix in 7.4.7 |
| Credited To | Simo Kohonen (Defused Cyber), Nguyen Duc Anh |
FortiClient Enterprise Management Server (EMS) is Fortinet's centralized endpoint management platform. It serves as the command-and-control layer for deploying, configuring, and monitoring FortiClient agents across an organization. Think of it as the brain that governs every endpoint in a Fortinet-managed environment:
When an attacker gains administrative access to EMS, they essentially own the keys to every managed endpoint in the organization.
FortiClient EMS uses a fairly standard web application stack behind the scenes:
+----------------+ +----------------+ +----------------+
| Browser / | HTTPS | Apache | WSGI | Django |
| API Client | -------> | (mod_ssl) | -------> | Backend |
+----------------+ +----------------+ +----------------+
When mutual TLS (mTLS) is configured, Apache's mod_ssl handles the client certificate verification. After validating the certificate, Apache passes the verification result downstream to Django through trusted WSGI environment variables:
SSL_CLIENT_VERIFY - The verification status (SUCCESS, NONE, FAILED)SSL_CLIENT_S_DN - The Subject Distinguished Name from the certificateSSL_CLIENT_SERIAL - The certificate serial numberThis is the standard and secure pattern. The problem is in how the Django middleware reads this data.
In FortiClient EMS 7.4.5 and 7.4.6, the Django authentication middleware was modified to also accept this same information from HTTP request headers:
X-SSL-CLIENT-VERIFYX-SSL-CLIENT-S-DNX-SSL-CLIENT-SERIALThis was likely added to support reverse proxy deployments where Apache isn't the TLS termination point. However, the middleware doesn't distinguish between these two sources. It checks the WSGI variables first, but if they're absent (no mTLS configured, or a direct connection), it falls back to the HTTP headers - which any client can set.
Here's the conceptual breakdown:
SECURE PATH (intended):
Apache mod_ssl validates cert --> sets WSGI env vars --> Django reads env vars [OK]
INSECURE PATH (the vulnerability):
Attacker sets HTTP headers directly --> Django reads headers --> Trusts them [FAIL]
The middleware effectively trusts the client to self-attest their own certificate verification status. That's like a bouncer asking someone "Hey, did the other bouncer already check your ID?" and letting them in when they say "yes."
Step 1: Attacker sends a POST request to an EMS API endpoint
with these headers:
X-SSL-CLIENT-VERIFY: SUCCESS
X-SSL-CLIENT-S-DN: CN=admin
X-SSL-CLIENT-SERIAL: 0000000000000001
Step 2: Django middleware checks for WSGI env vars → not present
Falls back to HTTP headers → finds X-SSL-CLIENT-VERIFY: SUCCESS
Step 3: Middleware treats request as authenticated with admin identity
Step 4: Attacker has full administrative API access
Step 5: From the admin API, attacker can:
- Push malicious policies to all managed endpoints
- Extract stored credentials and certificates
- Deploy payloads via software distribution
- Modify ZTNA configurations
- Pivot into the broader network
The entire attack requires a single HTTP request. No brute-forcing, no credential stuffing, no social engineering. Just one forged header.
The severity here goes beyond the server itself. FortiClient EMS is a force multiplier - compromising it gives an attacker leverage over every managed endpoint:
Immediate Impact:
Downstream Impact (via managed endpoints):
Enterprise Risk:
| Version | Status |
|---|---|
| FortiClient EMS 7.4.6 | Vulnerable |
| FortiClient EMS 7.4.5 | Vulnerable |
| FortiClient EMS 7.4.4 and earlier | Not affected |
| FortiClient EMS 7.2.x | Not affected |