Advisories

ASUSWRT Stack Overflow in wanduck.c

CVE ID

CVE-2018-20336

Tested Versions

  • ASUSWRT 3.0.0.4.384.20308 (2018/02/01)

Product URL(s)

  • https://www.asus.com/us/ASUSWRT/

ASUSWRT is the firmware that is shipped with modern ASUS routers. ASUSWRT has a web-based interface, so it doesn’t need a separate app, or restrict what you can change via mobile devices – you get full access to everything, from any device that can run a web browser.

Vulnerability

There is a stack overflow issue in parse_req_queries function in wanduck.c, which may lead to information leak.

PoC

from socket import *
HOST = '192.168.50.1'  
PORT = 18018
BUFSIZE = 4096
ADDR = (HOST, PORT)  
udpCliSock = socket(AF_INET, SOCK_DGRAM)   
data = "A"*2046+"\x00F"
udpCliSock.sendto(data,ADDR)  
data,ADDR = udpCliSock.recvfrom(BUFSIZE)  
if data:
	print len(data)  
udpCliSock.close()

Wanduck is a program that listens on TCP 18017 and UDP 18018. Port 18017 is most likely a HTTP server and 18018 is most likely a DNS server. The HTTP server on port 18017 will redirect HTTP request to the 80 port and the DNS server will process the dns request packet.

The file wanduck.c can be found under the src/rc/ directory is the provided ASUSWRT source code. We are going to have a look at the main function wanduck_main, this is the entry of the main program. run_dns_serv function is the entry to receive packet from port 18018, we will check the code from this function, the source is like below:

void run_dns_serv(int sockfd){
	int n;
	char line[MAXLINE];
	struct sockaddr_in cliaddr;
	int clilen = sizeof(cliaddr);
	memset(line, 0, MAXLINE);
	memset(&cliaddr, 0, clilen);
	if((n = recvfrom(sockfd, line, MAXLINE, 0, (struct sockaddr *)&cliaddr, (socklen_t *)&clilen)) == 0)	// client close
		return;
	else if(n < 0){
		perror("wanduck serv dns");
		return;
	}
	else
		handle_dns_req(sockfd, line, n, (struct sockaddr *)&cliaddr, clilen);
}

MAXLINE is defined in wanduck.h with the value of 2048, so this function will first receive a max buffer of 2048 bytes from the sender, and then handle_dns_req will process the DNS request packet. A variable reply_content is defined at the begining of the handle_dns_req function like below:

void handle_dns_req(int sfd, char *line, int maxlen, struct sockaddr *pcliaddr, int clen){
	dns_query_packet d_req;
	dns_response_packet d_reply;
	int reply_size;
	char reply_content[MAXLINE];

As we can see here, reply_content is designed to have a max size of 2048 bytes. After process is intialized in handle_dns_req, the dns request packet will be passed to parse_req_queries function, this function accepts 4 parameters:

  • char *content, stands for the reply(dns response) content
  • char *lp, stands for the DNS Question part
  • int len, stands for the length of the DNS Question part (a max of 2048-len(dns request header))
  • int *reply_size, pointer to the reply packet size

Let’s check this function, the whole function code is as below:

void parse_req_queries(char *content, char *lp, int len, int *reply_size){
	int i, rn;
	rn = *(reply_size);
	for(i = 0; i < len; ++i){
		content[rn+i] = lp[i];
		if(lp[i] == 0){
			++i;
			break;
		}
	}
	if(i >= len)
		return;
	content[rn+i] = lp[i];
	content[rn+i+1] = lp[i+1];
	content[rn+i+2] = lp[i+2];
	content[rn+i+3] = lp[i+3];
	i += 4;
	*reply_size += i;
}

Obviously, there is a stack overflow in the function: let’s assume there is a NULL(\x00) at the second-to-last of lp buffer, then the for loop will ended and i will be added with 1. There is a judgement to check whether i is larger than len, but this check is not enough. Let’s assume i equals to len-1, then the following code will be executed:

content[rn+i] = lp[i];
content[rn+i+1] = lp[i+1];
content[rn+i+2] = lp[i+2];
content[rn+i+3] = lp[i+3];
i += 4;
*reply_size += i;

As we have said, rn points to the reply packet size and this value, in this case, the rn passed to this function is the length of DNS Request Header. Then,

rn+i == rn+len-1

rn+i+1==rn+len

rn+i+2==rn+len+1

rn+i+3==rn+len+2

i+4==len-1+4==len+3

reply_size= len(dns header)+len-1+4

So this will casuse an Out of bound write problem, and also, this will also cause information leak, for the reply_size is greater than 0x800(2048) bytes.

Let’s debug the wanduck process dynamically, we will use the PoC we provied.

sub_7D218 is the handle_dns_req function, and sub_7D0F8 is the parse_req_queries function. We first set a breakpoint at the line where parse_req_queries is called:

Then we run our PoC code, and the program will break at line 63, and then we will follow the code and see what happens.

a3 is len in source code, and in our case this value is 0x7F4 because the DNS header is 0xC, so the value is 0x800-0xC=0x7F4. Since the second-to-last byte in our PoC is “\x00”, so line 20 will be hit when v4 is 0x7F3, since 0x7F3 is less than 0x7F4, then line 22 to line 30 will be executed.

result in our case is at 0xBE85A71C, from the source code we know its size is 0x800, so the end address of result is at 0xBE85AF1C, when line 26 is executed, result will be 0xBE85AF1B, so line 28 and line 29 will cause a buffer overflow.

What’s more, the reply_size will be 0x7F4+0xC+0x3=0x803, at the end of handle_dns_req , reply_size will also add 0x10(sizeof(d_reply.answers)), so the final reply_size will be 0x813. It is larger than 0x800, which will cause an information leak.

Vendor Response

The vendor has acknowledged the issue and released a new firmware update to address this vulnerability.

The updated firmware can be downloaded from the Support section of a particular router that runs ASUSWRT, such as https://www.asus.com/Networking/RTAC68U/HelpDesk_Download/.

The update description lists both issues CVE-2018-20334 and CVE-2018-20336 discovered by STAR Labs as fixed.

Timeline

  • 2019-02-19 Vendor disclosure
  • 2019-02-25 Vendor acknowledged
  • 2019-03-29 Firmware update released

Credit

Discovered by CodeBreaker of STAR Labs

Want to participate in such cutting-edge research?

We are hiring!

Find Out More