Vulnerability Analysis

CVE-2026-35616: Fortinet FortiClient EMS Pre-Auth API Bypass — What It Is & How to Fix It

Executive Summary

CVE-2026-35616 is a critical pre-authentication API access bypass vulnerability in Fortinet FortiClient Endpoint Management Server (EMS), scored CVSS 9.1. An unauthenticated attacker with network access to the EMS server can forge certificate-authentication headers to completely bypass API authentication, then execute arbitrary code or commands on the host. Active exploitation was first observed in the wild on March 31, 2026, CISA added the vulnerability to its Known Exploited Vulnerabilities (KEV) catalog on April 6, 2026, and federal agencies were required to patch by April 9, 2026. If your organization runs FortiClient EMS 7.4.5 or 7.4.6, treat this as a P0 remediation item.


1. What Is This Vulnerability?

FortiClient EMS uses a Django-based API backend exposed via Apache as a reverse proxy. To support mutual TLS (mTLS) client-certificate authentication, Apache is configured to pass certificate metadata to Django through HTTP request headers — specifically X-SSL-CLIENT-VERIFY and X-SSL-CLIENT-CERT. Django trusts these headers implicitly to determine whether a request comes from an authenticated, certificate-bearing client.

The vulnerability (CWE-284: Improper Access Control) exists because Apache was not stripping those headers when they arrived from external, untrusted sources. An attacker who can send HTTP requests directly to the EMS server can therefore inject these headers with forged values, convincing Django that the request carries a valid client certificate. The result: full authentication and authorization bypass without any credentials.

