Back to Blog
2026-04-07

The 9.1 Severity API Bug That's Still Hitting Production: CVE-2026-35616 and the Authentication Myth

Fortinet's CVE-2026-35616 scores 9.1 CVSS. Active exploitation confirmed. Here's why API authentication bypasses keep shipping—and the three patterns that cause them.

The 9.1 Severity API Bug That's Still Hitting Production: CVE-2026-35616 and the Authentication Myth

Critical CVE: CVSS 9.1 — Actively Exploited in the Wild

On April 4, 2026, Fortinet confirmed what API security researchers have screamed for years: authentication is still the easy button for attackers. CVE-2026-35616, a critical improper access control vulnerability in FortiClient EMS, allows unauthenticated attackers to execute arbitrary code via crafted API requests. CVSS 9.1. Active exploitation observed. Hotfix released for versions 7.4.5 and 7.4.6.

The vulnerability exists because someone treated the API layer as a trusted internal surface—assuming that if you're calling the API, you've already proven your identity. That's not authentication. That's wishful thinking with a CVE number.

This isn't an isolated incident. Auth0's 2025 audit found that over 30% of JWT implementations contained at least one critical vulnerability: accepting unsigned tokens, using brute-forceable secrets, or failing to validate expiration. The attackers know this. They're not hunting for zero-days in crypto primitives. They're exploiting the gap between "we have authentication" and "our authentication actually works."

The FortiClient EMS Timeline

April 4, 2026: FortiGuard PSIRT confirms active exploitation of CVE-2026-35616 in FortiClient EMS 7.4.5 and 7.4.6. CVSS score: 9.1. Impact: privilege escalation via unauthenticated API requests. Hotfixes deployed for both versions. Upcoming 7.4.7 will include the fix. The vulnerability was in the API layer's access control— attackers could bypass authentication checks entirely by sending specially crafted requests to the endpoint.

The Three Authentication Failure Patterns

Pattern 1: Trusting the API Gateway's Auth Without Validating Downstream

The most common mistake. Teams implement authentication at the API gateway, then disable or skip auth checks in downstream services because "the gateway already handled it." This creates a flat trust model where compromising one component compromises everything.

# WRONG: Assuming gateway auth means you don't need it downstream
@app.route('/api/admin/users', methods=['DELETE'])
def delete_all_users():
    # Gateway validated the token, we're trusted now
    # No role check. No ownership check. Just delete.
    db.execute("DELETE FROM users WHERE 1=1")
    return {"status": "success"}

The gateway checked credentials. Nobody checked whether the authenticated principal should have admin access to that endpoint. BOLA combined with missing authorization—still one of the most profitable attack chains in 2026.

Pattern 2: JWT Validation That's Half-Assed

// WRONG: Decoding ≠ Validating
func validateToken(tokenString string) bool {
    // This only DECODES. It doesn't VERIFY.
    token, _ := jwt.Parse(tokenString)
    if token == nil {
        return false
    }
    claims := token.Claims.(jwt.MapClaims)
    // Checking expiry manually? Most implementations miss this.
    if float64(time.Now().Unix()) > claims["exp"].(float64) {
        return false // Good, you caught expiry. But is the signature verified?
    }
    return true
}

The correct implementation requires signature verification with the server's public key, algorithm validation (reject none), and proper error handling that doesn't leak information.

// RIGHT: Full validation with proper error handling
func validateToken(tokenString string) (*jwt.Token, error) {
    return jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
            return []byte(os.Getenv("JWT_SECRET")), nil
        }
        // Reject algorithm confusion attacks
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    })
}

Pattern 3: OAuth State Parameter Treated as Optional

The OAuth authorization code flow requires a state parameter to prevent CSRF attacks. Many implementations treat it as optional—either not generating it, not validating it, or validating it incorrectly.

# WRONG: Skipping state validation
@app.route('/auth/callback')
def oauth_callback():
    code = request.args.get('code')
    # state parameter? what state parameter?
    tokens = exchange_code_for_tokens(code)
    session['access_token'] = tokens['access_token']
    return redirect('/dashboard')

# RIGHT: Validate state to prevent CSRF
@app.route('/auth/callback')
def oauth_callback():
    code = request.args.get('code')
    returned_state = request.args.get('state')
    stored_state = session.pop('oauth_state', None)

    if not returned_state or returned_state != stored_state:
        abort(400, "Invalid OAuth state parameter")

    tokens = exchange_code_for_tokens(code)
    session['access_token'] = tokens['access_token']
    return redirect('/dashboard')

What Actually Works

Defense-in-depth for API authentication isn't complicated. It's just consistently missing in production.

Defense-in-Depth at Every Layer

Gateway authenticates. Each microservice validates the token independently. Authorization checks happen at every boundary. Compromise one layer doesn't collapse the others.

JWT Validation: Full, Not Partial

Verify signature, validate algorithm, check expiration, confirm issuer and audience. Parse errors should return 401, not 500. Never decode without verification.

Refresh Token Rotation

Issue a new refresh token on every use. If a token is reused, revoke the entire token family immediately. Detect token theft through reuse detection, not just expiry.

PKCE for All Public Clients

Code exchange without PKCE is vulnerable to authorization code interception. Require proof of key exchange for every OAuth client, including "trusted" internal ones.

The Hard Truth

The attackers aren't breaking crypto. They're not finding weaknesses in RSA or AES. They're exploiting implementation decisions that treat authentication as a checkbox rather than a control.

CVE-2026-35616 will be patched. The hotfix will ship. But in six months, another API authentication bypass will ship in a different product, and the post-mortem will say the same thing: "authentication was assumed to be handled elsewhere."

Assume nothing. Validate everything.

Sanitize .env Files Before Sharing

Need to share environment variables? Use Env Sanitizer to automatically detect and mask secrets. All processing happens client-side—your data never leaves your browser.

Open Env Sanitizer →
Share this: