Our last blog post on the FortiClient EMS SQL injection vulnerability, CVE-2023-48788, as it turns out only worked on 7.0.x versions. This article will discuss the differences in exploitation between FortiClient EMS’s two mainline versions: 7.0.x and 7.2.x.
When writing exploits for different versions of vulnerable software, the differences in the exploit are usually small, such as different offsets, renamed parameters, or changed endpoints. Exploitation of the 7.2.x attack path for CVE-2023-48788 was an interesting challenge, because the core vulnerability and endpoint being attacked were the same, but the code path traversed was largely different.
A quick review of the previous article shows that this vulnerability affects ‘binary components’ of this software suite. Rather than this being a web application based SQL injection, this SQL injection is performed against an endpoint written in C++ and Golang and compiled for a 64 bit x86 Windows target. It also uses a custom linefeed based protocol.
Windows and SQL injection? Sounds like XP_CMDSHELL. Delicious.
XP_CMDSHELL is a type of SQL stored procedure used to evaluate custom commands on input or output data. A trivial example would be to use the XP_CMDSHELL procedure to hash a user password before storing its digest in a registered users table.
Attackers, however, frequently abuse this capability to turn SQL Injection into Remote Code Execution. That is how the exploit we designed for NodeZero gains access to vulnerable EMS servers.
Mitigations exist to prevent damage caused by this feature of MSSQL. For example, by default, XP_CMDSHELL is disabled and must be re-enabled by the attacker to execute commands. The privilege needed to re-enable XP_CMDSHELL is also a removable privilege, so the account in use can be prevented from enabling this functionality. In practice, this is rarely done, so SQLi against an MSSQL server is almost always a path to RCE.
The original 7.0.x weaponization utilized MSSQL’s CONVERT
on a hex encoded payload to bypass a behavior of FortiClient that would always uppercase the entire SQL query. This behavior causes many arbitrary Windows commands to fail as most utilities are case sensitive.
Figure 1. Original 7.0.x payload
Running the 7.0.x variant of the exploit against a vulnerable 7.2.x target immediately shows an issue.
The target returns an extremely opaque error message. From here, it seems prudent to observe the logs of a real client, and put the server in a debug logging state so we can observe any changes in the message type being sent to the server.
From this we can see that the format of the arguments for the registration message has changed (the SYSINFO field is now between the pipe characters). Further inspection of the base64 encoded system information parameter shows several required fields have been added to the message format.
Compensating for these changes is easy enough, and we can use copy and pasted data from the sample registration we observed. This, however, requires performing version detection to determine the correct message format for the exploit. Fortunately, there is a request that provides us with this information.
The new version of this output is as follows:
Let’s look at what is going on. Now that we have a valid message, let’s attempt to reuse our previous exploit payload.
A similarly opaque error message like before. That’s unfortunate. Let’s dig into what is going on here.
Interesting. 7.2.x appears to be using = as a delimiter on the input being provided to the vulnerable function. Another thing to note is that our input is being converted to upper case in the code path leading to the vulnerable function. This disqualifies base64 as an encoding method.
At this point, to diverge from the cool and dispassionate tone of most exploit deep dives, I’ll provide a glimpse into the reality of exploit development for people interested in this field, and encouragement for fellow exploit development professionals. We ran into three simple issues that coalesced into a difficult upgrade process. This set of issues disguised our success for over a week, in which we hammered this target with everything we could think of to get around the equals sign delimiter issue. The core issues follow:
The confluence of these issues was being unable to detect successful exploitation until we looked into the audit logs of the SQL server itself. At that point, we saw invocations of XP_CMDSHELL going back to the day after we began this project. Changing the test payload from calc.exe to notepad.exe demonstrated successful exploitation.
To quote Vonnegut: “So it goes.”
We mitigated the equals delimiter and upper case issues by using a mix of PowerShell and url encoding.
The basic algorithm of the payload looks like this:
EXEC xp_cmdshell ‘Powershell.exe -command “cmd.exe /c “UrlDecode( “<url encoded attacker command>” ) “‘
It relies on powershell to decode the arguments passed to cmd.exe. `start /b` was not needed here, due to the fact that MSSQL spawns commands in it’s own session, which is not attached to a window.
And the post-ex process tree for our lovely defender siblings.
NodeZero Attack Path utilizing CVE-2023-48788 to load a remote access tool and dump LSASS
Horizon3.ai clients and free-trial users alike can run a NodeZero operation to determine the exposure and exploitability of this issue.