Target: Fortinet FortiWeb
Component: fwbcgi (Administrative CGI Binary)
Vector: HTTP POST (Remote)
FortiWeb is positioned as the shield for critical assets—a Web Application Firewall (WAF) designed to filter malicious traffic. However, internally, it relies on a legacy architecture: Apache coupled with CGI binaries, a model dating back decades.
The security model relied on two fatal assumptions:
CGIINFO) originate only from internal IPC components.These assumptions crumbled when exposed to the public internet, leading to a CVSS 9.8 collapse.
The Context: This is a dual-failure chain. CVE-2025-64446 allows an attacker to bypass authentication entirely by forging internal headers. CVE-2025-58034 then leverages that fake identity to inject shell commands into the policy management engine.
Root Cause: Full Authentication Collapse via Trust Boundary Violation.
The vulnerability stems from Path Resolution issues in Apache and Implicit Trust in the fwbcgi binary.
Legitimate API endpoints live under /api/v2.0/cmdb/. Apache processes the URL path before enforcing logical boundaries. By using path traversal (../), an attacker can escape the API logic and invoke fwbcgi directly.
Once inside, fwbcgi calls cgi_auth(). Instead of checking a database or a cryptographic session token, it looks for a header named CGIINFO.
CGIINFO exists, the system Base64-decodes it, parses the JSON, and “hydrates” the user session based on the data inside.Once authenticated as a fake Admin, the attacker targets the policy_scripting_post_handler. This handler is responsible for creating automation scripts. It constructs a shell command to invoke an underlying OS utility:
/usr/local/bin/run_script --name <user_input>
The handler builds this command using string concatenation rather than argument vectors. It inserts user-controlled JSON fields (like name or script) verbatim.
The attacker bypasses the application layer routing by sending a request that traverses out of the API directory:
POST /api/v2.0/cmdb/system/admin/../../../../../cgi-bin/fwbcgi
The attacker injects the malicious CGIINFO header to forge a Super-Admin identity:
{
"username": "admin",
"profname": "super_admin",
"vdom": "root",
"loginname": "admin"
}
fwbcgi trusts this input blindly and grants full privileges.
With Admin access, the attacker sends a payload to the scripting handler:
name = "test; /bin/sh -c 'id'"
The server executes:
/usr/local/bin/run_script --name test (Legitimate)/bin/sh -c 'id' (Malicious)Impact: The attacker gains Remote Code Execution (RCE) as Root (UID 0). Because the appliance has no privilege separation, no chroot, and no seccomp filters, the compromise is total.
Forensic analysis indicates this vector was used as a beachhead to attack downstream infrastructure, including the Cisco AsyncOS appliances detailed in AOAB #6.
The remediation requires enforcing strict boundaries at the web server level and removing shell invocation from the code.
Vulnerable Logic (C++):
// FATAL: String concatenation allows shell metacharacters (; | &)
std::string cmd = "/usr/local/bin/run_script --name " + user_input;
system(cmd.c_str());
Patched Logic (C++):
// SECURITY FIX: Use execve/fork with an argument array.
// The shell is never invoked, so '; id' is treated as a filename, not a command.
char *args[] = {"/usr/local/bin/run_script", "--name", user_input.c_str(), NULL};
execve(args[0], args, env);
Trust is not a boundary. Headers are user input, not a trust signal. Assuming that “only internal components set this header” is a fatal flaw in any internet-facing device. Furthermore, CGI is not sandboxed. It executes with ambient privilege. Systems must be designed to assume hostile traffic by default, verifying every claim of identity cryptographically.
Immediate Action:
/cgi-bin/ in the URL path.