CVE-2021-31802 NETGEAR R7000 httpd PreAuth RCE

TL;DR

CVE-2021-31802 is my first CVE assignment, Thanks @d4rkn3ss for his brilliant work. I learn a lot from his work. Thanks @SecuriTeam_SSD for their professional work, they help me to get the CVE assignment within a month, which I never got a response from MITRE by myself.

Vulnerability Summary

This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of NETGEAR R7000 routers. Authentication is not required to exploit this vulnerability.
The vulnerability exists within the handling of http request, the issue results from the lack of proper validation of user-supplied data, which can result a heap overflow. An attacker can leverage this vulnerability to execute code with the root privilege.

Affected Systems

Netgear Nighthawk R7000 running firmware version 1.0.11.116 and before

Vulnerability Root Cause Analysis

I bypass the patch for the ZDI-20-709 vulnerability, so I get this vulnerability. The patch for ZDI-20-709 cannot solve the root cause of the vulnerability. The httpd program allows user to upload a file with the url /backup.cgi. While the root cause of the vulnerability is that the program uses two variables to represent the length of the uploaded file.
One variable is related to the value of the Content-length in the http post request header, the other one is the length of the file content in the http post request body.

The vulnerability exists in the sub_16674. Below picture is the heap overflow point:

The decompiled code is like this:

The program allocates memory for storing the file content by calling malloc,the return value is stored by dword_1DE2F8, the size is the value of Content-Length plus 600. The Content-Length value can be controlled by the attacker, thus if we provide a proper value, we can make the malloc to return any size of the heap chunk we want.

The memcpy function copies the http request payload from s1 to dword_1DE2F8, the copied buffer length is v80 - v91 which is the length of the file content in the http post request body.

So this is the problem, the size of the heap-based buffer dword_1DE2F8 can by controlled by the attacker with a small value, and the v80-v91 can also by controlled with another larger value. Thus, it can cause a heap overflow.

Exploit Considerations

The patch for ZDI-20-709 is that it adds a check for one byte before Content-Length, it checks if it is a '\n', I simply add a '\n' before the Content-Length in order to bypass the patch. Though the vulnerabilities are basically the same, but the exploit still needs a lot of efforts because the heap states are different between R6700 and R7000.

We may conduct a fastbin dup attack to the heap overflow vulnerability. But it is not easy to do this. Fastbin dup attack needs two continuous malloc function to get two return address from a same fastbin list, the first malloc returns the chunk whose fd pointer is overwritten by the heap overflow, the second malloc returns the address where we want to write data.

The bigest problem is that there should be no free procedure between these two malloc functions. But dword_1DE2F8 is checked every time before malloc:

If dword_1DE2F8 is not a null pointer, it will be freed and set 0. Thus we should find another point of calling malloc.

Luckily, there is another malloc whose size can by controlled by us, it is in the function of sub_A5B68

The function handles another file upload http request, we may use the /genierestore.cgi to trigger this function.

But there is another problem, both /genierestore.cgi and /backup.cgi requests can cause the fopen function gets called. The fopen function will call malloc(0x60) and mallloc(0x1000). malloc(0x1000) will cause __malloc_consolidate function gets called which will destroy the fastbin, since the size is larger than the value of max_fast.

We need to find a way to change the max_fast value to a large value so that the __malloc_consolidate will not be triggered. According to the implemenation of uClibc free function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 if ((unsigned long)(size) <= (unsigned long)(av->max_fast)

#if TRIM_FASTBINS
/* If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins */
&& (chunk_at_offset(p, size) != av->top)
#endif
) {

set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]); // <-------when size is set 8 bytes, the fastbin_index(size) is -1
p->fd = *fb;
*fb = p;
}

When we free a chunk whose size is 0x8, fastbin_index(size) return -1, and av->fastbins[fastbin_index(size)] will cause an out-of-bounds access.

1
2
3
4
5
6
7
8
9
10
11
struct malloc_state {
/* The maximum chunk size to be eligible for fastbin */
size_t max_fast; /* low 2 bits used as flags */
// 0

/* Fastbins */
// 4
mfastbinptr fastbins[NFASTBINS];
...
}

According to the struct of malloc_state, fb = &(av->fastbins[-1]) exactly points to max_fast, thus *fb = p will make the max_fast to a large value.
But in the normal situation, the chunk size cannot be 0x8 bytes, because it means that the user data is 0 byte. So we can first make use of the heap overflow vulnerability to overwrite the PREV_INUSE flag of a chunk so that it incorrectly indicates that the previous chunk is free. Due to the incorrect PREV_INUSE flag, we can get malloc() to return a chunk that overlaps an actual existing chunk. This lets us edit the size field in the existing chunk’s metadata, setting it to the invalid value of 8. When this chunk is freed and placed on the fastbin, malloc_stats->max_fast is overwritten by a large value.Then the fopen will not lead to a __malloc_consolidate, so we can conduct a fastbin dup attack.
Once we make the malloc return a chosen address, we could overwrite the GOT entry of the free to the address of system PLT code. Finally we execute utelnetd -l /bin/sh to start the telnet service, then we get the root shell of R7000.

I use some techniques to make the exploit more steady.

  1. To make the malloc chunks are adjacent so that the heap overflow will not corrupt other heap-based buffers, I send a very long payload to trigger closing the tcp connection in advance so that the /backup.cgi request will not calling fopen subsequently, and there will be no other malloc calling between two http requests.

  2. The httpd program’s heap state may be different when user login or logout the web management, to make the heap state consistent,I find that when I try to login the web management with wrong password for 3 times, the httpd program will redirect the user to a Router Password Reset page. I can make use of this feature to achieve a steady heap state.

Reference

  1. SSD Advisory – NETGEAR Nighthawk R7000 httpd PreAuth RCE
  2. CVE-2021-31802
  3. ZDI-20-709: HEAP OVERFLOW IN THE NETGEAR NIGHTHAWK R6700 ROUTER