Finding bugs to trigger Unauthenticated Command Injection in a NETGEAR router (PSV-2022–0044)

Flatt Security Inc.
11 min readMar 25, 2022



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 Analysis

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( 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.

WAC124 Specification

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 V1.0.4.6 when writing this article. The bug was officially fixed in V1.0.4.7

After downloading the firmware, extracting the firmware is very simple. Download and install binwalk ( ) and squashfs-tools to extract the firmware.

As seen below, it is possible to easily extract the firmware’s filesystem by using binwalk.

Output from binwalk
Router Directory Structure

Some key files for this article

The following is the list of files mentioned throughout this article.

  • /bin/mini_httpd, mini_httpd: The HTTP server daemon
  • /bin/setup.cgi, 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 username:password

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 @usb_opener_htm# in usb_new_fld.htm.


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

  1. Reads the requested file, with some file extension checks (we will talk about this later in the article)
  2. Finds for @variable#
  3. Replaces the template string to the actual value.
setup.cgi: html_parser

Also, there seemed to be other functions that add usb_opener_htm to 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.

403 Forbidden triggered by FindForbidValue

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 FindForbidValue.

setup.cgi: main

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.

setup.cgi: FindForbidValue

After a few trials of tests, I made the XSS work reliably without any problem.

view-source of the XSS payload
result of the XSS payload

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 .htm, .html, .asp will redirect the user to the login page, whereas accessing files with .png, .xml and other types of image extensions do not return any responses at all.

Filenames with htm/html/asp extensions redirect to the login page
Filenames with png/xml do not return any responses

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.

Ambiguous behavior

Now we have two things to figure out at this point.

  1. Why did xml files return outputs while .png and .jpg files didn't? Did it crash?
  2. Why did htm, asp, html files 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 main.

setup.cgi: main

Looking back at the html_parser function, it looks like the server checks whether the value of next_file contains (NOT ends with, strstr) .htm, .xml or .html.

But as we see in the first few trials, the login page was returned for asp, html and 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.

It does 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/to/file/blah.xml/1234 or 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 setup.cgi.

With all these things in my mind, I decided to brute-force the name of the drive and…

Bruteforcing %c to find the drive directory

Voila! It worked like a charm. We now have admin’s credentials in our hands.

Content of /etc/passwd

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 mini_httpd.

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.

Interestingly, this mini_httpd seemed to be a customized version from the original ACME's 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:

mini_httpd: path_exist

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.

This path_exist function basically

  1. checks if your path does not contain .htm, .html, .asp or such.
  2. checks if your path does not contain some dangerous characters that could cause unexpected behaviors, such as todo, etc.

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 e to %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 d to %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 mini_httpd.

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.

Trying authentication bypass with the fuzzed path

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 /tmp/itunes/apple.remote when 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 test_command_inject function.

We see that /bin, /sbin, `, 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)

Exploit Code

There is no precondition required to run this exploit. Any attacker who has access to 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.

Actual run of the exploit on the real router

Demonstration Video

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.

About us

Flatt Security Inc. provides security assessment services. We are willing to have offers from overseas. If you have any question, please contact us by

Thank you for reading this article.



Flatt Security Inc.

We are a cyber security company based in Tokyo, Japan. We provide security assessment services. HP: CVE: