elibaum.com

why is ssh exposed on my router? (part 1)

28 May 2021

Update 31 May 2021: after all that, it turns out SSH is not actually exposed to the internet. The connection was only being allowed because I was on the network; I thought I had tried this while connected to a VPN but maybe I had set that up incorrectly. Anyway… less concerned about this now.

I recently ran an nmap scan on my router, a TP-Link Archer AX50. From the LAN port, nothing looked amiss, but scanning my external IP address gave some interesting results:

$ nmap -Pn X.X.X.X
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-21 18:46 EDT
Nmap scan report for X.X.X.X
Host is up (0.0099s latency).
Not shown: 952 filtered ports, 46 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
53/tcp open  domain

Nmap done: 1 IP address (1 host up) scanned in 2048.37 seconds

(ssh actually also showed up on the LAN IP but I didn’t think much of it.)

At first I thought this was some computer on my local network, but no, I’m not forwarding port 22. It looks like the router itself does actually expose SSH to the internet… concerning. I tried a few usernames (root, user, admin, etc.) with my WiFi admin password without any luck.

Looking through the firmware

Luckily, TP-link makes the firmware available for download! As of this writing, my router is running version 200708. My idea was to either figure out how to SSH into the router, or see if I could identify any vulnerabilities.

Inside the firmware zip is a binary (as well as the license document, which helpfully reminds the user that GPL source is available… I’ll look into this later):

$ mv ax50v1_intel-up-ver1-0-9-P1\[20200708-rel55037\]_signed.bin firmware1.0.9.bin
$ ls
GPLeLicenseeTerms.pdf
How to upgrade TP-LINK Wireless AC Router(New VI).pdf
firmware1.0.9.bin
$ file firmware1.0.9.bin 
firmware1.0.9.bin: data
$ binwalk firmware1.0.9.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
4519          0x11A7          uImage header, header size: 64 bytes, header CRC: 0xB7455DE6, created: 2020-07-08 07:18:29, image size: 29654552 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x506ACDE2, OS: Linux, CPU: MIPS, image type: Multi-File Image, compression type: none, image name: "TP-Link Totalimage"
4591          0x11EF          uImage header, header size: 64 bytes, header CRC: 0xF85FFD41, created: 2020-07-08 07:18:28, image size: 150864 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x888C4C94, OS: Linux, CPU: MIPS, compression type: lzma, image name: "U-Boot Img"
39107         0x98C3          CRC32 polynomial table, little endian
51855         0xCA8F          CRC32 polynomial table, little endian
55855         0xDA2F          uImage header, header size: 64 bytes, header CRC: 0x908FBBD4, created: 2020-07-08 05:01:29, image size: 99600 bytes, Data Address: 0xA0400000, Entry Point: 0xA0400000, data CRC: 0x38F8E3FA, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "u-boot image"
55919         0xDA6F          LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 343580 bytes
155519        0x25F7F         uImage header, header size: 64 bytes, header CRC: 0xCD4DD048, created: 2020-07-08 07:18:29, image size: 131072 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x6C65B573, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "gphyfw"
155583        0x25FBF         uImage header, header size: 64 bytes, header CRC: 0xE76A1040, created: 2020-07-08 05:01:39, image size: 29176 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x6D88B827, OS: Linux, CPU: MIPS, image type: Multi-File Image, compression type: lzma, image name: "GPHY Firmware"
155655        0x26007         LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 65664 bytes
286655        0x45FBF         uImage header, header size: 64 bytes, header CRC: 0x846655C1, created: 2020-07-08 07:17:38, image size: 26226688 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x78134F7C, OS: Linux, CPU: MIPS, image type: Filesystem Image, compression type: lzma, image name: "LTQCPE RootFS"
286719        0x45FFF         Squashfs filesystem, little endian, version 4.0, compression:xz, size: 26223172 bytes, 5462 inodes, blocksize: 131072 bytes, created: 2020-07-08 07:17:37
26513407      0x1948FFF       uImage header, header size: 64 bytes, header CRC: 0xC71E56C7, created: 2020-07-08 05:17:29, image size: 3145664 bytes, Data Address: 0xA0020000, Entry Point: 0xA002DF00, data CRC: 0xD79B6C75, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "MIPS LTQCPE Linux-3.10.104"
26513471      0x194903F       LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 9232128 bytes

That’s promising! Time to extract:

