1 | | - | #!/usr/bin/python3 |
2 | | - | |
3 | | - | # I don't believe in license. |
4 | | - | # You can do whatever you want with this program. |
5 | | - | |
6 | | - | import os |
7 | | - | import sys |
8 | | - | import re |
9 | | - | import time |
10 | | - | import copy |
11 | | - | import random |
12 | | - | import argparse |
13 | | - | import requests |
14 | | - | import urllib.parse |
15 | | - | from functools import partial |
16 | | - | from threading import Thread |
17 | | - | from queue import Queue |
18 | | - | from multiprocessing.dummy import Pool |
19 | | - | from colored import fg, bg, attr |
20 | | - | |
21 | | - | MAX_EXCEPTION = 100 |
22 | | - | MAX_VULNERABLE = 100 |
23 | | - | |
24 | | - | # disable "InsecureRequestWarning: Unverified HTTPS request is being made." |
25 | | - | from requests.packages.urllib3.exceptions import InsecureRequestWarning |
26 | | - | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) |
27 | | - | |
28 | | - | t_history = [] |
29 | | - | |
30 | | - | def banner(): |
31 | | - | print(""" |
32 | | - | _ |
33 | | - | _ __ ___ _ __ _ _ | |_ _ __ _ _ |
34 | | - | | '_ ` _ \ | '_ \ | | | | | __| | '_ \ | | | | |
35 | | - | | | | | | | | |_) | | |_| | | |_ _ | |_) | | |_| | |
36 | | - | |_| |_| |_| | .__/ \__,_| \__| (_) | .__/ \__, | |
37 | | - | |_| |_| |___/ |
38 | | - | |
39 | | - | by @gwendallecoguic |
40 | | - | |
41 | | - | """) |
42 | | - | pass |
43 | | - | |
44 | | - | |
45 | | - | def rebuiltQuery( t_params ): |
46 | | - | query = '' |
47 | | - | for pname,t_values in t_params.items(): |
48 | | - | for k in range(len(t_values)): |
49 | | - | query = query + pname+'='+t_values[k] + '&' |
50 | | - | return query.strip('&') |
51 | | - | |
52 | | - | |
53 | | - | def _parse_qs( query ): |
54 | | - | t_params = {} |
55 | | - | tmptab = query.split('&') |
56 | | - | |
57 | | - | for param in tmptab: |
58 | | - | t_param = param.split('=') |
59 | | - | pname = t_param[0] |
60 | | - | if not pname in t_params: |
61 | | - | t_params[pname] = [] |
62 | | - | pvalue = '' if len(t_param) < 2 else t_param[1] |
63 | | - | t_params[pname].append( pvalue ) |
64 | | - | |
65 | | - | return t_params |
66 | | - | |
67 | | - | |
68 | | - | def testPath( t_urlparse ): |
69 | | - | path = '' |
70 | | - | t_path = ['/'] + t_urlparse.path.split('/') |
71 | | - | # print(t_urlparse) |
72 | | - | # print(t_path) |
73 | | - | # return |
74 | | - | for dir in t_path: |
75 | | - | # print("> "+dir) |
76 | | - | if len(dir): |
77 | | - | # print(dir) |
78 | | - | if not dir == '/': |
79 | | - | path = path + dir + '/' |
80 | | - | else: |
81 | | - | path = path + dir |
82 | | - | # else: |
83 | | - | # path = path.replace('//','/') |
84 | | - | |
85 | | - | if '.' in dir and dir == t_path[len(t_path)-1]: |
86 | | - | path = path.rstrip('/') |
87 | | - | |
88 | | - | new_value = path |
89 | | - | # new_value = path + '/' |
90 | | - | # new_value = new_value.replace('//','/') |
91 | | - | t_urlparse = t_urlparse._replace(path=new_value) |
92 | | - | url = urllib.parse.urlunparse(t_urlparse) |
93 | | - | doTest( url ) |
94 | | - | |
95 | | - | |
96 | | - | def testPayload( url, tmp ): |
97 | | - | # print(url) |
98 | | - | t_urlparse = urllib.parse.urlparse( url ) |
99 | | - | |
100 | | - | testPath( t_urlparse ) |
101 | | - | |
102 | | - | |
103 | | - | def testURL( url ): |
104 | | - | time.sleep( 0.01 ) |
105 | | - | t_multiproc['n_current'] = t_multiproc['n_current'] + 1 |
106 | | - | |
107 | | - | if _verbose <= 1: |
108 | | - | sys.stdout.write( 'progress: %d/%d\r' % (t_multiproc['n_current'],t_multiproc['n_total']) ) |
109 | | - | # t_multiproc['n_current'] = t_multiproc['n_current'] + 1 |
110 | | - | |
111 | | - | # testPayload(url,0) |
112 | | - | pool = Pool( 1 ) |
113 | | - | pool.map( partial(testPayload,url), 'dummy' ) |
114 | | - | pool.close() |
115 | | - | pool.join() |
116 | | - | |
117 | | - | |
118 | | - | def doTest( url, method='GET', post_params='' ): |
119 | | - | |
120 | | - | # with open('generated_urls', 'a+') as fp: |
121 | | - | # fp.write(url+"\n") |
122 | | - | # return |
123 | | - | |
124 | | - | # t_realdotest.append( [url,method,post_params] ) |
125 | | - | realDoTest( [url,method,post_params] ); |
126 | | - | return |
127 | | - | |
128 | | - | |
129 | | - | def realDoTest( t_params ): |
130 | | - | |
131 | | - | vuln = '-' |
132 | | - | status_code = 0 |
133 | | - | content_type = '-' |
134 | | - | method_found = [] |
135 | | - | url = t_params[0] |
136 | | - | # print(url) |
137 | | - | method = t_params[1] |
138 | | - | post_params = t_params[2] |
139 | | - | |
140 | | - | if url in t_history: |
141 | | - | return |
142 | | - | |
143 | | - | t_history.append(url) |
144 | | - | |
145 | | - | if _verbose <= 1: |
146 | | - | sys.stdout.write( 'progress: %d/%d\r' % (t_multiproc['n_current'],t_multiproc['n_total']) ) |
147 | | - | # t_multiproc['n_current'] = t_multiproc['n_current'] + 1 |
148 | | - | |
149 | | - | t_urlparse = urllib.parse.urlparse(url) |
150 | | - | u = t_urlparse.scheme + '_' + t_urlparse.netloc |
151 | | - | |
152 | | - | # if not u in t_exceptions: |
153 | | - | # t_exceptions[u] = 0 |
154 | | - | # if t_exceptions[u] >= MAX_EXCEPTION: |
155 | | - | # if _verbose >= 3 and _verbose < 4: |
156 | | - | # print("skip too many exceptions %s" % t_urlparse.netloc) |
157 | | - | # return |
158 | | - | |
159 | | - | # if not u in t_vulnerable: |
160 | | - | # t_vulnerable[u] = 0 |
161 | | - | # if t_vulnerable[u] >= MAX_VULNERABLE: |
162 | | - | # if _verbose >= 3 and _verbose < 4: |
163 | | - | # print("skip already vulnerable %s" % t_urlparse.netloc) |
164 | | - | # return |
165 | | - | |
166 | | - | try: |
167 | | - | r = requests.request( 'OPTIONS', url, data=post_params, headers=t_custom_headers, timeout=5, verify=False ) |
168 | | - | except Exception as e: |
169 | | - | # t_exceptions[u] = t_exceptions[u] + 1 |
170 | | - | if _verbose >= 3 and _verbose < 4: |
171 | | - | sys.stdout.write( "%s[-] error occurred: %s%s\n" % (fg('red'),e,attr(0)) ) |
172 | | - | return |
173 | | - | |
174 | | - | status_code = r.status_code |
175 | | - | |
176 | | - | # print(r.headers) |
177 | | - | if 'Content-Type' in r.headers: |
178 | | - | content_type = r.headers['Content-Type'] |
179 | | - | |
180 | | - | if 'Allow' in r.headers and 'PUT' in r.headers['Allow']: |
181 | | - | r_fail = False |
182 | | - | method_found.append('PUT') |
183 | | - | post_params = 'test=test' |
184 | | - | try: |
185 | | - | r_put = requests.request( 'PUT', url, data=post_params, headers=t_custom_headers, timeout=5, verify=False ) |
186 | | - | except Exception as e: |
187 | | - | # t_exceptions[u] = t_exceptions[u] + 1 |
188 | | - | r_fail = True |
189 | | - | if _verbose >= 3 and _verbose < 4: |
190 | | - | sys.stdout.write( "%s[-] error occurred: %s%s\n" % (fg('red'),e,attr(0)) ) |
191 | | - | |
192 | | - | if not r_fail: |
193 | | - | # print(r_put.headers) |
194 | | - | # print(r_put.status_code) |
195 | | - | status_code = r_put.status_code |
196 | | - | |
197 | | - | if 'Content-Type' in r_put.headers: |
198 | | - | content_type = r_put.headers['Content-Type'] |
199 | | - | else: |
200 | | - | content_type = '-' |
201 | | - | |
202 | | - | if r_put.status_code >= 200 and r_put.status_code < 300: |
203 | | - | vuln = 'VULNERABLE' |
204 | | - | |
205 | | - | if 'Allow' in r.headers and 'DELETE' in r.headers['Allow']: |
206 | | - | method_found.append('DELETE') |
207 | | - | |
208 | | - | output = '%s\t\tC=%d\t\tT=%s\t\tM=%s\t\tV=%s\n' % (url,status_code,content_type,','.join(method_found),vuln) |
209 | | - | |
210 | | - | fp = open( t_multiproc['f_output'], 'a+' ) |
211 | | - | fp.write( output ) |
212 | | - | fp.close() |
213 | | - | |
214 | | - | if vuln == 'VULNERABLE' or (_verbose >= 2 and _verbose < 4): |
215 | | - | if vuln == 'VULNERABLE': |
216 | | - | sys.stdout.write( '%s%s%s' % (fg('light_red'),output,attr(0)) ) |
217 | | - | else: |
218 | | - | if len(method_found): |
219 | | - | sys.stdout.write( '%s%s%s' % (fg('light_yellow'),output,attr(0)) ) |
220 | | - | else: |
221 | | - | sys.stdout.write( output ) |
222 | | - | |
223 | | - | |
224 | | - | parser = argparse.ArgumentParser() |
225 | | - | parser.add_argument( "-a","--path",help="set paths list" ) |
226 | | - | parser.add_argument( "-d","--header",help="custom headers, example: cookie1=value1;cookie2=value2...", action="append" ) |
227 | | - | parser.add_argument( "-o","--hosts",help="set host list (required or -u)" ) |
228 | | - | # parser.add_argument( "-r","--redirect",help="follow redirection" ) |
229 | | - | parser.add_argument( "-s","--scheme",help="scheme to use, default=http,https" ) |
230 | | - | parser.add_argument( "-t","--threads",help="threads, default 10" ) |
231 | | - | parser.add_argument( "-u","--urls",help="set url list (required or -o)" ) |
232 | | - | parser.add_argument( "-v","--verbose",help="display output, 0=nothing, 1=only vulnerable, 2=all requests, 3=full debug, 4=only vulnerable,no extra text like banner, default: 1" ) |
233 | | - | parser.parse_args() |
234 | | - | args = parser.parse_args() |
235 | | - | |
236 | | - | if args.verbose: |
237 | | - | _verbose = int(args.verbose) |
238 | | - | else: |
239 | | - | _verbose = 1 |
240 | | - | |
241 | | - | if _verbose < 4: |
242 | | - | banner() |
243 | | - | |
244 | | - | if args.scheme: |
245 | | - | t_scheme = args.scheme.split(',') |
246 | | - | else: |
247 | | - | t_scheme = ['http','https'] |
248 | | - | |
249 | | - | t_custom_headers = {} |
250 | | - | if args.header: |
251 | | - | for header in args.header: |
252 | | - | if ':' in header: |
253 | | - | tmp = header.split(':') |
254 | | - | t_custom_headers[ tmp[0].strip() ] = tmp[1].strip() |
255 | | - | |
256 | | - | t_hosts = [] |
257 | | - | if args.hosts: |
258 | | - | if os.path.isfile(args.hosts): |
259 | | - | fp = open( args.hosts, 'r' ) |
260 | | - | t_hosts = fp.read().strip().split("\n") |
261 | | - | fp.close() |
262 | | - | else: |
263 | | - | t_hosts.append( args.hosts ) |
264 | | - | n_hosts = len(t_hosts) |
265 | | - | if _verbose < 4: |
266 | | - | sys.stdout.write( '%s[+] %d hosts found: %s%s\n' % (fg('green'),n_hosts,args.hosts,attr(0)) ) |
267 | | - | |
268 | | - | t_urls = [] |
269 | | - | if args.urls: |
270 | | - | if os.path.isfile(args.urls): |
271 | | - | fp = open( args.urls, 'r' ) |
272 | | - | t_urls = fp.read().strip().split("\n") |
273 | | - | fp.close() |
274 | | - | else: |
275 | | - | t_urls.append( args.urls ) |
276 | | - | else: |
277 | | - | while True: |
278 | | - | try: |
279 | | - | url = input() |
280 | | - | except EOFError: |
281 | | - | break |
282 | | - | else: |
283 | | - | t_urls.append( url ) |
284 | | - | |
285 | | - | n_urls = len(t_urls) |
286 | | - | if _verbose < 4: |
287 | | - | sys.stdout.write( '%s[+] %d urls found: %s%s\n' % (fg('green'),n_urls,args.urls,attr(0)) ) |
288 | | - | |
289 | | - | if n_hosts == 0 and n_urls == 0: |
290 | | - | parser.error( 'hosts/urls list missing' ) |
291 | | - | |
292 | | - | t_path = [ '' ] |
293 | | - | if args.path: |
294 | | - | if os.path.isfile(args.path): |
295 | | - | fp = open( args.path, 'r' ) |
296 | | - | t_path = fp.read().strip().split("\n") |
297 | | - | fp.close() |
298 | | - | else: |
299 | | - | t_path.append( args.path ) |
300 | | - | n_path = len(t_path) |
301 | | - | if _verbose < 4: |
302 | | - | sys.stdout.write( '%s[+] %d path found: %s%s\n' % (fg('green'),n_path,args.path,attr(0)) ) |
303 | | - | |
304 | | - | if args.threads: |
305 | | - | _threads = int(args.threads) |
306 | | - | else: |
307 | | - | _threads = 10 |
308 | | - | |
309 | | - | t_totest = [] |
310 | | - | u_max_length = 0 |
311 | | - | d_output = os.getcwd()+'/mput' |
312 | | - | f_output = d_output + '/' + 'output' |
313 | | - | if not os.path.isdir(d_output): |
314 | | - | try: |
315 | | - | os.makedirs( d_output ) |
316 | | - | except Exception as e: |
317 | | - | sys.stdout.write( "%s[-] error occurred: %s%s\n" % (fg('red'),e,attr(0)) ) |
318 | | - | exit() |
319 | | - | |
320 | | - | if _verbose < 4: |
321 | | - | sys.stdout.write( '%s[+] options are -> threads:%d, verbose:%d%s\n' % (fg('green'),_threads,_verbose,attr(0)) ) |
322 | | - | |
323 | | - | for scheme in t_scheme: |
324 | | - | for host in t_hosts: |
325 | | - | for path in t_path: |
326 | | - | u = scheme + '://' + host.strip() + path |
327 | | - | t_totest.append( u ) |
328 | | - | l = len(u) |
329 | | - | if l > u_max_length: |
330 | | - | u_max_length = l |
331 | | - | |
332 | | - | for url in t_urls: |
333 | | - | for path in t_path: |
334 | | - | u = url.strip() + path |
335 | | - | t_totest.append( u ) |
336 | | - | l = len(u) |
337 | | - | if l > u_max_length: |
338 | | - | u_max_length = l |
339 | | - | |
340 | | - | n_totest = len(t_totest) |
341 | | - | |
342 | | - | # random.shuffle(t_totest) |
343 | | - | # print("\n".join(t_totest)) |
344 | | - | # exit() |
345 | | - | |
346 | | - | t_realdotest = [] |
347 | | - | t_exceptions = {} |
348 | | - | t_vulnerable = {} |
349 | | - | t_multiproc = { |
350 | | - | 'n_current': 0, |
351 | | - | 'n_total': n_totest, |
352 | | - | 'u_max_length': u_max_length+5, |
353 | | - | 'd_output': d_output, |
354 | | - | 'f_output': f_output, |
355 | | - | } |
356 | | - | |
357 | | - | def doWork(): |
358 | | - | while True: |
359 | | - | url = q.get() |
360 | | - | testURL( url ) |
361 | | - | q.task_done() |
362 | | - | |
363 | | - | q = Queue( _threads*2 ) |
364 | | - | |
365 | | - | for i in range(_threads): |
366 | | - | t = Thread( target=doWork ) |
367 | | - | t.daemon = True |
368 | | - | t.start() |
369 | | - | |
370 | | - | try: |
371 | | - | for url in t_totest: |
372 | | - | q.put( url ) |
373 | | - | q.join() |
374 | | - | except KeyboardInterrupt: |
375 | | - | sys.exit(1) |
376 | | - | |
377 | | - | |