22.01.2026 16:35

Look at FortiCloud SSO Bypass Exploitation (CVE-2025-59718/59719)

In December last year, Fortinet disclosed [1] a vulnerability in SAML processing, which allowed full bypass of authentication to management interfaces with FortiCloud SSO enabled. According to new, still not officially confirmed reports, the vulnerability may not have been fully patched [10]. As affected devices are represented in my small high-interactive honeypots network, we have an opportunity to take a look at what the attackers do.

First reports about the exploitation surfaced on the 12th of December, when it was reported by two security companies [2, 5]. Specifically, ArcticWolf reported observation of attackers exporting configuration backups. A few days later, following a question about that from my colleague, I looked into my honeypot logs and was able to immediately spot some suspicious requests to “/remote/saml/login”, but the feature wasn’t active on my devices. If you read the post from VulnCheck [3], you already know the spoiler – this was not the valid exploit.

I decided to give it a try, and on 19th December, I activated the FortiCloud SSO and waited. Meanwhile, I spotted multiple attempts to detect enabled FortiCloud SSO. They were similar to publicly available checks [6], but a different one than used later by VulnCheck: my logs showed special requests directly targeting the FortiCloud SSO feature, while VulnCheck looked for the presence of a FortiCloud SSO login button. Both ideas are good, and using crafted requests may have a goal of lowering hits on honeypots.

Based on the techniques and the post-exploitation activity, I grouped the attacks into a few campaigns, but it does not mean that they all are actually performed by different threat actors. In later attempts, actions originally performed separately started to be executed together (e.g., campaigns 1, 2 and 3 were later combined in one user session).

Campaign Zero

The group “zero” consists of requests from known “PoC” scripts [7]. As already stated, they seem to be invalid – in the case of my machines, they are ignored and treated as any other non-existing URL (this does not necessarily mean returning HTTP 404 in case of FortiGate). The confirmation that this is not the right exploit came around 10 pm at 2025-12-19, when my FortiGate instance got hacked the first time using a significantly different request.

The Campaign Zero appears to be active until the end of December.

First Campaign

It is important to note the first real exploit originated from an IP that previously checked if FortiCloud SSO was enabled. It used crafted requests, though a bit different from those available in GitHub [6]. The exploitation behavior seems similar to the behavior described by ArcticWolf, and the successful login bypass generates a log entry like:

eventtime=[REDACTED] tz="+0100" logid="xx" type="event" subtype="system" level="information" vd="root" logdesc="Admin login successful" sn="[REDACTED]" user="admin" ui="sso([REDACTED])" method="sso" srcip=[REDACTED] dstip=[REDACTED] action="login" status="success" reason="none" profile="super_admin" msg="Administrator admin logged in successfully from sso([REDACTED])"

Such a log is issued on every successful exploitation of the flaw. Following the login, the attacker performed exactly one request attempting to dump the configuration:

GET /api/v2/monitor/system/config/backup?destination=file&file_format=fos&scope=global

However, something did not go well: my FortiGate responded with HTTP 405 saying that the HTTP method is not allowed. This is a bit surprising because this path is widely used to download backups, and even the official library uses these kinds of GET requests [8, 9]. Unfortunately, the full REST API documentation is not publicly available. Some older docs and scripts suggest that the provided parameter values may not be correct, but I rather suspect targeting a specific FortiOS version, which is not present in my setup. The response to such a request does not contain the full configuration:

{"path":"system","name":"config","action":"backup","serial":"[REDACTED]","version":"v7.x.x","build":[REDACTED],"status":"error","http_status":405}

These attacks, in the pattern “exploit-dump-fail,” repeat regularly since activating the FortiCloud login. Additionally, these attacks did not target FortiGate exclusively, but also FortiWeb, where this API path does not exist at all. While it seems similar to Artic Wolf’s report, failed backup attempts do not cause FortiOS to emit any log, and – as shown above – do not result in a configuration dump in my environment.

The first campaign is the most present in my logs, with multiple exploit attempts a day.

Second campaign

The next day, I observed another exploitation of the FortiGate machine. This time the execution followed strictly the procedure from the Arctic Wolf post: after bypassing the login, a proper request was used to create a configuration dump. The request looks like (headers omitted):

POST /api/v2/monitor/system/config/backup
{"destination":"file","file_format":"fos","scope":"global"}