$ binwalk -e firmware1.0.9.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
(same as above...)

WARNING: Extractor.execute failed to run external extractor 'unsquashfs -d 'squashfs-root' '%e'': [Errno 2] No such file or directory: 'unsquashfs', 'unsquashfs -d 'squashfs-root' '%e'' might not be installed correctly

WARNING: Extractor.execute failed to run external extractor 'sasquatch -p 1 -le -d 'squashfs-root' '%e'': [Errno 2] No such file or directory: 'sasquatch', 'sasquatch -p 1 -le -d 'squashfs-root' '%e'' might not be installed correctly

WARNING: Extractor.execute failed to run external extractor 'sasquatch -p 1 -be -d 'squashfs-root' '%e'': [Errno 2] No such file or directory: 'sasquatch', 'sasquatch -p 1 -be -d 'squashfs-root' '%e'' might not be installed correctly
286719        0x45FFF         Squashfs filesystem, little endian, version 4.0, compression:xz, size: 26223172 bytes, 5462 inodes, blocksize: 131072 bytes, created: 2020-07-08 07:17:37
26513407      0x1948FFF       uImage header, header size: 64 bytes, header CRC: 0xC71E56C7, created: 2020-07-08 05:17:29, image size: 3145664 bytes, Data Address: 0xA0020000, Entry Point: 0xA002DF00, data CRC: 0xD79B6C75, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "MIPS LTQCPE Linux-3.10.104"
26513471      0x194903F       LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 9232128 bytes

oops! macOS doesn’t like it. Trying on linux.

$ ls
194903F  26007  45FFF.squashfs  DA6F  squashfs-root
$ file *
194903F:        data
26007:          u-boot legacy uImage, GRX500 V1.1 GPHY BE, Linux/MIPS, Firmware Image (Not compressed), 65536 bytes, Wed Jul  8 05:01:39 2020, Load Address: 0x00000000, Entry Point: 0x00000000, Header CRC: 0x3777347D, Data CRC: 0x8E18E096
45FFF.squashfs: Squashfs filesystem, little endian, version 1024.0, compressed, 4909644877756628992 bytes, 1444216832 inodes, blocksize: 512 bytes, created: Thu Apr 12 07:38:07 1979
DA6F:           data
squashfs-root:  directory
$ ls squashfs-root
bin  data  dev  etc  lib  mnt  opt  overlay  proc  rom  root  sbin  sys  tmp  usr  var  www

Exciting. Clearly the Linux image was inside the squashfs, and 26007 is just the bootloader. What’s 194903F?

$ binwalk -e 194903F 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
1040          0x410           device tree image (dtb)
7389484       0x70C12C        Linux kernel version 3.10.1
7464308       0x71E574        gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
7804252       0x77155C        xz compressed data
7853860       0x77D724        Unix path: /lib/firmware/updates/3.10.104
7930028       0x7900AC        Unix path: /dev/switch_api/0
7935256       0x791518        Unix path: /dev/switch_api/1
7961493       0x797B95        eCos RTOS string reference: "ecostat"
7961522       0x797BB2        eCos RTOS string reference: "ecostat -c pic0=0,pic1=1:EXL,K,S,U,IE 1"
8030833       0x7A8A71        PARity archive data - file number 20548
8215729       0x7D5CB1        Copyright string: "Copyright (c) 2006-2007 BalaBit IT Ltd."
8238988       0x7DB78C        Neighborly text, "NeighborSolicitsp6InMsgs"
8239008       0x7DB7A0        Neighborly text, "NeighborAdvertisementsrs"
8242790       0x7DC666        Neighborly text, "neighbor %.2x%.2x.%pM lost rename link %s to %s"
8298302       0x7E9F3E        Unix path: /var/run/rpcbind.sock
8639872       0x83D580        CRC32 polynomial table, little endian
9220320       0x8CB0E0        ASCII cpio archive (SVR4 with no CRC), file name: "dev", file name length: "0x00000004", file size: "0x00000000"
9220436       0x8CB154        ASCII cpio archive (SVR4 with no CRC), file name: "dev/console", file name length: "0x0000000C", file size: "0x00000000"
9220560       0x8CB1D0        ASCII cpio archive (SVR4 with no CRC), file name: "root", file name length: "0x00000005", file size: "0x00000000"
9220676       0x8CB244        ASCII cpio archive (SVR4 with no CRC), file name: "TRAILER!!!", file name length: "0x0000000B", file size: "0x00000000"

