This is a side door straight into the guard booth, not a harder way through the front gate
CVE-2024-55591 is an unauthenticated auth bypass in the FortiOS/FortiProxy management plane. Fortinet says crafted requests to the Node.js websocket module can yield super_admin privileges; affected versions are FortiOS 7.0.0–7.0.16, FortiProxy 7.0.0–7.0.19, and FortiProxy 7.2.0–7.2.12, with fixes in FortiOS 7.0.17, FortiProxy 7.0.20, and FortiProxy 7.2.13.
The vendor's CRITICAL label matches reality once you focus on how these boxes are used: they sit on the perimeter, hold policy control, VPN access, and routing trust, and this bug has confirmed in-the-wild exploitation and KEV status. The only real downward pressure is that the management interface must be reachable, but that friction is not enough to move this out of CRITICAL because exposed admin planes were exactly what attackers targeted in observed campaigns.
5 steps from start to impact.
Find exposed admin planes
- FortiGate/FortiProxy HTTP or HTTPS administrative interface is reachable from the internet
- Target is running an affected version
- Many mature enterprises keep management off the public internet or pin it to jump-host IPs
- Version fingerprinting is imperfect from the outside, so some scans produce false targets
Open a pre-auth WebSocket
- HTTPS management service responds
- Node.js websocket path is exposed on the target build
- WAFs rarely sit in front of firewall admin planes
- TLS inspection usually does not apply to inbound management sessions
Race or abuse the auth context to become super_admin
local_access_token shortcut, and a race into the CLI/Telnet auth path where no unique secret is needed to assert a privileged context. The attacker can effectively choose an access profile and land in the appliance CLI as an administrator.- Target is vulnerable and unpatched
- Exploit timing lands correctly or the crafted request path succeeds
- This is not a one-packet memory corruption bug; the attacker needs the right protocol behavior
- Fortinet notes that knowing an admin username helps CLI login, though brute forcing usernames remains feasible
jsconsole, Local_Process_Access, and suspicious admin object creation. Signature-only detection is incomplete.Establish durable control on the appliance
- Attacker has gained
super_adminprivileges - Configuration changes are permitted and saved
- Change monitoring or config drift alerting can surface this quickly
- Organizations with out-of-band config backups may detect unexpected deltas
ui="jsconsole", random admin usernames, and config changes under system.admin or SSL VPN groups.Pivot through the firewall into the enterprise
- Internal networks, VPN settings, or trust relationships are present behind the device
- The compromised box is a meaningful choke point in the environment
- Segmentation and identity controls still matter after initial access
- EDR on downstream hosts can still stop follow-on intrusion activity
The supporting signals.
| In-the-wild status | Confirmed exploited. Fortinet says it is exploited in the wild, and Arctic Wolf tied pre-disclosure activity from mid-November 2024 to this vulnerability. |
|---|---|
| KEV status | Yes. Added to CISA KEV on 2025-01-14 with a federal due date of 2025-01-21. |
| Proof-of-concept availability | Public research and tooling exist. watchTowr Labs published deep technical analysis plus a GitHub detection script and a PoC repo. |
| EPSS | 0.94124 from the user-supplied intel block — extremely high exploit probability and directionally consistent with real exploitation. |
| CVSS and interpretation | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H means unauthenticated remote takeover of the management plane with full confidentiality, integrity, and availability impact. |
| Affected versions | FortiOS 7.0.0–7.0.16; FortiProxy 7.0.0–7.0.19; FortiProxy 7.2.0–7.2.12. FortiOS 7.2, 7.4, and 7.6 are listed as not affected in Fortinet's advisory. |
| Fixed versions | Upgrade to FortiOS 7.0.17+, FortiProxy 7.0.20+, or FortiProxy 7.2.13+ per Fortinet PSIRT. |
| Observed operator behavior | Fortinet observed random admin creation, local user creation, group/policy changes, and SSL VPN login using attacker-added users. Arctic Wolf observed public-exposure cases with jsconsole logins and follow-on configuration changes. |
| Exposure data | Censys reported 3,445,797 exposed FortiOS/FortiProxy devices total at the time of its advisory, including 51 exposed FortiProxy instances; 16% were geolocated in the United States. Censys notes not all exposed instances could be version-confirmed as vulnerable. |
| Disclosure and reporting | Fortinet published the advisory on 2025-01-14. Fortinet credits Sonny of watchTowr for the CSF-related vulnerability work later added to the same advisory stream. |
noisgate verdict.
The decisive factor is simple: this is unauthenticated remote compromise of a perimeter security appliance that is already confirmed under active exploitation. The only meaningful friction is management-plane exposure, but when that condition is met the attacker lands on one of the highest-blast-radius boxes in the enterprise.
Why this verdict
- Baseline stays near max: start from vendor/NVD criticality because the bug is
AV:N/PR:N/UI:Non a perimeter device, not a niche internal service. - One real friction lowers it slightly: exploitation generally requires the HTTP/HTTPS administrative interface to be reachable, which narrows the exposed population versus 'every FortiGate on the internet.'
- That discount gets erased by field evidence: CISA KEV on
2025-01-14, Fortinet-confirmed exploitation, and Arctic Wolf's pre-disclosure campaign reporting show attackers were already doing exactly this against exposed admin planes. - Blast radius is exceptional:
super_adminon a firewall/VPN appliance means policy tampering, credential or config theft, rogue VPN access, and a clean path to internal-network pivoting.
Why not higher?
There is no higher bucket than CRITICAL, and this is close to the top of that range already. The one thing keeping it from a perfect 10 is that the attack path is not universal across all deployments; organizations that never expose the admin interface have materially less reachability.
Why not lower?
Downgrading this would ignore the two facts that matter most: active exploitation and initial-access utility on a perimeter choke point. This is not a post-compromise local bug, not an authenticated admin edge case, and not a narrow feature toggle affecting an obscure subset.
What to do — in priority order.
- Remove public admin exposure — Take the HTTP/HTTPS administrative interface off the public internet or pin it to explicit management-source IPs immediately, within hours because KEV/active exploitation overrides the normal timeline. This directly breaks step 1 of the attack chain and is Fortinet's preferred workaround.
- Apply restrictive local-in policy — If public removal is not immediately possible, restrict GUI access with Fortinet
local-in-policycontrols immediately, within hours so only jump hosts or admin networks can reach it. Fortinet explicitly prefers local-in policies over relying ontrusthostalone. - Disable Security Fabric if exposed and unpatched — For the CSF request variant described in Fortinet's advisory, disable Security Fabric from the CLI immediately, within hours if you cannot patch at once. This is a temporary blast-radius reduction, not a substitute for upgrading.
- Hunt for appliance persistence — Review Fortinet logs and configs for
ui="jsconsole",Local_Process_Access, random admin creation, local user creation, SSL VPN group changes, and unexpected firewall policy edits immediately, within hours. Active exploitation means exposure may already equal compromise. - Freeze new VPN/admin objects — Alert on or require review for any new local users, admin accounts, and SSL VPN group membership changes immediately, within hours. Fortinet and Arctic Wolf both observed attackers using those exact persistence patterns.
MFAon admin accounts does not fix a pre-auth management-plane bypass; the attacker is not following the normal login flow.- Blocking the IoC
srcip/dstipvalues from Fortinet logs does not help; Fortinet says those fields were attacker-generated parameters, not reliable source addresses. trusthostby itself is not enough unless every GUI user is configured with it; Fortinet explicitly sayslocal-in-policyis the preferred workaround.- Endpoint controls alone do nothing on the firewall itself; EDR only becomes useful after the attacker pivots beyond the appliance.
Crowdsourced verification payload.
Run this from an auditor workstation that can reach the target's HTTPS management interface; invoke it exactly as python3 fortios_cve_2024_55591_check.py 10.0.0.1 443. It needs only network access to the admin port and does not require credentials or privileged local access; it performs a lightweight pre-auth websocket behavior check and prints VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# CVE-2024-55591 pre-auth websocket behavior check
# Usage: python3 fortios_cve_2024_55591_check.py <host> [port]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=USAGE
import base64
import os
import random
import socket
import ssl
import sys
TIMEOUT = 7
def build_request(host, path):
key = base64.b64encode(os.urandom(16)).decode()
headers = [
f"GET {path} HTTP/1.1",
f"Host: {host}",
"User-Agent: noisgate-cve-2024-55591-check/1.0",
"Upgrade: websocket",
"Connection: Upgrade",
f"Sec-WebSocket-Key: {key}",
"Sec-WebSocket-Version: 13",
"Origin: https://%s" % host,
"\r\n",
]
return "\r\n".join(headers).encode()
def recv_some(sock):
data = b""
try:
while True:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
if b"\r\n\r\n" in data:
break
except socket.timeout:
pass
return data
def main():
if len(sys.argv) not in (2, 3):
print("UNKNOWN")
print("Usage: python3 fortios_cve_2024_55591_check.py <host> [port]", file=sys.stderr)
sys.exit(3)
host = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) == 3 else 443
# Use a few candidate paths because deployed behavior can vary by build.
candidates = [
"/ws/events",
"/ws/cli/open",
"/ws/%d" % random.randint(10000, 99999),
]
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
saw_rejection = False
errors = []
for path in candidates:
try:
raw = socket.create_connection((host, port), timeout=TIMEOUT)
raw.settimeout(TIMEOUT)
tls = ctx.wrap_socket(raw, server_hostname=host)
req = build_request(host, path)
tls.sendall(req)
resp = recv_some(tls)
tls.close()
head = resp.decode(errors="ignore")
if "101 Switching Protocols" in head:
print("VULNERABLE")
sys.exit(1)
if any(code in head for code in ["401 Unauthorized", "403 Forbidden", "404 Not Found", "400 Bad Request"]):
saw_rejection = True
continue
errors.append(f"{path}: unexpected response: {head[:120]!r}")
except Exception as e:
errors.append(f"{path}: {e}")
if saw_rejection:
print("PATCHED")
sys.exit(0)
print("UNKNOWN")
if errors:
print("; ".join(errors[:3]), file=sys.stderr)
sys.exit(2)
if __name__ == "__main__":
main()
If you remember one thing.
jsconsole, Local_Process_Access, rogue admin/local users, and VPN/group changes before restoring trust; that is your noisgate mitigation SLA override. Complete firmware remediation to fixed builds within the noisgate remediation SLA of ≤90 days, but in any environment with exposed admin planes you should operationally handle the upgrade as an emergency change, not backlog work.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.