In response, a full configuration dump is returned. It also issues log entries like:

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="warning" vd="root" logdesc="Admin performed an action from GUI" user="admin" ui="GUI([REDACTED])" action="download" status="success" msg="System config file has been downloaded by user admin via GUI([REDACTED])"

Third campaign

The next day, I registered another successful exploitation attempt; this time the access was used to perform a single configuration change request: a new user with administrative privileges was created. The request looks like:

POST /api/v2/cmdb/system/admin?vdom=root
{"name": "root_admin", "wildcard": "disable", "remote-auth": "disable", "remote-group": "", "peer-auth": "disable", "peer-group": "", "trusthost1": "0.0.0.0/0", "trusthost2": "0.0.0.0/0", "trusthost3": "0.0.0.0/0", "trusthost4": "0.0.0.0/0", "trusthost5": "0.0.0.0/0", "trusthost6": "0.0.0.0/0", "trusthost7": "0.0.0.0/0", "trusthost8": "0.0.0.0/0", "trusthost9": "0.0.0.0/0", "trusthost10": "0.0.0.0/0", "ip6-trusthost1": "::/0", "ip6-trusthost2": "::/0", "ip6-trusthost3": "::/0", "ip6-trusthost4": "::/0", "ip6-trusthost5": "::/0", "ip6-trusthost6": "::/0", "ip6-trusthost7": "::/0", "ip6-trusthost8": "::/0", "ip6-trusthost9": "::/0", "ip6-trusthost10": "::/0", "accprofile": {"q_origin_key": "super_admin"}, "allow-remove-admin-session": "enable", "comments": "", "vdom": [{"name": "root"}], "schedule": "", "accprofile-override": "disable", "radius-vdom-override": "disable", "password-expire": "0000-00-00 00:00:00", "force-password-change": "disable", "two-factor": "disable", "two-factor-authentication": "", "two-factor-notification": "", "fortitoken": "", "email-to": "", "sms-server": "fortiguard", "sms-custom-server": "", "sms-phone": "", "guest-auth": "disable", "guest-usergroups": [], "guest-lang": "", "password": "[REDACTED]"}

This gives the highest possible permissions, without requiring any MFA method or restricting network access. Additionally, the provided password was very easy to brute force. This action causes a log entry like:

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="information" vd="root" logdesc="Object attribute configured" user="admin" ui="GUI([REDACTED])" action="Add" cfgtid=[REDACTED] cfgpath="system.admin" cfgobj="root_admin" cfgattr="old-password[*]accprofile[super_admin]vdom[root]password[*]" msg="Add system.admin root_admin"

Creating new admin accounts is also consistent with the newest report from Arctic Wolf [12].

Fourth campaign

Another day later, the authentication bypass was used to log in and collect device’s configuration. This time, instead of an API request, the built-in WebSocket-based terminal feature in the GUI was used. The attacker executed just four commands:

  • config system console
  • set output standard
  • show full-configuration user local
  • show system ha

The second command changes how the data are shown, preventing using pagination – this makes automated processing easier. The last two commands were used to collect the configuration, but instead of the full dump, only specific information about local users and high availability settings was collected. In later attempts, I observed the execution of show full-configuration as well, and the web console access was also used successfully on a FortiWeb instance.

Related log entries are:

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="information" vd="root" logdesc="Admin login successful" sn="[REDACTED]" user="admin" ui="jsconsole" method="jsconsole" srcip=[REDACTED] dstip=[REDACTED] action="login" status="success" reason="none" profile="super_admin" msg="Administrator admin logged in successfully from jsconsole"

eventtime=[REDACTED] tz="+0100" logid="0100044546" type="event" subtype="system" level="information" vd="root" logdesc="Attribute configured" user="admin" ui="jsconsole(192.168.2.254)" action="Edit" cfgtid=983367680 cfgpath="system.console" cfgattr="output[more->standard]" msg="Edit system.console "

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="alert" vd="root" logdesc="Configuration changed" user="admin" ui="jsconsole" msg="Configuration is changed in the admin session"

In these logs, we can see that the console was used, but there is no information on what exactly has happened.

Fifth Campaign

A few hours later I noticed another different exploitation. This time, the thread actor performed three requests, which created a new access profile, a new API user, and finally an API key for them. The requests look like:

