The Anatomy of a Bug Door: Dissecting Two D-Link Router Authentication Bypasses

October 01, 2020 | Vincent Lee

Back in February, D-Link released a firmware patch for two authentication bypass vulnerabilities, ZDI-20-267 (CVE-2020-8863) and ZDI-20-268 (CVE-2020-8864), that affected the D-Link DIR-882, DIR-878 and DIR-867 routers. These wonderful bugs resided in the handling of the HNAP protocol. The vulnerabilities were discovered and reported to the Zero Day Initiative by chung96vn of VinCSS (Member of Vingroup).

We’ll first look into ZDI-20-268 to familiarize ourselves with HNAP’s authentication scheme. After that, we’ll analyze the rather peculiar ZDI-20-267, which has the word “backdoor” written all over it.

But first, what is HNAP?

HNAP, or Home Network Administration Protocol, is a proprietary SOAP-based protocol invented by Pure Networks, Inc., which was later acquired by Cisco. This protocol dates back to 2007 and can be thought of as a direct competitor of UPnP. The prominent users of this protocol were Cisco and D-Link. However, both have discontinued the use of this protocol in 2012 and 2016 respectively. This functionality is often hidden from the administrative panel, making it impossible to disable. If your router still supports HNAP, it probably means your router is due for an upgrade. 

Being an obsolete proprietary protocol, little documentation for it exists on the Internet. HNAP offers two types of authentication schemes: Basic and HMAC-based. The best documentation on the HMAC-based authentication scheme I could find is a Github wiki page from a reverse engineering project.

HNAP Authentication Process

Authentication to the server (router) requires two transactions. First the client sends a request message and obtains an authentication challenge from the server.

The server responds to the request with three values: Challenge, Cookie and PublicKey

The client must first combine the PublicKey along with the user password to create a PrivateKey. Take note of this as it will become important later. The client will then use the newly generated PrivateKey and the Challenge to generate a new value. The client places this value in the LoginPassword field of the login message as a response to the challenge issued by the server:

The server can authenticate the client by independently computing the PrivateKey and LoginPassword using the user account password on record, calculating the expected response to the Challenge, and comparing it with the LoginPassword supplied by the client. If the values match, the client has successfully authenticated itself.

ZDI-20-268/CVE-2020-8864

This authentication bypass vulnerability is caused by the incorrect use of strncmp() to compare the server-calculated LoginPassword with the LoginPassword presented by the client. Below is the control flow diagram of the vulnerable function:

Figure 1 - Control flow diagram of the vulnerable function for ZDI-20-268

In essence, the above segment of the control flow graph describes the following common vulnerable code pattern:

When attacker_provided_password is an empty string, strlen() returns 0. Then, since strncmp() is called with a length parameter of 0, it does not compare any characters at all. Instead it returns a value of 0, indicating equality. In ZDI-20-268, if an attacker provides an empty LoginPassword value, strncmp() will return 0 and follow the code path for successful authentication.

ZDI-20-267/CVE-2020-8863

The title for this vulnerability reads: 

D-Link Multiple Routers HNAP PrivateLogin Incorrect Implementation of Authentication Algorithm Authentication Bypass Vulnerability

The word “PrivateLogin” arouses curiosity. To whom is this Login private? Let’s take a look at how the router processes an HNAP login request to find out how this PrivateLogin backdoor was implemented in a few lines of code.

When authenticating through HNAP, the server normally generates the PrivateKey based on the user’s password. However, something different happens when an attacker provides an additional <PrivateLogin> element in the Login request. The server instead generates the PrivateKey with the value of the Username element instead of the user password. Below is an example of an authentication-bypassing request:

Below is Ghidra’s decompiler output of the function responsible for generating the authentication challenge values provided by the researcher:  

At line 31, the contents of the PrivateLogin element, if present, is extracted from the login request and stored in the PrivateLogin variable. The Username element is also extracted and stored in the Username variable a few lines above.

The PrivateLogin variable is later used at line 58. The if condition can be understood more easily if we apply the De Morgan’s Law. The condition checks that the PrivateLogin element is present and further ensures the PrivateLogin element contains the string “Username”. If both conditions are satisfied, the value of the Username element (i.e. “Admin”) will be copied into the Password variable with strncpy(). This differs from the normal code path where the router would call GetPassword() to read the admin password from NVRAM.

At line 65, the now tainted Password is passed into GenPrivateKey() for the generation the Challenge, Cookie, and PublicKey values for the authentication challenge. As a result, an attacker now knows all the all the required values to recreate the PrivateKey and respond to the authentication challenge without knowing the real admin password to the router.

Conclusion

How did this backdoor get into the product? Why did the developer write these lines of codes? Was it part of the manufacturer’s original design? Or were these lines of code written by a rogue employee? Why didn’t a code review catch that? Was there any code review process? Was ZDI-20-268 also coded intentionally as an alternate means of maintaining a foothold? We don’t have answers to any of the above questions. However, we know for certain the existence of such vulnerabilities in the firmware is symptomatic of larger problems and warrants more action on the vendor’s part than merely shipping a patch.

Perhaps it is time to consider consumer-grade routers disposable items. The hardware has a tendency to last longer than the support lifecycle. We also saw this earlier this year when NETGEAR decided not to patch 45 models of home routers, instead choosing to designate them as out-of-support. Those shopping for new routers should consider how many years of manufacturer support will be available when making their purchasing decisions. It will certainly help ensure fewer security-related headaches in the future.

You can find me on Twitter @TrendyTofu, and follow the team for the latest in exploit techniques and security patches.