ZyXEL micro_httpd pre-auth RCE
ZyXEL routers running the micro_httpd web server are affected by a stack-based buffer overflow vulnerability due to insecure handling of memory in the getAuthInfoFromQuery function.
An unauthenticated user can send a specially crafted request to the /login/login-page.cgi URI and gain remote code execution under the root user scope.
Successfully tested on ZyXEL models VMG1312-B10A and VMG1312-B10B with firmware versions V1.00(AAJZ.17)C0 and V1.00(AAVS.0)b21.
Analysis of the vulnerable function
The binary is compiled for the 32-bit MIPS architecture in big endian format, We will proceed to disassemble the getAuthInfoFromQuery function.
0x00413944 8f9988f0 lw t9, -sym.imp.memset(gp)
0x00413948 27b00020 addiu s0, sp, 0x20
0x0041394c 00a08821 move s1, a1
0x00413950 00c09021 move s2, a2
0x00413954 00809821 move s3, a0
0x00413958 00002821 move a1, zero
0x0041395c 02002021 move a0, s0
0x00413960 0320f809 jalr t9
0x00413964 240603e8 _addiu a2, zero, 0x3e8 ; (1000 bytes)
As we can see, a memset() call is used to write 1,000 null bytes on a stack buffer, which is later written beyond bounds with an arbitrary length determined by the HTTP request input, suggesting a stack overflow condition.
0x00413984 3c02004f lui v0, 0x4f
0x00413988 8c47fca0 lw a3, -0x360(v0)
0x0041398c 8f998e58 lw t9, -sym.imp.fread(gp)
0x00413990 02002021 move a0, s0
0x00413994 02603021 move a2, s3 ; (s3 = input length)
0x00413998 0320f809 jalr t9
0x0041399c 24050001 _addiu a1, zero, 1
As we can see, value of the “a2” argument, which specifies the number of elements to read for the fread() function, is greater than our buffer’s size, so we can write beyond the getAuthInfoFromQuery function’s stack frame, overwriting the return address (ra) CPU register with an arbitrary memory address, hijacking the program’s control flow.
On newer firmware versions, the binary uses AES encryption. This can be useful to allow commonly bad characters on our shellcode, thanks to the fact that the cgiAESDencrypted function’s output will be base64 encoded. Also, there is an cgiUrlDecode function that will allow us to hex encode bad characters as well.
This is the code block that references the cgiAESDecrypted call in the getAuthInfoFromQuery function.
0x00413a70 8f998194 lw t9, -sym.cgiAESDecrypted(gp)
0x00413a74 0320f809 jalr t9
0x00413a78 00000000 _nop
The next code block references the cgiUrlDecode call on the getAuthInfoFromQuery function.
0x00413a7c 8fbc0010 lw gp, (var_10h)
0x00413a80 00402021 move a0, v0
0x00413a84 8f998df8 lw t9, -sym.cgiUrlDecode(gp)
0x00413a88 0320f809 jalr t9
0x00413a8c afa20018 _sw v0, (var_18h)
Proof of concept
We can now get reliable remote code execution with this buffer overflow in the httpd binary with root privileges (supervisor is the root user modified by ZyXEL).