TPLink TLWR740n Router Remote Code Execution

Introduction

In October of 2017 we disclosed multiple vulnerabilities in TP-Link’s WR940n router that occurred due to multiple code paths calling strcpy on user controllable unsanitised input (CVE-2017-13772)  The httpd binary responsible for these vulnerabilities contained patterns of code that looked similar to the following:

At the time of disclosure, there were around 7000 of these devices accessible from the Internet.

N.B – The decision to go public on this one was tricky as we’d already released a Proof-of-Concept in our previous blog post which works for this model too, unknowingly to us at the time. Due to the sheer number of affected devices around the globe we expected TP-Link to issue a fix very quickly, especially as they had already fixed the EXACT same issue in a previous device. Sadly this wasn’t the case. We can’t be sure if this is currently being exploited in the wild, however it is likely.

Until a fix has eventually been released by TP-Link (No idea when this will be..) ensure your router is using a strong password and you’ve changed default credentials!

Code Reuse is Vulnerability Reuse

Code reuse is a huge problem within the IoT industry. In most cases, what we generally see is a company who sells devices with poor security to vendors who then brand them and sell them on. Tracking the original manufacturer can be quite difficult and, in our experience, getting such vulnerabilities patched is even harder. A good example of this kind of code reuse was discovered in 2017 by Pierre Kim, who found roughly 185,000 devices which all shared the same vulnerabilities.

When we originally found the vulnerabilities in TP-Link’s WR940n router, our first thought was  “how has no one else discovered this yet”, it’s hardly a difficult bug to spot. Our second thought was possibly more naïve, “there’s no way we’ll find something like this again”.

Whilst making various Shodan searches for new targets to do some research on, we came across TP-Link’s WR740n router, which is a much older model and hasn’t had a firmware update for a few years for some versions.