POST /api/v2/cmdb/system/accprofile?vdom=root
{"name": "system_api", "scope": "vdom", "comments": "", "secfabgrp": "read-write", "ftviewgrp": "read-write", "authgrp": "read-write", "sysgrp": "read-write", "netgrp": "read-write", "loggrp": "read-write", "fwgrp": "read-write", "vpngrp": "read-write", "utmgrp": "read-write", "wanoptgrp": "read-write", "wifi": "read-write", "netgrp-permission": {"cfg": "none", "packet-capture": "none", "route-cfg": "none"}, "sysgrp-permission": {"admin": "none", "upd": "none", "cfg": "none", "mnt": "none"}, "fwgrp-permission": {"policy": "none", "address": "none", "service": "none", "schedule": "none", "others": "none"}, "loggrp-permission": {"config": "none", "data-access": "none", "report-access": "none", "threat-weight": "none"}, "utmgrp-permission": {"antivirus": "none", "ips": "none", "webfilter": "none", "emailfilter": "none", "data-loss-prevention": "none", "file-filter": "none", "application-control": "none", "icap": "none", "voip": "none", "waf": "none", "dnsfilter": "none", "endpoint-control": "none"}, "admintimeout-override": "disable", "admintimeout": 10, "system-diagnostics": "enable"}

POST /api/v2/cmdb/system/api-user?datasource=1&vdom=root&with_meta=1
{"name": "automation_user", "comments": "automation_user", "accprofile": {"q_origin_key": "system_api"}, "vdom": [{"name": "root"}], "schedule": "", "cors-allow-origin": "", "peer-auth": "disable", "peer-group": "", "trusthost": []}

POST /api/v2/monitor/system/api-user/generate-key?vdom=root
{"api-user": "automation_user"}

The related log entries look like:

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="information" vd="root" logdesc="Object attribute configured" user="admin" ui="GUI([REDACTED])" action="Add" cfgtid=[REDACTED] cfgpath="system.accprofile" cfgobj="system_api" cfgattr="secfabgrp[read-write]ftviewgrp[read-write]authgrp[read-write]sysgrp[read-write]netgrp[read-write]loggrp[read-write]fwgrp[read-write]vpngrp[read-write]utmgrp[read-write]wanoptgrp[read-write]wifi[read-write]" msg="Add system.accprofile system_api"

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="information" vd="root" logdesc="Object attribute configured" user="admin" ui="GUI([REDACTED])" action="Add" cfgtid=[REDACTED] cfgpath="system.api-user" cfgobj="automation_user" cfgattr="comments[automation_user]accprofile[system_api]" msg="Add system.api-user automation_user"

It seems like this user was then used to download the configuration, but unfortunately the corresponding request is missing in my honeypot logs, most likely due to an issue in my code I discovered later. Interestingly, the FortiOS log entry about downloading configuration seems to be issued shortly before logs about creating the user, and this pattern is present in logs from two different FortiGate instances.

eventtime=[REDACTED] tz="+0100" logid="[REDACTED]" type="event" subtype="system" level="warning" vd="root" logdesc="Admin performed an action from GUI" user="automation_user" ui="RESTAPI([REDACTED])" action="download" status="success" msg="System config file has been downloaded by user automation_user via RESTAPI([REDACTED])"

I only observed this campaign on the 22nd of December. The same IP originating from the AWS IP range exploited two different FortiGates but used two different names for created API users.

Real Exploit

The real exploit is still not easy to find online, but my colleague Erik was able to discover and obtain a working script from a toolkit used by an unknown threat actor. The behavior consists of collecting the configuration backup and creating a new admin user, but possible usernames differ from those observed in the honeypot activity. As such, we are pretty confident that the exploit has started to spread among multiple threat actors, and we expect more to discover it.

Consequences

The exploitation allows gaining full administrative control over the device, and the different exploitation attempts are still happening. Intentionally or not, some created accounts used weak passwords, and they have been quickly (in a few hours since creation) caught in brute-forcing attempts... Surprisingly, all by the same IP 178.22.24.20 that started brute force attempts against my honeypots on 2025-12-13, and it seems to use a quite limited set of credentials.

