# How the Kaseya VSA Zero-Day Exploit Worked **blog.truesec.com/2021/07/06/kaseya-vsa-zero-day-exploit** This article explains the pre-auth remote code execution exploit against Kaseya VSA Server that was used in the mass Revil ransomware attack on July 2, 2021. After validating the patch and verifying that the attack vector is no longer present, we believe it is time to share these details for the benefit of the community. On July 5, after an initial investigation of affected organizations, Truesec contacted Kaseya and provided a detailed technical write-up of these vulnerabilities along with forensic evidence of exploitation. Kaseya released the patch 9.5.7a (9.5.7.2994) that addresses the security issues on July 11. We strongly believe this information will help the security community in their response to the attack and from a larger perspective it will help the industry understand what happened so we can address the underlying issues, and ultimately increase our capabilities to prevent future breaches. ### Overview The exploit abused four vulnerabilities in the Kaseya application that were chained as visualized in the figure below. ----- ## The High-Level Steps of the Exploit [1. Obtained an authenticated session by abusing a flaw in the authentication logic [CWE-304] in /dl.asp.](http://cwe.mitre.org/data/definitions/304.html) [2. Uploaded the revil ransomware (agent.crt) through an unrestricted upload vulnerability [CWE-434] while also bypassing the request](http://cwe.mitre.org/data/definitions/434.html) [forgery protection [CWE-352] in /cgi-bin/KUpload.dll.](http://cwe.mitre.org/data/definitions/352.html) 3. Uploaded the ASP payload (screenshot.jpg) in the same fashion as described in 2. [4. Invoked the payload in screenshot.jpg through a local code injection vulnerability [CWE-94] in userFilterTableRpt.asp.](http://cwe.mitre.org/data/definitions/94.html) 5. Created Kaseya procedures to copy files and execute the ransomware. 6. Executed the procedures. 7. Removed logs and other forensic evidence. Here we will focus on the exploit and describe steps 1 through 4 in detail. The payloads are not in scope for this article. ## Step 1 – Bypassing Authentication [CWE-304] The threat actor first sent a POST request to the resource /dl.asp with the POST data userAgentGuid=guid. First request of the exploit: authentication bypass In dl.asp, userAgentGuid is used in a SELECT query to lookup the database row of the agent. The agentGuid must exist due to the subsequent if statement. The threat actor used the agentGuid of the agent on the VSA server itself (more on this below). After the lookup, dl.asp tries to see if the provided password matches the values stored in the database for that agent. The provided password is then compared in several different ways. The login flow is illustrated in the pseudo code below: if password == hash(row[nextAgentPassword] + row[agentGuidStr]) login ok elseif password == hash(row[curAgentPassword] + row[agentGuidStr]) login ok elseif password == hash(row[nextAgentPassword] + row[displayName]) login ok ----- e se pass o d as ( o [cu ge t ass o d] o [d sp ay a e]) login ok elseif password == row[password] login failed else login ok The last two statements are where the interesting thing happens. In case the password equals row[password] the login will fail. However, in **the case that all checks failed, it would default to an else clause that sets “loginOK” to true.** Because no password was provided in the request, the “password” variable would be NULL and loginOK would end up being true. When loginOK is set to true, the application sends the login session cookie and will eventually (if no other parameters are provided like in the attacker’s request) end up in an if clause that returns 302 redirect to the userPortal. Response to authentication bypass request ### How Did the Actor Obtain the AgentGuid? The outstanding question is how the threat actor obtained around 60 (estimated number of VSA victims) unique valid agentGuids. It appears they simply knew the agentGuids before launching the attack. Truesec has discovered several methods the attack could have been performed without prior knowledge of a valid agentGuid. An example that was also fixed in 9.5.7a is that the userAgentGuid parameter could have been .root.kserver instead of an actual agentGuid. Due to what was described as legacy code, in case agentGuid is not a number, it will lookup the agentGuid automatically from the table machNameTab. Truesec has not found and is not aware of any public evidence that shows exactly how these agentGuids were obtained. Possibly, these random agentGuids might have been what limited the impact of the attack to under 60 out of around 35 000 Kaseya VSA customers. ## Steps 2 and 3 – Uploading Files [CWE-434] [CWE-352] All requests from this point on used the authenticated session obtained in step one. The threat actor started the upload by sending a GET request to /done.asp without any parameters. When receiving the request, the application creates a row in the tempData table, stages an upload folder, and finally returns a so-called loadKey value. A valid loadkey is required to perform the file upload. ----- Request and response to obtain a valid loadKey To upload files the threat actor sent a multiform-data POST request to the resource /cgi-bin/KUpload.dll. The request contained the following parameters: FileName (name of the file) FileData (content of the file to upload) LoadKey (the value obtained by GETting done.asp) RedirectPath (path that the application will redirect to after successful upload) PathData (folder the file will be saved in) __RequestValidationToken (bypassable CSRF token) ### Bypassing the CSRF Protection The __RequestValidationToken was not properly validated. For example, the value “xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx” was accepted as a valid token. ### Uploading the Ransomware (Agent.crt) The first upload performed by the threat actor was a file named agent.crt. This file was an encoded version of the ransomware that was later pushed to all agents from the compromised VSA server. ----- Upload of agent.crt. Upon successful upload, the server returns HTTP 200 OK with a body containing a link pointing to /?FileName= &PathData=&originalName=&FileSize=&TimeElapsed=