Is this the Linux kernel, maybe? The extraction resulted in:

$ ls -R
.:
71E574  77155C.xz  8CB0E0.cpio  console  cpio-root  dev  root

./cpio-root:
dev  root

./cpio-root/dev:

./cpio-root/root:

./dev:

./root:

…but the cpio archive only contains empty files, and the xz archive is apparently corrupted. weird???

Going back up to the main level, there’s not much else to see. DA6F is just a CRC polynomial table.

$ binwalk DA6F 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
211536        0x33A50         CRC32 polynomial table, little endian
213632        0x34280         CRC32 polynomial table, little endian

So that pretty much leaves me with the squashfs image. What can I find? etc is probably a good place to start, at least to find human-readable stuff.

$ ls etc/
avahi             dropbear               init.d            passwd             samba_performance_turning
banner            easy-rsa               inittab           powermanager.conf  sbin
certificate       filesystems            iproute2          ppa.conf           services
cloud_config.cfg  firewall.user          ltqpreinit        ppp                shadow
cloud_https.cfg   flag_2g                mcast.conf        pptpd.conf         shells
config            flag_5g                modules.d         preinit            ssl
config.sh         fstab                  mtab              preinit.d          sysctl.conf
conntrackd        functions.sh           netatalk          profile            sysupgrade.conf
crontabs          group                  nixio             proftpd            tsched.conf.d
device_table.txt  hosts                  openvpn           protocols          tsched.d
dhcp6cctlkey      hotplug                openwrt_release   rc.common          TZ
dhcp6s.conf       hotplug2-common.rules  openwrt_version   rc.d               uci-defaults
dhcp6sctlkey      hotplug2-init.rules    opkg.conf         rc.local           udev
diag.sh           hotplug2.rules         oui               resolv.conf        wave_components.ver
dnsmasq.conf      hotplug.d              partition_config  samba              webpage_time

welp, looks like TP-link is just running a repackaged version of OpenWRT. Guess I shouldn’t be surprised. Oh, and dropbear - there’s my ssh server!

$ cat openwrt_release 
DISTRIB_ID="OpenWrt"
DISTRIB_RELEASE="Attitude Adjustment"
DISTRIB_REVISION="unknown"
DISTRIB_CODENAME="attitude_adjustment"
DISTRIB_TARGET="model_intel_grx350/generic"
DISTRIB_DESCRIPTION="OpenWrt Attitude Adjustment 12.09-rc1"

I’m not super familiar with OpenWRT, but after a bit of digging, I realized that this version is from… 2013. That’s insane! I’m not seeing that target platform on OpenWRT’s website, but I bet I can find it somewhere if I go down that route.

As for my original question,

$ cat shadow 
root:x:0:0:99999:7:::
daemon:*:0:0:99999:7:::
ftp:*:0:0:99999:7:::
network:*:0:0:99999:7:::
nobody:*:0:0:99999:7:::
admin:x:0:0:99999:7:::
guest::0:0:99999:7:::
$ cat passwd 
root:x:0:0:root:/root:/bin/ash
daemon:*:1:1:daemon:/var:/bin/false
ftp:*:55:55:ftp:/home/ftp:/bin/false
network:*:101:101:network:/var:/bin/false
nobody:*:65534:65534:nobody:/var:/bin/false
admin:x:1000:0:admin:/var:/bin/false
guest::2000:65534:guest:/var:/bin/false

Only root is given a shell, but no accounts have a valid password. So there will be no logging in directly to the router.

I dug around the file system for a while longer, and discovered:

Possible Vulnerabilities

I headed over to the CVE database to see if anything popped up for old versions of dropbear. Indeed, there are a few; notably:

The link to the revision fixing 7406 above no longer works, but this one works as of May 2021. It looks like the issue (line diff 2.43) boils down to calling printf with a variable format string! The svr_dropbear_exit function takes a format string and variable arguments that it will append to its own log message; however, that log message also includes the (attacker supplied) username we’re trying to authenticate. If the username is allowed to contain %, then we’re vulnerable to all sorts of printf nastiness, such as parameter overflows and %n memory-writes.

Questions at this point, however:

Other Ideas

This was just an initial investigation, so I may return later and look into some of these questions more in depth.