Until last Tuesday, we all believed that the vulnerability was patched, and we could just patiently wait for the next one. The recent report [10] suggests that has never been the case, although we still wait for the official confirmation. More importantly, some public comments suggest the existence of a vulnerability in the general SAML support, contrary to current statements that the scope is limited to FortiCloud SSO. This is not yet confirmed, as there is no evidence available.

Finally, it is important to mention that the flaw exists - so far - only on the management interface. We at CERT.at, and also Fortinet itself [11], strongly recommend keeping management interfaces out of the public internet, regardless of the authentication method used. To highlight the reason for this recommendation (besides newly published vulnerabilities targeting management interfaces), below you can see the number of login attempts to the management interface of one of my FortiGate honeypots in the last two months. On average, it’s about 50 thousand attempts daily.

Timeline

Time in UTC

  • 2025-12-12, around 9 am – first exploit attempts using public available (invalid) PoCs
  • 2025-12-12, around 10 am – first scans directly for enabled FortiCloud SSO
  • 2025-12-19, around 3 pm – enabling FortiCloud SSO on honeypots
  • 2025-12-19, around 9 pm – first successful exploitation (Campaign 1)
  • 2025-12-20, around 8 am – first successful configuration dump (Campaign 2)
  • 2025-12-21, around 2 am – first new user created (Campaign 3)
  • 2025-12-22, around 2 am – first configuration dump using terminal in GUI (Campaign 4)
  • 2025-12-23, around 8 am – first profile and API user created (Campaign 5)
  • Now – exploitation did not stop; almost all campaigns are still active. Last recorded exploitation: 2026-01-22

Indicators of Compromise

Usernames used in the initial authorization bypass

  • admin
  • support@forticloud.com
  • admin.workspace@gmail.com

Created access profiles

  • admin_api
  • system_api

Created API users

  • apiadmin
  • automation_user

Created admin accounts

  • sync
  • reports
  • forti-autosync
  • monitor
  • master_admin
  • security_admin
  • root_admin
  • admin1
  • admin2
  • adm1n
  • adm2n
  • admin3
  • roadmin

IP addresses performing exploitation

  • 38.60.203.31
  • 185.173.235.232
  • 161.35.185.133
  • 216.126.237.142
  • 45.131.153.211
  • 138.197.113.70
  • 45.152.65.134
  • 16.79.57.21
  • 223.254.128.15
  • 43.173.167.151
  • 103.106.230.140
  • 167.71.200.26
  • 81.90.188.105
  • 149.255.35.151
  • 108.61.187.236

Known VPN and proxies were excluded from the list.

References

[1] https://www.fortiguard.com/psirt/FG-IR-25-647
[2] https://arcticwolf.com/resources/blog/arctic-wolf-observes-malicious-sso-logins-following-disclosure-cve-2025-59718-cve-2025-59719/
[3] https://www.vulncheck.com/blog/forticloud-sso-login-bypass
[4] https://www.cert.at/de/warnungen/2025/12/kritische-sicherheitslucken-in-mehreren-fortinet-produkten-forticloud-sso-aktiv-ausgenutzt-updates-verfugbar 
[5] https://www.linkedin.com/posts/drayagha_for-the-latest-fortigate-cves-cve-2025-59718-activity-7407214356226281473-K0vf 
[6] https://github.com/darses/nuclei-templates/blob/fe913cc2030e33d69fdb5b1265483995f05e66ab/drafts/fortinet-fortios-cloudsso-detect.yaml 
[7] https://github.com/rix4uni/cvemapping/blob/7111dd6bac7aef9a4e324e38920851cfd41e36f0/2025/CVE-2025-59718/CVE-2025-59718.py 
[8] https://github.com/fortinet-solutions-cse/fortiosapi/blob/de593924f2b1018f1bfc85d4487858bdb676ebfc/tests/test_fortiosapi_virsh.py#L326
[9] https://github.com/fortinet-solutions-cse/fortiosapi/blob/master/fortiosapi/fortiosapi.py#L387 
[10] https://www.reddit.com/r/fortinet/comments/1qibdcb/possible_new_sso_exploit_cve202559718_on_749/ 
[11] https://docs.fortinet.com/document/fortigate/7.6.0/best-practices/317406/management-network 
[12] https://arcticwolf.com/resources/blog/arctic-wolf-observes-malicious-configuration-changes-fortinet-fortigate-devices-via-sso-accounts/ 

Written by: Kamil Mankowski