
Non-destructive scanner for CVE-2026-35616, a pre-authentication API bypass in FortiClient EMS. Detects vulnerability by comparing HTTP responses with and without spoofed SSL headers, using only Python standard library.
Safely detect whether a FortiClient EMS server is vulnerable to CVE-2026-35616 without causing any disruption. See the full write-up on the Bishop Fox blog.
CVE-2026-35616 is a pre-authentication API bypass in FortiClient EMS 7.4.5 and 7.4.6 that allows remote, unauthenticated attackers to bypass certificate-based authentication through HTTP header spoofing. The Django application trusts user-controllable HTTP headers (X-SSL-CLIENT-VERIFY, X-SSL-CLIENT-CERT) as equivalent to Apache mod_ssl WSGI environment variables, and the Apache configuration never strips them. Combined with certificate chain validation that performs only Distinguished Name string matching (no cryptographic signature verification), an attacker can forge certificates and gain authenticated API access. Fortinet has confirmed exploitation in the wild.
This tool performs non-destructive vulnerability testing by:
/api/v1/fabric_device_auth/fortigate/init with no spoofed headersX-SSL-CLIENT-VERIFY: SUCCESS but no certificate dataSending the verify header with no certificate PEM data triggers the detection path but cannot authenticate because no certificate chain is provided. The server remains stable and can continue handling connections.
contains_certificate() passes the gate but validate_cert_chain() crashes on missing PEM data.RequestHeader unset directives that strip the spoofed header before it reaches Django.INCONCLUSIVE result.git clone https://github.com/BishopFox/CVE-2026-35616-check
cd CVE-2026-35616-check
No external dependencies. The scanner uses only Python standard library modules.
Test a FortiClient EMS server at <TARGET>:<PORT>. The default port is 443/TCP.
python3 CVE-2026-35616-check.py <TARGET> [PORT]
$ python3 CVE-2026-35616-check.py 192.168.1.1
======================================================================
FortiClient EMS CVE-2026-35616 Vulnerability Scanner
Non-Destructive Detection
======================================================================
[*] Target: 192.168.1.1:443
[*] Testing for CVE-2026-35616 (non-destructive)
[1] Sending baseline POST (no spoof headers)... HTTP 401
[2] Sending POST with X-SSL-CLIENT-VERIFY: SUCCESS... HTTP 500
[*] Analyzing responses...
[+] Baseline: 401 — Certificate not found in request header.
[+] Spoofed: 500 — Server encountered an error, please try again later.
[!] Spoofed header changed server behavior
[!] X-SSL-CLIENT-VERIFY is reaching Django (hotfix not applied)
======================================================================
RESULT: VULNERABLE to CVE-2026-35616
Affected versions: FortiClient EMS 7.4.5 - 7.4.6
Recommendation: Apply Fortinet hotfix or upgrade to 7.4.7+
======================================================================
$ python3 CVE-2026-35616-check.py 192.168.1.1
======================================================================
FortiClient EMS CVE-2026-35616 Vulnerability Scanner
Non-Destructive Detection
======================================================================
[*] Target: 192.168.1.1:443
[*] Testing for CVE-2026-35616 (non-destructive)
[1] Sending baseline POST (no spoof headers)... HTTP 401
[2] Sending POST with X-SSL-CLIENT-VERIFY: SUCCESS... HTTP 401
[*] Analyzing responses...
[+] Baseline: 401 — Certificate not found in request header.
[+] Spoofed: 401 — Certificate not found in request header.
[-] Responses identical (header stripped by Apache)
======================================================================
RESULT: NOT VULNERABLE (hotfix applied)
Apache is stripping X-SSL-CLIENT-VERIFY before it reaches Django
======================================================================
The tool can only detect the vulnerability if the following conditions are met:
/api/v1/fabric_device_auth/fortigate/init endpoint exists and uses cert_chain_approved authenticationIf any condition isn't met, the tool will report an INCONCLUSIVE result.
This code is distributed under an MIT license.
Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program.