This is fundamentally a trust-boundary failure — the internal component (Django) trusted data that should only have been set by an internal component (Apache's own TLS termination), but the gate between the untrusted exterior and the trusted interior was never closed.

Attack Vector

The attack is straightforward and requires no valid credentials, no prior access, and no user interaction:

  1. Attacker sends a crafted HTTP/HTTPS request to the FortiClient EMS API endpoint.
  2. The request includes forged headers: X-SSL-CLIENT-VERIFY: SUCCESS and a fake client certificate in X-SSL-CLIENT-CERT.
  3. Apache, without stripping these headers, forwards the request to Django.
  4. Django interprets the headers as proof of a valid authenticated certificate and grants full API access.
  5. The attacker now has elevated privileges and can issue commands, modify endpoint configurations, or drop further payloads on the EMS server.
POST /api/v1/endpoint/action HTTP/1.1
Host: <TARGET_EMS>:8013
Content-Type: application/json
X-SSL-CLIENT-VERIFY: SUCCESS
X-SSL-CLIENT-CERT: <forged_or_empty_cert_pem>

{"action": "deploy_script", "target": "all", "payload": "..."}

Because this is a network-accessible, pre-authentication exploit, it is trivially scriptable and was found being actively scanned on the internet within days of the underlying technique becoming understood.

Real-World Impact

  • watchTowr's honeypots first recorded exploitation attempts on March 31, 2026, before a public patch existed.
  • Security researchers at Bishop Fox published a detailed technical breakdown confirming the header-stripping root cause.
  • CISA's KEV designation confirms confirmed exploitation by threat actors in the wild against real organizations.
  • Attackers observed inside affected environments were deploying additional tooling for lateral movement, indicating this was used as an initial access vector in broader campaigns.

2. Who Is Affected?

Affected Product Affected Versions
Fortinet FortiClient EMS 7.4.5
Fortinet FortiClient EMS 7.4.6

FortiClient EMS versions prior to 7.4.5 and 7.4.7 and later are not affected by this specific flaw.

You are at risk if:

  • Your FortiClient EMS server is on version 7.4.5 or 7.4.6.
  • The EMS management port (default: TCP 8013) is accessible from untrusted networks (internet-facing or broad internal segments).
  • You have not yet applied Fortinet's emergency hotfix.

Who typically runs FortiClient EMS? Enterprise and mid-market organizations using Fortinet's endpoint security suite to centrally manage FortiClient agents — common in financial services, healthcare, government, and critical infrastructure. This makes the affected population high-value targets.


3. How to Detect It (Testing)

Manual Testing Steps

⚠️ Only perform these steps against systems you own or have explicit written authorization to test.

Step 1 — Identify the EMS version: Log into the FortiClient EMS web console and navigate to System → Dashboard. The running version is displayed in the top-right corner. Alternatively, query the API:

curl -sk https://<EMS_HOST>:8013/api/v1/version

Step 2 — Check header-stripping behavior (passive): Review the Apache configuration file on the EMS host (typically at /etc/httpd/conf/httpd.conf or in a FortiClient-specific conf drop-in under /etc/httpd/conf.d/). Look for RequestHeader unset X-SSL-CLIENT-VERIFY and RequestHeader unset X-SSL-CLIENT-CERT. If these directives are absent, the system is unpatched and vulnerable.

Step 3 — Confirm exploitability (active probe): Send a crafted unauthenticated request with spoofed headers:

curl -sk -X GET \
  -H "X-SSL-CLIENT-VERIFY: SUCCESS" \
  -H "X-SSL-CLIENT-CERT: placeholder" \
  https://<EMS_HOST>:8013/api/v1/endpoint/list

A 200 OK response with endpoint data (rather than a 401/403) confirms the vulnerability is exploitable. A patched system will return 401/403 regardless of the injected headers.

Automated Scanning

Tool: Nuclei (ProjectDiscovery) A community template for CVE-2026-35616 is available. Run:

nuclei -u https://<EMS_HOST>:8013 -t cves/2026/CVE-2026-35616.yaml -v

Expected vulnerable output: [CVE-2026-35616] [http] [critical] https://<EMS_HOST>:8013 — Authentication Bypass Confirmed

Tool: Tenable Nessus / Rapid7 InsightVM Both platforms released plugin/check updates in April 2026. Run a credentialed or uncredentialed scan targeting your EMS host. Search for plugin ID referencing CVE-2026-35616 in your scan results.

Tool: Shodan / Censys (External Exposure Check)

shodan search "FortiClient EMS" port:8013 country:US

If your EMS management port appears in public scan results, you have unnecessary internet exposure that should be closed independent of patching.

Code Review Checklist

If you maintain a self-hosted or customized FortiClient EMS deployment, verify:

  • Apache config includes RequestHeader unset X-SSL-CLIENT-VERIFY before any ProxyPass directives
  • Apache config includes RequestHeader unset X-SSL-CLIENT-CERT before any ProxyPass directives
  • Django API views do not trust request headers for authentication without verifying they originated from the TLS layer
  • Access to the EMS API port (8013) is restricted by firewall rules to authorized management subnets only
  • mTLS client-certificate authentication is configured end-to-end, not via header pass-through

4. How to Fix It (Mitigation)

Step-by-Step Remediation

Immediate (apply within 24 hours given active exploitation):

  1. Identify all FortiClient EMS instances in your environment running 7.4.5 or 7.4.6. Check your asset inventory or run a network scan for hosts exposing port 8013.

  2. Apply the emergency hotfix released by Fortinet for FortiClient EMS 7.4.5 and 7.4.6. Download from the FortiGuard Support portal (requires valid support contract):

    • Navigate to: Support → Firmware & Downloads → FortiClient EMS → 7.4 branch → Hotfix packages
    • Apply hotfix package FG-IR-26-099-HOTFIX per the installation guide. No system downtime is required.
  3. Validate the hotfix applied successfully by confirming the Apache configuration now includes the RequestHeader unset directives (see Validation section below).

  4. Upgrade to FortiClient EMS 7.4.7 (full patch) as soon as it is available in your release channel. The hotfix is a bridge; the full version is the permanent fix.

  5. Review logs for signs of prior exploitation (see Monitoring section). If indicators of compromise are found, initiate your incident response process before patching — patching over an active compromise does not evict an attacker who has already established persistence.

Code Fix Example

The hotfix resolves the vulnerability by instructing Apache to actively strip the spoofable headers before proxying requests to Django. The effective change to the Apache configuration is:

Before (vulnerable):

<VirtualHost *:8013>
    SSLEngine on
    SSLCACertificateFile /path/to/ca.crt
    SSLVerifyClient optional
    SSLOptions +ExportCertData +StdEnvVars

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # Headers passed to Django — but NOT sanitized from external requests
    RequestHeader set X-SSL-CLIENT-VERIFY "%{SSL_CLIENT_VERIFY}s"
    RequestHeader set X-SSL-CLIENT-CERT "%{SSL_CLIENT_CERT}s"
</VirtualHost>

After (patched):

<VirtualHost *:8013>
    SSLEngine on
    SSLCACertificateFile /path/to/ca.crt
    SSLVerifyClient optional
    SSLOptions +ExportCertData +StdEnvVars

    # Strip any attacker-supplied headers BEFORE they reach Django
    RequestHeader unset X-SSL-CLIENT-VERIFY
    RequestHeader unset X-SSL-CLIENT-CERT

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # Now safely set headers from the actual TLS session variables
    RequestHeader set X-SSL-CLIENT-VERIFY "%{SSL_CLIENT_VERIFY}s"
    RequestHeader set X-SSL-CLIENT-CERT "%{SSL_CLIENT_CERT}s"
</VirtualHost>

The key ordering principle: always unset (strip) the header from incoming requests before the set directive re-populates it from the real TLS session environment variable.

Configuration Hardening

In addition to applying the patch, implement these network-level mitigations to reduce the attack surface regardless of vulnerability status:

  • Firewall the EMS management port (8013): Restrict inbound access to dedicated management subnets, VPN exit nodes, and known admin workstations only. FortiClient agents communicate with EMS but should do so through a properly scoped network path, not from the open internet.
  • Enable FortiClient EMS internal certificate pinning for agent-to-server communications to make header forgery attacks even harder to execute even if a future flaw is discovered.
  • Disable unused EMS API features: If your deployment does not use the remote script deployment or bulk enrollment API endpoints, disable them in EMS System Settings.

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A — Patch confirmation: After applying the hotfix, run the same curl probe from the detection section. The server must return HTTP 401 or 403 when the spoofed headers are supplied. A 200 response means the patch did not apply correctly.
  • Scenario B — Legitimate auth still works: Verify that legitimate FortiClient agents can still enroll and check in with EMS using their valid certificates. Generate a test enrollment from a known agent after patching and confirm it succeeds without errors.
  • Scenario C — No regressions in EMS console: Log into the EMS web UI as an admin user and confirm normal operations: endpoint list loads, policies push correctly, and reports generate without error.

Security Test Cases

Test Case 1: Authentication bypass no longer possible

  • Precondition: Hotfix has been applied to the FortiClient EMS host.
  • Steps:
    1. From an external host on the network, run: curl -sk -X GET -H "X-SSL-CLIENT-VERIFY: SUCCESS" -H "X-SSL-CLIENT-CERT: fake" https://<EMS_HOST>:8013/api/v1/endpoint/list
    2. Observe the HTTP response code and body.
  • Expected Result: HTTP 401 Unauthorized or HTTP 403 Forbidden. No endpoint data should be returned.
  • Fail Condition: HTTP 200 with endpoint data returned — patch did not apply.

Test Case 2: Apache header-stripping confirmed at the config level

  • Precondition: Access to the EMS server filesystem (admin or root).
  • Steps:
    1. Run: grep -r "RequestHeader unset X-SSL-CLIENT" /etc/httpd/
    2. Confirm both X-SSL-CLIENT-VERIFY and X-SSL-CLIENT-CERT appear in the output.
  • Expected Result: Both RequestHeader unset directives present in the Apache config, appearing before the corresponding RequestHeader set lines.
  • Fail Condition: Either directive is absent.

Automated Tests

Add this to your CI/CD pipeline or security regression test suite for any environment with FortiClient EMS:

import requests
import sys

EMS_HOST = "https://your-ems-host:8013"

def test_cve_2026_35616_auth_bypass():
    """
    Verify CVE-2026-35616 is not exploitable.
    Sends a request with spoofed cert-auth headers.
    MUST return 401 or 403; any 2xx response is a failure.
    """
    headers = {
        "X-SSL-CLIENT-VERIFY": "SUCCESS",
        "X-SSL-CLIENT-CERT": "FAKECERT",
    }
    resp = requests.get(
        f"{EMS_HOST}/api/v1/endpoint/list",
        headers=headers,
        verify=False,
        timeout=10
    )
    assert resp.status_code in (401, 403), (
        f"FAIL: CVE-2026-35616 auth bypass may be present. "
        f"Got HTTP {resp.status_code} with spoofed headers."
    )
    print(f"PASS: Auth bypass rejected with HTTP {resp.status_code}")

if __name__ == "__main__":
    test_cve_2026_35616_auth_bypass()

6. Prevention & Hardening

Best Practices

  • Defense in depth for EMS exposure: Management and orchestration servers like FortiClient EMS should never be directly internet-accessible. Place them behind a VPN gateway or on an isolated management VLAN, accessible only to administrators through jump hosts.
  • Header hygiene in reverse proxy architectures: Any time Apache, nginx, or another reverse proxy forwards requests to a backend that uses custom headers for authentication or authorization signals, those headers must be stripped from the incoming request before being set from internal, trusted sources. This is a well-established principle that should be in your baseline Apache/nginx hardening standards.
  • Apply vendor hotfixes for actively exploited CVEs within 24–48 hours: CISA's KEV designation is a reliable signal that a vulnerability is being actively weaponized. Establish an internal SLA that KEV-listed vulnerabilities receive emergency patch deployment within 24–48 hours, not the standard monthly patch cycle.
  • Subscribe to FortiGuard PSIRT notifications: Fortinet publishes advisories at fortiguard.fortinet.com/psirt. Subscribe via RSS or email to receive immediate notification when new vulnerabilities affecting your deployed product versions are disclosed.

Monitoring & Detection

To detect attempted or successful exploitation of CVE-2026-35616, monitor for the following:

Apache access logs — watch for suspicious API calls without valid sessions:

grep -E '"(GET|POST|PUT|DELETE) /api/v1/' /var/log/httpd/access_log \
  | grep -v "200\|204" \
  | awk '{print $1, $7, $9}' \
  | sort | uniq -c | sort -rn | head -50

Repeated API requests returning 2xx from IPs that have no legitimate endpoint enrollment history are a red flag.

FortiClient EMS audit logs: In the EMS console: Logs → Audit Logs → API Activity. Look for API calls with cert_auth=SUCCESS from source IPs outside your known management subnets. Any such entries before the hotfix was applied should be treated as potential indicators of compromise.

SIEM detection rule (pseudo-logic):

event_source = FortiClient_EMS_Apache_Access
AND http_method IN ["GET","POST","PUT","DELETE"]
AND uri_path STARTSWITH "/api/v1/"
AND http_status = 200
AND NOT src_ip IN [management_subnet_list]
AND NOT ssl_client_cert_cn IN [known_agent_cert_list]
→ ALERT: Possible CVE-2026-35616 exploitation attempt

Network-level detection: Deploy an IDS/IPS rule to flag HTTP requests to port 8013 containing both X-SSL-CLIENT-VERIFY and X-SSL-CLIENT-CERT headers originating from outside your management network. Snort/Suricata signature:

alert http any any -> $EMS_HOSTS 8013 \
  (msg:"CVE-2026-35616 FortiClient EMS Auth Bypass Attempt"; \
   flow:to_server,established; \
   http.header; content:"X-SSL-CLIENT-VERIFY"; \
   http.header; content:"X-SSL-CLIENT-CERT"; \
   classtype:attempted-admin; sid:2026356160; rev:1;)

References

Latest from the blog

See all →