Part 2 of a series about participation in the Pwn2Own Toronto 2023 contest.
This blogpost is the second part of the series about our journey to the pwn2own Toronto 2022 contest.
In Our Pwn2Own journey against time and randomness, part 1 we explained how we attacked the router from the WAN side and lost our battle against randomness and time just by a few seconds. Here we will describe two vulnerabilities that we found on the LAN side of the Netgear RAX30 router.
Pwn2own Toronto 2022 occured on December 6-9, 2022, so why publish this now ?
Well, we reported the discovered vulnerabilities to the vendor and followed a coordinated vulnerability disclosure process that took a lot of time, and we decided not to rage-quit and just publish them, so after a lenghty coordination process we reached the agreed date for publication and here's the blog post.
This vulnerability was not attributed to us, it seems that another competitor also found it. However, since we found this vulnerability during the competition, we will describe it nonetheless.
On the LAN side, a server brought to our notice this program called soap_serverd
, it is a server that is exposed on all LAN interfaces but does not seem to be designed for human interaction, which is of great interest to us.
In fact, this service is used by the Netgear application to communicate with the router.
During our analysis, we realized from the first line of parsing the HTTP entry that some things could go wrong. Indeed, as you can see in the decompiled code below, the parsing of the HTTP header is using sscanf
with a string format without size control.
memset(auStack_1820,0,2044);
local_1e24 = 0;
memset(auStack_1e20,0,508);
local_1c24 = 0;
memset(auStack_1c20,0,508);
local_1a24 = 0;
memset(auStack_1a20,0,508);
local_1024 = 0;
memset(auStack_1020,0,2044);
local_824 = 0;
memset(auStack_820,0,2044);
iVar1 = FUN_00019050(&local_1824,0x800);
if (iVar1 == 0) {
log_log(7,"handle_soapRequest",0x182,"line:[%s]",&local_1824);
pcVar6 = "No request found.";
}
else {
iVar1 = __isoc99_sscanf(&local_1824,"%[^ ] %[^ ] %[^ ]",&local_1e24,&local_1c24,
&local_1a24);
if (iVar1 == 3) {
iVar7 = strcasecmp((char *)&local_1e24,"post");
if (iVar7 == 0) {
FUN_00015060(param_1);
soap_serverd
reads the HTTP headers calling sscanf
to extract the HTTP method, path, and protocol version.
Because we didn't want to waste any time unnecessarily, our strategy was to focus on vulnerabilities that were easier to exploit, and we kept this vulnerability for the time we had left. The Netgear hotfix released right before the contest forced us to change our plans and we never went any further than a simple DOS with this vulnerability.
However, we were happy to see that, in their last release Netgear corrected this vulnerability by controlling the size of the parsed elements.
iVar1 = __isoc99_sscanf(&local_1824,"%511[^ ] %511[^ ] %511[^ ]",&local_1e24,&local_1c24,
&local_1a24);
If you are interested in a blogpost that converts this vulnerability into a remote code execution one, we strongly encourage you to read this blogpost.
The Netgear RAX30 router integrates several paths to access a set of services, some with authentication and some without.
Among the unauthenticated calls, there is the possibility to push files to the router via the rex_cgi
binary.
Below, an extract of rex_cgi
which handles the file uploads.
if (http_docroot != '\0') {
if (http_boundary == (void *)0x0) {
pcVar1 = "/tmp/uploadFile";
} else {
pcVar1 = "/tmp/multipartFile";
}
strcpy(psz_dest_file,pcVar1);
pfile_dest = fopen(psz_dest_file,"w+");
if (pfile_dest != (FILE *)0x0) {
for (i = http_content_length; i != 0; i = i - 0x400) {
if (i < 0x401) {
fread(buffer,i,1,stdin);
fwrite(buffer,i,1,pfile_dest);
break;
}
fread(buffer,0x400,1,stdin);
fwrite(buffer,0x400,1,pfile_dest);
}
fclose(pfile_dest);
strcpy(&DAT_00027427,psz_dest_file);
}
As you can see, the file is directly written inside the /tmp
directory, and no size verification is done. Thus, if you submit a file of a size greater than router's available RAM, the router will freeze.
To try this you can use this script:
import requests
url = 'http://192.168.1.1/tm_block/tm_block.cgi'
files={'testfile':open('test.cfg','rb')}
resp = requests.put(url, files=files)
print(resp.status_code)
To have a POC that works properly, you should have a test.cfg
file of size over 1G, the RAM size of this router.
This vulnerability is not critical, but it could be really inconvenient for anyone wanting to use this router.
We reverse engineered the firmware format of the Netgear RAX. The figure below details it precisely:
This analysis was based on the original image found on the manufacturer's website, the bcm_flasher
binary and its library libbcm_flashutil.so
allowing to flash the image on the NAND memory and the library libpu_util.so
containing two interesting functions for the understanding of the firmware format:
puUtl_signCfg
is used to check the signature embedded in the firmware format, andpuUtl_getSignHeader
is used to retrieve the important information contained in the proprietary header.The router uses a wrapping header for the ITB format used on Broadcom chipset and some other chipset, the ITB format is a firmware format based on FDT (Flattened Device Tree) for SoC (System-on-a-Chip) of the ARM processor family. The ITB format is designed to store a firmware image that includes both the firmware binary code and a Flattened Device Tree representation of the devices embedded in the SoC. This representation allows the firmware to recognize the hardware devices and configure them correctly. The ITB format also allows for the storage of important metadata such as firmware version, security information and firmware integrity checks. It is possible for instance to create a signed image commonly called a FIT image, but for our case it is not used. They have their own method for signing the firmware.
In fact, one field is called signature
, and this signature is based on sha256 and 2 salted parts (K and K2). This verification is found inside the puUtl_signCfg
function.
SHA256_Init(sha_context);
SHA256_Update(sha_context,"hr89sdfgjkehx",0xd);
SHA256_Update(sha_context,&Header,iVar4 + 4U);
SHA256_Update(sha_context,ITB,size_ITB);
SHA256_Update(sha_context,"nohsli9fjh3f",0xc);
SHA256_Final((uchar *)&uStack_548,sha_context);
A sha256 hash is computed with hr89sdfgjkehx
+ PADDING + TYPE_VER + SIZE + VERSION + TYPE_VER + SIZE + DB_VERSION + ITB_HEADER + ITB + nohsli9fjh3f
.
Thus, because the "signature" of a firmware image is just the SHA256 digest of image data concatenated with a secret string embeded in the the firmare it is possible to create firmware images with valid signatures that will pass the signature validation check.
During our participation in the Pwn2Own Toronto 2022 contest we discovered a couple of vulnerabilities that exploitable on the LAN side of the router. One of them was also discovered by another participating team and it is fixed in the latest firmware version. We reverse engineered the firmware's format and also discovered that is possible to create custom firmware with a valid signature and flash it on the router for persistence. We reported the vulnerabilities to Netgear and followed a 173-day coordinated disclosure process for publication.
case #47304021
in their technical support portal.case #47468661
in their technical support portal.If you would like to learn more about our security audits and explore how we can help you, get in touch with us!