We decided to grab the current firmware for hardware version 5 UK (https://static.tp-link.com/res/down/soft/TL-WR740N(UN)_V5_160715.zip) and have a look around for some low hanging fruit. The extraction of the filesystem is trivial, and is documented in our previous blog post, so we will skip right into reversing the httpd binary.

Straight away we saw some functions that looked familiar (if not identical), so went for a complete chance and just looked for a known vulnerable GET parameter “dnsserver2”. Instantly we get a pretty obvious buffer overflow:

This block of code is exactly the same as noted in CVE-2017-13772, in-fact it is the second vulnerable location we decided to generate an exploit for (which you can find in that post).

Finding all Vulnerable Locations

Tracking down every single vulnerable location and checking if they are the same as the WR940n router would be quite tedious, so we decided that it might be fun to use some IDA python scripting instead. Our goal being to identify all vulnerable locations as well as the GET parameters that can be used to trigger them. This is a relatively easy task due to the fact that the vulnerable pattern is so simple – strcpy(httpGetEnv(“some_param”)).

The script is as follows:

import idautils

print "[+] Beginning scan..."

#Locate the address of strcpy
strcpy = LocByName("strcpy")

#Get the cross references to it
xrefsTo = CodeRefsTo(strcpy, 0)

numLocations = 0

#Begin looping over each xref
for ref in xrefsTo:
    current = ref - 0x50 #go back 20 instructions (mips is 4byte aligned). 
    
    #scan forwards, looking for the call to httpGetEnv
    while current < ref:
        current += 4
        op = GetOpnd(current, 1) #get the second operand, looking for la $t9, httpGetEnv.
        if op == "httpGetEnv":
            print "[+] Possible vulnerable code path at: %s" %hex(current)
            numLocations += 1
            tmp = current - 8 #go back 2 instructions
            
            #Now we are looking for the argument to httpGetEnv.
            while tmp <= current + 16:
                if GetOpnd(tmp, 0) == "$a1":
                    #sometimes there’s a la $a1, 0x5c which is redundant, possibly just padding.
                    if "0x" in GetOpnd(tmp, 1):
                        pass
                    else:
                        print "\t Possible http parameter: %s" %GetOpnd(tmp, 1)
                tmp += 4

print "[+] Found %d locations" %numLocations
print "[+] Done!"

Let’s look at what this code is doing, the following diagram shows the sequence of each of the loops on a target code block known to be vulnerable:

Notice how between $t9 being loaded with httpGetEnv and $t9 being called, $a1 has 2 different values copied into it, the value 0x56 is redundant, the actual argument is a pointer to the string “dnsserver1”.

The only other thing to consider with this code is how far we jump back initially, you could of course go back 80 or 100 instructions, however you end up with false positives. We found the optimal value to be 20 instructions.

Running this code within IDA for the WR740n httpd binary gives the following output:

[+] Beginning scan...
[+] Possible vulnerable code path at: 0x449eb0
Possible http parameter: aIpstart
[+] Possible vulnerable code path at: 0x449ee8
Possible http parameter: aIpend
[+] Possible vulnerable code path at: 0x449f90
Possible http parameter: aStaticprefix
[+] Possible vulnerable code path at: 0x44a6fc
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44a734
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x44b0ec
Possible http parameter: (aSta_gw+4)
[+] Possible vulnerable code path at: 0x44b194
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44b1e8
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x44b238
Possible http parameter: aDomain
[+] Possible vulnerable code path at: 0x44b26c
Possible http parameter: aHostname_0
[+] Possible vulnerable code path at: 0x44bb60
Possible http parameter: aUsername
[+] Possible vulnerable code path at: 0x44bb98
Possible http parameter: aPassword_1
[+] Possible vulnerable code path at: 0x44bc18
Possible http parameter: aFixedip
[+] Possible vulnerable code path at: 0x44bd28
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44bd7c
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x44c6f0
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44c744
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x44cd94
Possible http parameter: (aSta_ip+4)
[+] Possible vulnerable code path at: 0x44ce00
Possible http parameter: aGateway
[+] Possible vulnerable code path at: 0x44ceb0
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44cf04
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x456ed8
Possible http parameter: aSave
[+] Possible vulnerable code path at: 0x4624d4
Possible http parameter: aPsksecret
[+] Possible vulnerable code path at: 0x52315c
Possible http parameter: aRemote_addr
[+] Found 24 locations
[+] Done!

And we can then double check we are correct by jumping to any one of these addresses, here we see the vulnerable path at 0x44b328:

The Point

If we now go back to the WR940n httpd binary and run the same script, we get extremely similar results:

[+] Beginning scan...
[+] Possible vulnerable code path at: 0x44bfdc
Possible http parameter: aIpstart
[+] Possible vulnerable code path at: 0x44c014
Possible http parameter: aIpend
[+] Possible vulnerable code path at: 0x44c0bc
Possible http parameter: aStaticprefix
[+] Possible vulnerable code path at: 0x44c85c
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44c894
Possible http parameter: aDnsserver2
<…Snip…>
[+] Possible vulnerable code path at: 0x44f1a4
Possible http parameter: (aSta_ip+4)
[+] Possible vulnerable code path at: 0x44f210
Possible http parameter: aGateway
[+] Possible vulnerable code path at: 0x44f2c0
Possible http parameter: aDnsserver1
[+] Possible vulnerable code path at: 0x44f314
Possible http parameter: aDnsserver2
[+] Possible vulnerable code path at: 0x4596f8
Possible http parameter: aSave
[+] Possible vulnerable code path at: 0x465290
Possible http parameter: aPsksecret
[+] Possible vulnerable code path at: 0x52cdcc
Possible http parameter: aRemote_addr
[+] Found 24 locations
[+] Done!

In fact the only differences we have are the addresses of the vulnerable locations. It should be noted that this does not necessarily mean that each one of these locations is 100% exploitable, but it does give us a good way of focusing our attention.

The main issue here is that we disclosed the vulnerabilities for the WR940n router over 5 months ago and we would assume that someone would have noticed that the 2 devices use extremely similar if not identical software.

Timeline

25/1/18 – Initial contact with description of issue, contact with [email protected]
26/1/18 – Reply from TP-Link asking for more details, sent them the details for CVE-2017-13772 (wr940n model).
1/2/18 – TP_Link inform us they are looking into the issue.
15/2/18 – Request from us for an update.
30/2/18 – Request from us for an update.
26/3/18 – Another request for an update, warning of public disclosure sent.
28/3/18 – Reply from [email protected], inform us they are releasing a patch in the “recent days”.
29/3/18 – [email protected] send us beta firmware to fix the issue.
29/3/18 – Sent a reply to [email protected] to confirm the issue fixed.
9/4/18 – Request for an estimate for when the firmware goes live.
18/4/18 – Another request, another warning of public disclosure sent.
26/4/18 – No reply received, public disclosure of vulnerability.

Credit

Tim Carrington – A member of Fidus’ UK Penetration Testing and Research team.