├── README.md └── pyBuster.py /README.md: -------------------------------------------------------------------------------- 1 | # pyBuster 2 | 3 | A multi-target URL bruteforcer. Python2 and Python3 compliant. 4 | 5 | Scan a single target: 6 | 7 | `python pyBuster.py -u google.com -w words.txt -v` 8 | 9 | Scan multiple targets (verbose flag removed as its quite noisy): 10 | 11 | `python pyBuster.py -ul hosts.txt -w words.txt` 12 | 13 | Select ports to scan: 14 | 15 | `python pyBuster.py -u hosts.txt -w words.txt -v -p 80,443,8443` 16 | 17 | Change the number of threads: 18 | 19 | `python pyBuster.py -u hosts.txt -w words.txt -v -th 20 -tw 2` 20 | 21 | Hosts and words file should be one entry per line. 22 | 23 | Example host list: 24 | 25 | ``` 26 | www.test1.com 27 | www.test2.com 28 | 192.168.1.2 29 | 192.168.2.0/24 30 | ``` 31 | 32 | Example word list: 33 | 34 | ``` 35 | backup.zip 36 | test.html 37 | login.php 38 | ``` 39 | 40 | 41 | Help: 42 | 43 | ``` 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | -u HOST, --host HOST Single host to scan (default: None) 47 | -ul HOSTLIST, --hostlist HOSTLIST 48 | File containing multiple hosts to scan (default: None) 49 | -w WORDLIST, --wordlist WORDLIST 50 | File containing words/paths to test (default: None) 51 | -p PORTS, --ports PORTS 52 | Comma separated list of ports to scan (default: 53 | 80,443) 54 | -c CODES, --codes CODES 55 | Success HTTP codes comma separated (default: 200) 56 | -r REDIRECT, --redirect REDIRECT 57 | Follow redirects (default: False) 58 | -th HOSTTHREADS, --hostthreads HOSTTHREADS 59 | Number of concurrent hosts (default: 10) 60 | -tw WORDTHREADS, --wordthreads WORDTHREADS 61 | Number of threads per host (default: 3) 62 | -v, --verbose Show more information (default: None) 63 | ``` 64 | -------------------------------------------------------------------------------- /pyBuster.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import sys 4 | import struct 5 | import time 6 | import datetime 7 | import socket 8 | import requests 9 | import re 10 | if sys.version[0] == '2': 11 | from Queue import Queue 12 | else: 13 | from queue import Queue 14 | from threading import Thread 15 | import argparse 16 | import urllib3 17 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 18 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 20 | 21 | # Setup args 22 | parser = argparse.ArgumentParser(description='pyBuster a multi-target URL bruteforcer',formatter_class=argparse.ArgumentDefaultsHelpFormatter) 23 | parser.add_argument('-u','--host', help='Single host to scan') 24 | parser.add_argument('-ul','--hostlist', help='File containing multiple hosts to scan') 25 | parser.add_argument('-w','--wordlist', help='File containing words/paths to test', required=True) 26 | parser.add_argument('-p','--ports', help='Comma separated list of ports to scan', default='80,443') 27 | parser.add_argument('-c','--codes', help='Success HTTP codes comma separated', default='200') 28 | parser.add_argument('-r','--redirect', help='Follow redirects', default=False) 29 | parser.add_argument('-th','--hostthreads', help='Number of concurrent hosts', default=10) 30 | parser.add_argument('-tw','--wordthreads', help='Number of threads per host', default=3) 31 | parser.add_argument('-v','--verbose', help='Show more information', action='count') 32 | args = vars(parser.parse_args()) 33 | 34 | 35 | def convert_cidr(cidr): 36 | (ip, mask) = cidr.split('/') 37 | mask = int(mask) 38 | host_bits = 32 - mask 39 | i = struct.unpack('>I', socket.inet_aton(ip))[0] # note the endianness 40 | start = (i >> host_bits) << host_bits # clear the host bits 41 | end = (start | ((1 << host_bits) - 1))+1 42 | 43 | ip_list = [] 44 | for i in range(start, end): 45 | ip_list.append(socket.inet_ntoa(struct.pack('>I',i))) 46 | return ip_list 47 | 48 | # Load hosts and words 49 | try: 50 | temp_host_list = [] 51 | host_list = [] 52 | 53 | if args["host"]: 54 | temp_host_list.append(args["host"]) 55 | elif args["hostlist"]: 56 | with open(args["hostlist"]) as file: 57 | temp_host_list = file.read().strip().split('\n') 58 | 59 | for t in temp_host_list: 60 | if "/" in t and t.split("/")[1].isdigit(): 61 | for ip in convert_cidr(t): 62 | host_list.append(ip) 63 | else: 64 | host_list.append(t) 65 | 66 | path_list = [] 67 | with open(args["wordlist"]) as file: 68 | path_list = file.read().strip().split('\n') 69 | print (datetime.datetime.now()) 70 | print ("[*] Hosts: " + str(len(host_list)) ) 71 | print ("[*] Paths: " + str(len(path_list)) ) 72 | except IOError: 73 | print ("[!] Error: Coudn't read input file") 74 | sys.exit(1) 75 | 76 | # Parse additional args 77 | ports = [int(x) for x in args["ports"].split(",")] 78 | codes = [int(x) for x in args["codes"].split(",")] 79 | redirect = bool(args["redirect"]) 80 | host_threads = int(args["hostthreads"]) 81 | word_threads = int(args["wordthreads"]) 82 | verbose = args["verbose"] 83 | 84 | # Check if port is open 85 | def check_port(host,port): 86 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 87 | s.settimeout(1) 88 | try: 89 | status = s.connect_ex((host, port)) 90 | s.close() 91 | if status == 0: 92 | if verbose: print ("[*] Port open: " + str(host) + " - " + str(port)) 93 | return check_path(host,port,"test") 94 | else: 95 | if verbose: print ("[!] Error: Cannot reach " + str(host)) 96 | return 0 97 | except socket.error: 98 | if verbose: print ("[!] Error: Cannot reach " + str(host)) 99 | return 0 100 | 101 | 102 | 103 | # Check if path exists 104 | def check_path(host,port,path): 105 | try: 106 | url = str(host) + ":" + str(port) + '/' + str(path) 107 | if "443" in str(port): 108 | url = 'https://' + str(url) 109 | else: 110 | url = 'http://' + str(url) 111 | 112 | response = requests.get(url, verify=False, timeout=2, allow_redirects=redirect) 113 | 114 | if response.status_code in codes: 115 | title = re.search('(?<=).+?(?=)', response.text, re.DOTALL) 116 | if title: 117 | title = title.group().strip() 118 | else: 119 | title = "" 120 | print (str(response.status_code) + "," + str(len(response.content)) + "," + url + "," + title) 121 | elif verbose: 122 | print (str(response.status_code) + "," + str(len(response.content)) + "," + url) 123 | return 1 124 | 125 | except Exception as e: 126 | if verbose: print ("[!] Error: Timeout or unexpected response from " + str(host) + ":" + str(port) + '/' + str(path) ) 127 | return 0 128 | 129 | # Query a single path 130 | def path_worker(host,port,pathq): 131 | while pathq.qsize(): 132 | path = pathq.get() 133 | check_path(host,port,path) 134 | pathq.task_done() 135 | 136 | 137 | # Create threads for each host 138 | def host_worker(hostq): 139 | 140 | while hostq.qsize(): 141 | host = hostq.get() 142 | open_ports = [] 143 | for port in ports: 144 | if check_port(host,port): 145 | open_ports.append(port) 146 | 147 | for port in open_ports: 148 | 149 | pathq = Queue() 150 | 151 | for path in path_list: 152 | pathq.put(path) 153 | 154 | for i in range(word_threads): 155 | t = Thread(target=path_worker,args=(host,port,pathq)) 156 | t.daemon = True 157 | t.start() 158 | 159 | pathq.join() 160 | 161 | if verbose: print ("[*] Host complete: " + str(host) + ":" + str(port)) 162 | 163 | hostq.task_done() 164 | 165 | 166 | def main(): 167 | 168 | try: 169 | hostq = Queue() 170 | threads = [] 171 | 172 | for host in host_list: 173 | hostq.put(host) 174 | 175 | for i in range(host_threads): 176 | t = Thread(target=host_worker,args=(hostq,)) 177 | t.daemon = True 178 | t.start() 179 | threads.append(t) 180 | 181 | while True: 182 | print ("Complete: " + str(len(host_list) - hostq.qsize()) + "/" + str(len(host_list))) 183 | i = 0 184 | if all(t.isAlive() == False for t in threads): 185 | break 186 | time.sleep(3) 187 | 188 | except (KeyboardInterrupt, SystemExit): 189 | print ('\n[!] Stopping scan.\n') 190 | 191 | 192 | main() 193 | --------------------------------------------------------------------------------