Finding bugs to trigger Unauthenticated Command Injection in a NETGEAR router (PSV-2022–0044)
Hi, I’m stypr (@stereotype32 ) from Flatt Security Inc.
Last year, I wrote a blog post about technical explanations about 0days found in Japanese OSS products.
I have found a lot of vulnerabilities in various products since then. Unfortunately, most bugs I found did not get it fixed right away, so I did not get any chances to share some exciting vulnerabilities I found until today.
This article will explain how I found various vulnerabilities and chained some of the vulnerabilities into an unauthenticated command execution without any preconditions in a NETGEAR’s WAC124(AC2000) router.
I spent about a week finding all these bugs and chained these bugs into two different exploits, one requiring a precondition and one not requiring a precondition.
The sad news was that NETGEAR only gives out bounties for Nighthawk and Orbi routers exclusively, which means I did not receive any bounties. However, It was fun to exploit as it was my first time to exploit this router apart from penetration tests and assessments.
It was so fun that I plan to dig some more vulnerabilities on other types of routers in the near future. I want to thank NETGEAR team for very kind and quick support.
Before starting analysis on an embedded device, We also need to check what components are available from the device and how the router stores firmware. This is done to ensure that I use my time efficiently by not missing out on any unseen critical factors.
Specification of the router
While reading the hardware specification, we can notice that the CPU is built for the MIPS architecture.
I’ve used Ghidra(https://ghidra-sre.org/) this time as Ghidra seemed to give a decent performance and quality on decompiling MIPS-based binaries.
Furthermore, the router has a USB port for media sharing, which will later be used for exploiting a vulnerability.
Dumping the firmware
It is good to note that some routers/IoT devices require some fundamental hardware knowledge to dump the firmware from the router or even require you to get access through the Serial(UART) port to access the debug/dev terminal.
Fortunately, NETGEAR firmware is generally available from the official website, so we need to google a bit for the firmware model and download the appropriate firmware. The latest(vulnerable) version for WAC124 is V22.214.171.124 when writing this article. The bug was officially fixed in V126.96.36.199
After downloading the firmware, extracting the firmware is very simple. Download and install
binwalk (https://github.com/ReFirmLabs/binwalk ) and
squashfs-tools to extract the firmware.
As seen below, it is possible to easily extract the firmware’s filesystem by using
Some key files for this article
The following is the list of files mentioned throughout this article.
mini_httpd: The HTTP server daemon
setup.cgi: The CGI(ELF Binary) for processing configurations
/www.eng/: root directory for the httpd server
/etc/htpasswd: plaintext file of unencrypted credentials for the admin page authentication — Format of the file is
Finding Cross-site Scripting (XSS)
It is generally a good idea to find some basic vulnerabilities such as Cross-site Scripting(XSS), as a lot of embedded devices in general are not sanitizing inputs properly for their web components.
With this in mind, I checked some possible HTM/HTML files in
/www.eng/, and I found a very interesting template-like parameter called
I decided to take a deeper look at how this works, and it was found that there is a function called
html_parser that parses the template from the accessed file.
I haven’t gone through in details with this function, but what this function does was
- Reads the requested file, with some file extension checks (we will talk about this later in the article)
- Finds for
- Replaces the template string to the actual value.
Also, there seemed to be other functions that add
nvram and such, but I will not go through it as the content might become too long to put in a single blog post.
However, it seemed like some of the malicious inputs were blocked by the server.
So I decided to check the
main function on
setup.cgi, where I found that some illegal inputs from HTTP requests are blocked by the function named
While reading the decompiled code of
FindForbidValue, I found that some parameters such as
|| and backticks (
`) are blocked by the filter, but XSS didn't seem to be blocked properly.
As long as these checks are bypassed, XSS would definitely work.
After a few trials of tests, I made the XSS work reliably without any problem.
However, my main objective was to trigger a shell while being an unauthenticated user.
I decided to take a serious look at other useful features. Although finding these vulnerabilities might look useless to some security researchers, finding XSS helped me refresh my mind before starting the static analysis.
Finding Unauthenticated Arbitrary File Read
I found out some weird behaviors from the
next_file parameter while testing
setup.cgi with a help of the manual static analysis,
When the user is not logged in, accessing files with
.asp will redirect the user to the login page, whereas accessing files with
.xml and other types of image extensions do not return any responses at all.
However, we see some irregularities on the outputs here. For some reason,
.xml does not return empty responses.
So, I decided to read existing files with path traversals and later found out that existing
.xml files can be read whereas the
next_file parameter cannot read existing image files.
Now we have two things to figure out at this point.
- Why did
xmlfiles return outputs while
.jpgfiles didn't? Did it crash?
- Why did
htmlfiles return the login page?
Analyzing the template routine
I decided to look at
setup.cgi again, and realized that
html_parser is always called when the
next_file parameter is passed to the function
Looking back at the
html_parser function, it looks like the server checks whether the value of
next_file contains (NOT ends with, strstr)
But as we see in the first few trials, the login page was returned for
htm extensions, and it didn't seem to pass through this routine.
I later found out that this kind of behavior was caused by
mini_httpd, which is the HTTP daemon of the router. I also assumed that
.png and other image extensions are also affected by the daemon, so I decided not to take a further look since we have
.xml file extension working at this point.
So, we know that any valid file containing
.xml on the filename will open properly. What should we do next?
Exploiting to trigger the system shell
Let’s take a look at the
html_parser function again.
strstr for the file extension check, which means that it DOES look for the existence of file extensions in the supplied filename, but that does not mean that the path has to end with one of those given file extensions.
This could mean that file paths such as
path/test.xml.asdf are still considered valid file paths.
So what we can do now is to create a valid folder like
valid_folder.xml and do path traversal from that folder to read an arbitrary file.
Now, we have the remaining problem of creating an invalid folder that contains
.xml on its name. As mentioned earlier in this article, we have a USB port on this router. So I decided to create a folder named
evil.xml on my USB drive and inserted this malicious drive into the router.
PS F:\> tree f v /F
The next step is to find the correct location of the mounted USB drive in the router. It was found out that the format of the mounted USB drive’s location is in
/mnt/shares/%c, as seen from the
With all these things in my mind, I decided to brute-force the name of the drive and…
Voila! It worked like a charm. We now have admin’s credentials in our hands.
From now, all we need is to log in as an administrator, enable Telnet from the debug page and then spawn the shell.
RCE#1 Exploit PoC (With preconditions)
Assuming that the router has the SMB server open without any credentials, we can just log in to the SMB server anonymously and upload the folder named
exploit.xml on the mounted disk and perform an arbitrary file read to leak the administrator's credentials.
To prevent people from running the exploit all over the internet, I decided to remove some codes from the actual exploit. It shouldn’t be too difficult to make this snippet usable, though.
It’s not over yet!
The drawback of this exploit is the precondition to achieve this attack.
Since my main objective was to get the shell without being unauthenticated and without any precondition, I decided to look deeper at other files such as
Finding Authentication Bypass
As seen earlier in the Arbitrary File Read, some file extensions didn’t seem to pass through
setup.cgi, so I decided to take a deeper look at
mini_httpd, which is the HTTP daemon module of the router.
mini_httpd seemed to be a customized version from the original ACME's http://www.acme.com/software/mini_httpd/ project.
Unfortunately, the customized build seemed to be somewhat far different from the original build, so I decided not to look at the official source code.
After disassembling the
mini_httpd and reading codes for a while, there seemed to be some kinds of checks in a function called
path_exist, and the code was a bit interesting:
It seemed too complicated to look at first sight. However, after reading this function and its related codes, I found out that the whole point of these codes is just to ensure that the unauthenticated user can only have access to certain types of file extensions.
path_exist function basically
- checks if your path does not contain
- checks if your path does not contain some dangerous characters that could cause unexpected behaviors, such as
Bypassing some filters
I decided to bypass the
todo= filter first since this parameter is essential for us to perform some important requests to the server.
Let’s first try with the existing payload we have in our hands.
Now, let’s see what happens with when we change
%65 from the parameter's name.
It still works perfectly even when the query string is encoded. In this case, we now know that the whole query string is decoded internally.
Now, let’s add
todo= on the request.
As we see, the server redirects the user to the login page (unauthorized request) as it is considered as an invalid path.
What if we change
%64 from the todo parameter's name?
For some unknown reason, passing
todo= returned the output of authenticated
index.htm, which is supposed to be shown only for authenticated users.
At this point, we now know that this string check is possible to bypass, and we also know that some unexpected behaviors are happening from the server.
Fuzzing the HTTP request
After some possible bypasses in the query string, I also found some weird behaviors when the HTTP request is sent by curl.
Do you see the first line of output? It sends
(null) 403 Forbidden as a response.
At this point, I stopped to think further since this
mini_httpd seemed like it had many unknown behaviors that appeared to be challenging to trace the root cause. Instead of looking further, I decided to write a dumb HTTP path fuzzer to find out some possible bypasses for the
After running the fuzzer for 20~30 minutes, I was able to bypass the authentication and get access without any prior authentication.
I’m removing the actual payload for your homework. It should be fun for you to create an HTTP fuzzer too. I think it is still possible to find authentication bypasses on other NETGEAR models, so this one is just for you. It seemed like some other security researchers have found similar or identical bugs by fuzzing the path, so it may be worth creating your own fuzzers. 🙂
Anyways, I’ve just developed a simple dumb fuzzer with a prefix of a valid path. You can possibly create something like my fuzzer and perform some fuzzing on the HTTP protocol.
Finding Command Injection
We now have a working authentication bypass, and we can now easily turn on the Telnet console to get access into the shell. But we still have some remaining problems; we don’t have the administrator’s credentials.
Although we have the arbitrary file read, we will not talk about it since it requires some preconditions to leak the credentials.
Again, looking back at the
setup.cgi code, I found something called the
COMMAND function, and this function seems to work like a typical
system() function, but with a format string support.
While looking at its XREF functions, I saw a function where you can set up a password for the iTunes Server. The function writes password to
remote_passcode is a valid name.
However, we see a check function called
test_command_inject before the
COMMAND function is actually being executed. Let's have a look at the
We see that
0x00 are blocked. Fortunately, we don't have the vertical bar(
|) being blocked by the check function.
Since the command is
/bin/echo [input] >> /tmp/itunes/apple.remote, We can put something like
admin:styexp>/etc/htpasswd|, which eventually becomes
/bin/echo admin:styexp>/etc/htpasswd|>>/tmp/itunes/apple.remote on the actual execution.
With this way, we can overwrite
/etc/htpasswd with the command injection vulnerability. There is no need to leak any credentials as we can directly run system commands from this function.
RCE#2 Exploit PoC (No precondition)
There is no precondition required to run this exploit. Any attacker who has access to
www.routerlogin.com can exploit and get the system shell from the server.
To prevent people from running the exploit all over the internet, I decided to remove some of its codes for now. However, it shouldn’t be too difficult for you to make this snippet to work.
The demonstration video includes the actual working exploit of the PoC code described above. The exploit will bypass the authentication, perform a command injection to overwrite the credentials and spawn a shell while being an unauthorized user.
Flatt Security Inc. provides security assessment services. We are willing to have offers from overseas. If you have any question, please contact us by https://flatt.tech/en/.
Thank you for reading this article.