├── README.md └── reconbot /README.md: -------------------------------------------------------------------------------- 1 | # Reconbot 2 | **Reconnaissance and Enumeration bot** 3 | 4 | Reconbot allows you to speed up the process of Reconnaissance and Enumeration by automaticly running different nmap scans, enumeration scripts and brute-force scripts. All logged in their seperate files while displaying some details to be able to start poking quickly. 5 | 6 | 7 | ![pivot screenshot](https://i.ibb.co/dt19g20/image.png) 8 | 9 | 10 | ## Installation 11 | You need 2 python packages for this script: 12 | `sudo pip3 install termcolor bs4` 13 | 14 | Reconbot also uses the following tools to further enumerate a target, it is recommended you install theise (most if not all are installed on kali by default): 15 | * hydra 16 | * gobuster 17 | * nikto 18 | * rpcinfo 19 | * enum4linux 20 | * smbmap 21 | * snmp-check 22 | * onesixtyone 23 | * snmpwalk 24 | 25 | Then you can just run reconbot: 26 | 27 | `sudo ./reconbot 10.10.10.10` 28 | 29 | 30 | 31 | ``` 32 | usage: Reconbot [-h] [-HF] [-p] [-U] [-P] [-w] [--nmaponly] [--quick] [--bruteonly] [--verbose] 33 | 34 | Automated Reconnaissance Bot 35 | 36 | positional arguments: 37 | Target 38 | 39 | optional arguments: 40 | -h, --help show this help message and exit 41 | -HF , --hostfile File containing targets to scan 42 | -p , --ports Ports to scan 43 | -U , --userlist Wordlist to use for usernames when bruteforcing 44 | -P , --passlist Wordlist to use for passwords when bruteforcing 45 | -w , --weblist Wordlist to use for web directory bruteforcing 46 | --nmaponly Only activate the nmap scripts 47 | --quick Only activate quick port scanning 48 | --bruteonly Only activate the hydra bruteforcing scripts 49 | --verbose Display extra output in the reconbot output (things like paths gobuster finds) 50 | 51 | EXAMPLES 52 | 53 | reconbot 1.1.1.1 # Full recon scan of target 54 | reconbot -HF ./hosts # Full recon scan of targets in file 55 | reconbot 1.1.1.1 -p 80,443 # Recon scan of target only scanning port 80 56 | reconbot 1.1.1.1 -w /usr/share/wordlist/webfiles.txt # Full recon scan of target using custom web directory bruteforce wordlist 57 | reconbot 1.1.1.1 -U /wordlist/usernames.txt -P /wordlist/passwords.txt # Full recon scan of target using custom username and password wordlists 58 | reconbot 1.1.1.1 --nmaponly # Nmap only scan of target 59 | reconbot 1.1.1.1 --nmaponly --quick # Nmap only scan of target only using quick scans 60 | reconbot 1.1.1.1 -p 22,21 --bruteonly --quick -P /wordlist/passwords.txt # Bruteforce only scan of target only using quick nmap scans to discover ports 61 | ``` 62 | -------------------------------------------------------------------------------- /reconbot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import re 4 | import os 5 | import subprocess 6 | import threading 7 | import time 8 | import argparse 9 | import string 10 | import random 11 | import requests 12 | import urllib3 13 | from termcolor import colored 14 | from bs4 import BeautifulSoup, Comment 15 | from shutil import which 16 | 17 | class reconbot: 18 | 19 | def __init__(self, args, IP): 20 | urllib3.disable_warnings() 21 | self.defaultvalues = { 22 | "weblist" : "/usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt", 23 | "userlist" : "/usr/share/seclists/Usernames/top-usernames-shortlist.txt", 24 | "passlist" : "/usr/share/seclists/Passwords/darkweb2017-top100.txt" 25 | } 26 | self.scriptvars = { 27 | "host_timeout" : "30m", 28 | "user_agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" 29 | } 30 | self.which_checklist = [ 31 | "hydra", 32 | "gobuster", 33 | "nikto", 34 | "rpcinfo", 35 | "enum4linux", 36 | "smbmap", 37 | "snmp-check", 38 | "onesixtyone", 39 | "snmpwalk" 40 | ] 41 | self.args = args 42 | self.IP = IP 43 | self.scriptthreads = {} 44 | self.checkroot() 45 | self.checkIP() 46 | self.checkargs() 47 | self.services = {} # format = protocl_port : service 48 | self.runningscripts = [] 49 | self.mapped_services = { 50 | "telnet": ["hydra_telnet", "nmap_nse_telnet"], 51 | "ftp": ['nmap_nse_ftp', "hydra_ftp"], 52 | "ssh" : ["hydra_ssh", "nmap_nse_ssh"], 53 | "http" : ['gobuster', 'nikto', 'nmap_nse_http'], 54 | "https" : ['gobuster_https', 'nikto_https', 'nmap_nse_http'], 55 | "ssl/http" : ['gobuster_https', 'nikto_https', 'nmap_nse_http'], 56 | "msrpc" : ['rpcinfo', 'nmap_nse_rpc', 'nmap_nse_msrpc'], 57 | "netbios" : ['enum4linux', 'nmap_nse_smb', 'nmap_nse_nbstat', 'smbmap'], 58 | "microsoft-ds" : ['enum4linux', 'nmap_nse_nbstat', 'nmap_nse_smb', 'smbmap', "hydra_smb"], 59 | "snmp" : ['snmp-check', 'onesixtyone', 'snmpwalk', 'nmap_nse_snmp'], 60 | "smtp" : ['nmap_nse_smtp'], 61 | "ntp" : ['nmap_nse_ntp'], 62 | "dns" : ['nmap_nse_dns'], 63 | "imap": ['nmap_nse_imap'], 64 | "ldap": ['nmap_nse_ldap'], 65 | "mongodb": ['nmap_nse_mongodb'], 66 | "ms-sql": ['nmap_nse_ms-sql'], 67 | "irc": ['nmap_nse_irc'], 68 | "mysql": ['nmap_nse_mysql', "hydra_mysql"], 69 | "ms-wbt-server": ["hydra_rdp"] 70 | } 71 | self.mapped_commands = { 72 | "gobuster" : f"gobuster dir -u http://__IP__:__PORT__/ -a '{self.scriptvars['user_agent']}' -s '200,204,301,302,307,403,500' -x 'txt,html,php,asp,aspx,jsp' -w {self.WEBLIST}", 73 | "gobuster_https" : f"gobuster dir -u https://__IP__:__PORT__/ -a '{self.scriptvars['user_agent']}' -s '200,204,301,302,307,403,500' -x 'txt,html,php,asp,aspx,jsp' -w {self.WEBLIST} -k", 74 | "nikto" : "nikto -ask=no --host http://__IP__:__PORT__/", 75 | "nikto_https" : "nikto -ask=no --host https://__IP__:__PORT__/", 76 | "enum4linux" : "enum4linux -a __IP__ ", 77 | "rpcinfo" : "rpcinfo __IP__ ", 78 | "snmp-check" : "snmp-check __IP__", 79 | "onesixtyone" : "onesixtyone __IP__", 80 | "snmpwalk" : "snmpwalk __IP__", 81 | "smbmap" : "smbmap -H __IP__ -P __PORT__", 82 | "nmap_nse_telnet": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"telnet* and not telnet-brute*\" __IP__", 83 | "nmap_nse_ssh": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"ssh* and not ssh-brute*\" __IP__", 84 | "nmap_nse_nbstat" : "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"nbstat*\" __IP__", 85 | "nmap_nse_smb" : "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"smb-enum*\" __IP__", 86 | "nmap_nse_rpc" : "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"rpc* and not rpcap*\" __IP__", 87 | "nmap_nse_snmp": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"snmp*\" __IP__", 88 | "nmap_nse_smtp": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"smtp*\" __IP__", 89 | "nmap_nse_http": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"http*\" __IP__", 90 | "nmap_nse_ntp": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"ntp*\" __IP__", 91 | "nmap_nse_dns": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"dns*\" __IP__", 92 | "nmap_nse_ftp": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"ftp* and not ftp-brute*\" __IP__", 93 | "nmap_nse_imap": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"imap*\" __IP__", 94 | "nmap_nse_ldap": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"ldap*\" __IP__", 95 | "nmap_nse_irc": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"irc*\" __IP__", 96 | "nmap_nse_mongodb": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"mongodb*\" __IP__", 97 | "nmap_nse_ms-sql": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"ms-sql*\" __IP__", 98 | "nmap_nse_mysql": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"mysql* and not mysql-brute*\" __IP__", 99 | "nmap_nse_msrpc": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"msrpc*\" __IP__", 100 | "nmap_nse_dns": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"dns*\" __IP__", 101 | "nmap_nse_oracle": "nmap -Pn -p __PORT__ --host-timeout " + self.scriptvars['host_timeout'] + " --script \"oracle*\" __IP__", 102 | 103 | # hydra will attempt to bruteforce about 1700 combinations (17 usernames with 100 passwords) 104 | "hydra_ftp": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} ftp://__IP__", 105 | "hydra_ssh": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} ssh://__IP__", 106 | "hydra_smb": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} smb://__IP__", 107 | "hydra_mysql": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} mysql://__IP__", 108 | "hydra_telnet": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} telnet://__IP__", 109 | "hydra_rdp": f"hydra -s __PORT__ -L {self.USERLIST} -P {self.PASSLIST} rdp://__IP__" 110 | } 111 | self.runonce_commands = [ 112 | "enum4linux", 113 | "onesixtyone", 114 | "snmpwalk", 115 | "smtp-user-enum", 116 | "rpcinfo" 117 | ] 118 | self.blacklist_services = { 119 | "nikto" : ["https", "ssl/http"], 120 | "gobuster" : ["https", "ssl/http"] 121 | } 122 | self.processor_scripts = [ 123 | "hydra", 124 | "gobuster" 125 | ] 126 | self.process_list = { 127 | "hydra" : self.processor_hydra, 128 | "gobuster" : self.processor_gobuster 129 | } 130 | self.detail_list = { 131 | "hydra" : "Credentials", 132 | "gobuster" : "Paths" 133 | } 134 | self.processor_stopcodes = [] 135 | self.reconbot_info = { 136 | } 137 | 138 | def run(self): 139 | scanning_thread = threading.Thread(target=self.initial) 140 | scanning_thread.daemon = True 141 | scanning_thread.start() 142 | 143 | time.sleep(3) 144 | tcounter = 0 # The clock starts tickin 145 | while True: 146 | tcounter += 1 147 | counter = 0 148 | alivescripts = [] 149 | for script in self.scriptthreads: 150 | if (self.scriptthreads[script].is_alive()): 151 | counter += 1 152 | alivescripts.append(script) 153 | if counter == 0: 154 | self.bprint( 155 | colored('ReconBot finished scanning ', 'green', attrs=['bold']) + colored(self.TARGET + '','red',attrs=['bold'])) 156 | self.shutdown() 157 | break 158 | if (tcounter % 180 == 0) and (tcounter != 0): 159 | msg = "" 160 | msg += colored(f"-"*100, 'red') + '\n' 161 | for info_item in self.reconbot_info: 162 | if len(self.reconbot_info[info_item]) > 0: 163 | msg += colored(f"{info_item}\n", 'red' , attrs=['bold']) 164 | for item in sorted(self.reconbot_info[info_item]): 165 | msg += colored(f"\t{item}\n", 'green', attrs=['bold']) 166 | msg += colored(f"\n{counter}", 'red', attrs=['bold']) + colored(' scripts still running\n', 'green',attrs=['bold']) 167 | for scr in alivescripts: 168 | msg += '\t' + colored(scr, 'red', attrs=['bold']) + '\n' 169 | msg += '\n' + colored(f"-" * 100, 'red') 170 | self.bprint(msg) 171 | time.sleep(1) 172 | 173 | def bprint(self, text): 174 | print(text) 175 | self.logfile = open(f"{self.TARGET}/reconbot_output.txt", "a+") 176 | self.logfile.write(text + '\n') 177 | self.logfile.close() 178 | 179 | def bot_print(self, line, color, icon): 180 | text = "" 181 | text += colored('[', 'white', attrs=['bold']) + colored(icon, color, attrs=['bold']) + colored('] ', 'white', attrs=['bold']) 182 | text += colored(line, color) 183 | self.bprint(text) 184 | 185 | def bot_print_alert(self, line): # print a colored line 186 | self.bot_print(line, 'red', '!') 187 | 188 | def bot_print_process(self, line): # print a colored line 189 | self.bot_print(line, 'green', '+') 190 | 191 | def bot_print_done(self, line): # print a colored line 192 | self.bot_print(line, 'yellow', '+') 193 | 194 | def bot_print_finding(self, info): 195 | text = "" 196 | text += colored('[', 'white', attrs=['bold']) + colored('i', 'green', attrs=['bold']) + colored('] ', 'white', attrs=['bold']) 197 | text += info 198 | self.bprint(text) 199 | 200 | def bot_startline(self): 201 | self.bprint(colored('-'*100, 'grey', attrs=['bold'])) 202 | 203 | def bot_endline(self): 204 | self.bprint(colored('-' * 100, 'grey', attrs=['bold'])) 205 | 206 | def random(self, length): 207 | return ''.join(random.choice(string.ascii_lowercase) for i in range(length)) 208 | 209 | def checkroot(self): 210 | if os.geteuid() != 0: 211 | text = colored('[', 'white', attrs=['bold']) + colored('!', 'red', attrs=['bold']) + colored('] ', 'white',attrs=['bold']) + colored("You need to be root to run this script!", 'red') 212 | print(text) 213 | sys.exit() 214 | 215 | def checkIP(self): 216 | if not re.search('^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$', self.IP): 217 | self.printUsage() 218 | else: 219 | self.TARGET = self.IP 220 | self.createDir() 221 | self.printHeader() 222 | 223 | def checkargs(self): # Check the arguments given 224 | if self.args.ports != None: 225 | self.PORTS = self.args.ports.split(',') 226 | try: 227 | for i in self.PORTS: 228 | if len(i) == 0: 229 | raise Exception 230 | else: 231 | if type(int(i)) != int: 232 | raise Exception 233 | self.PORTS = self.args.ports 234 | self.bot_print_done(f'Found custom supplied ports: {self.PORTS}') 235 | except: 236 | self.bot_print_alert("ERROR: User supplied ports have been formatted wrong, switching to full recon mode\n") 237 | time.sleep(3) 238 | self.PORTS = False 239 | else: 240 | self.PORTS = False 241 | 242 | if self.args.weblist != None: 243 | try: 244 | self.WEBLIST = open(self.args.weblist) 245 | self.WEBLIST = self.args.weblist 246 | self.bot_print_done(f'Found custom supplied weblist: {self.WEBLIST}') 247 | except: 248 | self.bot_print_alert(f"ERROR: Web wordlist specified could not be opened, switching to default wordlist ({self.defaultvalues['weblist']})\n") 249 | time.sleep(3) 250 | self.WEBLIST = self.defaultvalues['weblist'] 251 | else: 252 | self.WEBLIST = self.defaultvalues['weblist'] 253 | 254 | if self.args.userlist != None: 255 | try: 256 | self.USERLIST = open(self.args.userlist) 257 | self.USERLIST = self.args.userlist 258 | self.bot_print_done(f'Found custom supplied userlist: {self.USERLIST}') 259 | except: 260 | self.bot_print_alert(f"ERROR: Username wordlist specified could not be opened, switching to default wordlist ({self.defaultvalues['userlist']})\n") 261 | time.sleep(3) 262 | self.USERLIST = self.defaultvalues['userlist'] 263 | else: 264 | self.USERLIST = self.defaultvalues['userlist'] 265 | 266 | if self.args.passlist != None: 267 | try: 268 | self.PASSLIST = open(self.args.passlist) 269 | self.PASSLIST = self.args.passlist 270 | self.bot_print_done(f'Found custom supplied passlist: {self.PASSLIST}') 271 | except: 272 | self.bot_print_alert(f"ERROR: Password wordlist specified could not be opened, switching to default wordlist ({self.defaultvalues['passlist']})\n") 273 | time.sleep(3) 274 | self.PASSLIST = self.defaultvalues['passlist'] 275 | else: 276 | self.PASSLIST = self.defaultvalues['passlist'] 277 | 278 | 279 | self.NMAPONLY = self.args.nmaponly 280 | if self.NMAPONLY: 281 | self.bot_print_done(f'Found nmaponly flag, only scanning with nmap') 282 | 283 | self.QUICK = self.args.quick 284 | if self.QUICK: 285 | self.bot_print_done(f'Found quick flag, only scanning quick ports') 286 | 287 | self.BRUTEONLY = self.args.bruteonly 288 | if self.BRUTEONLY: 289 | self.bot_print_done(f'Found bruteonly flag, only using hydra scripts') 290 | 291 | self.VERBOSE = self.args.verbose 292 | if self.VERBOSE: 293 | self.bot_print_done(f'Found verbose flag, dumping more info in the reconbot output') 294 | 295 | def printUsage(self): # Print some basic usage info 296 | usagetext = "" 297 | usagetext += colored('Use reconbot -h to see how to use this script', 'red', attrs=['bold']) 298 | print(usagetext) 299 | sys.exit() 300 | 301 | def printHeader(self): 302 | header = "\n" 303 | header += colored('-'*100, 'green', attrs=['bold']) 304 | header += colored('\nReconBot - Automatic Reconnaissance Bot\n', 'red', attrs=['bold']) 305 | header += colored('-'*100, 'green', attrs=['bold']) 306 | header += "\n" 307 | self.bprint(header) 308 | self.bot_print_done(f"Output saved to: {self.TARGET}/reconbot_output.txt") 309 | 310 | def createDir(self): 311 | self.PATH = f'{self.TARGET}/recon' 312 | if not os.path.exists(self.PATH): 313 | os.makedirs(self.PATH) 314 | os.chmod(self.TARGET, 0o755) 315 | os.chmod(self.PATH, 0o755) 316 | os.chown(self.TARGET, 1000, 1000) 317 | os.chown(self.PATH, 1000, 1000) 318 | self.XMLPATH = self.PATH + '/xml_nmap' 319 | if not os.path.exists(self.XMLPATH): 320 | os.makedirs(self.PATH + '/xml_nmap') 321 | os.chmod(self.TARGET, 0o755) 322 | os.chmod(self.PATH, 0o755) 323 | os.chown(self.TARGET, 1000, 1000) 324 | os.chown(self.PATH, 1000, 1000) 325 | 326 | def nmap_quick(self): 327 | self.bot_print_process('Starting NMAP TCP Quick scan') 328 | if self.PORTS: 329 | newports = self.get_command_output(f'nmap -sS -p {self.PORTS} -Pn --open -oN {self.PATH+"/nmap_quick"} -oX {self.XMLPATH+"/nmap_quick_xml"} {self.TARGET}', "NMAP Quick Scan", True) 330 | else: 331 | newports = self.get_command_output(f'nmap -sS -Pn --open -oN {self.PATH+"/nmap_quick"} {self.TARGET} -oX {self.XMLPATH+"/nmap_quick_xml"}', "NMAP Quick Scan", True) 332 | self.check_scripts("tcp") 333 | if newports != 0: 334 | self.nmap_detailed("NMAP TCP Quick detailed scan", True, "TCP", "Quick") 335 | 336 | def nmap_udp_quick(self): 337 | if not self.PORTS: 338 | self.bot_print_process('Starting NMAP UDP QUICK scan (top 20 ports)') 339 | newports = self.get_command_output(f'nmap --open -sU -Pn -oN {self.PATH+"/nmap_udp_top20ports"} -oX {self.XMLPATH+"/nmap_udp_top20ports_xml"} --top-ports=20 {self.TARGET}', "NMAP UDP QUICK scan", True) 340 | if newports != 0: 341 | self.nmap_detailed("NMAP UDP Quick detailed scan", False, "UDP", "Quick") 342 | 343 | def nmap_full(self): 344 | if not self.PORTS: 345 | self.bot_print_process('Starting NMAP TCP Full scan') 346 | newports = self.get_command_output(f'nmap --open -sS -Pn -p- -oN {self.PATH+"/nmap_full"} -oX {self.XMLPATH+"/nmap_full_xml"} {self.TARGET}', "NMAP Full Scan", True) 347 | self.check_scripts("tcp") 348 | if newports != 0: 349 | self.nmap_detailed("NMAP TCP Full detailed scan", True, "TCP", "Full") 350 | 351 | def nmap_udp_full(self): 352 | if not self.PORTS: 353 | self.bot_print_process('Starting NMAP UDP Top 50 scan') 354 | newports = self.get_command_output(f'nmap --open -sU -Pn -p- -oN {self.PATH+"/nmap_udp_top_200"} -oX {self.XMLPATH+"/nmap_udp_top_200_xml"} --top-ports=200 {self.TARGET}', "NMAP UDP Full Scan", True) 355 | if newports != 0: 356 | self.nmap_detailed("NMAP UDP Top 50 detailed scan", False, "UDP", "Full") 357 | 358 | def nmap_detailed(self, name, checkscripts, protocol, type): 359 | ports = "" 360 | for i in self.services: 361 | if protocol.lower() in i: 362 | ports += i.split('-')[0] + ',' 363 | ports = ports[:-1] 364 | if protocol == "TCP": 365 | self.bot_print_process(f'Starting {name} scan on found {protocol} ports: {ports}') 366 | newports = self.get_command_output(f'nmap --open -Pn -sV -sC -p {ports} -oN {self.PATH+"/nmap_detailed_"+protocol+"_"+type} -oX {self.XMLPATH+"/nmap_detailed_"+protocol+"_"+type+"_xml"} {self.TARGET}', name, False) 367 | elif protocol == "UDP": 368 | self.bot_print_process(f'Starting {name} scan on UDP ports') 369 | newports = self.get_command_output(f'nmap --open -Pn -sU -sV -sC -p {ports} -oN {self.PATH+"/nmap_detailed_"+protocol+"_"+type} -oX {self.XMLPATH+"/nmap_detailed_"+protocol+"_"+type+"_xml"} {self.TARGET}', name, False) 370 | if checkscripts: 371 | self.check_scripts("tcp") 372 | 373 | def nmap_vulnscan(self): 374 | tcp_ports = "" 375 | udp_ports = "" 376 | for i in self.services: 377 | if "tcp" in i: 378 | tcp_ports += i.split('-')[0] + ',' 379 | elif "udp" in i: 380 | udp_ports += i.split('-')[0] + ',' 381 | 382 | tcp_ports = tcp_ports[:-1] 383 | udp_ports = udp_ports[:-1] 384 | if len(tcp_ports) > 1: 385 | tcp_vulnscan_thread = threading.Thread(target=self.nmap_vulnthread, args=[tcp_ports, "TCP"]) 386 | tcp_vulnscan_thread.daemon = True 387 | tcp_vulnscan_thread.start() 388 | self.scriptthreads["NMAP Vuln TCP Scan"] = tcp_vulnscan_thread 389 | 390 | if len(udp_ports) > 1: 391 | udp_vulnscan_thread = threading.Thread(target=self.nmap_vulnthread, args=[udp_ports, "UDP"]) 392 | udp_vulnscan_thread.daemon = True 393 | udp_vulnscan_thread.start() 394 | self.scriptthreads["NMAP Vuln UDP Scan"] = udp_vulnscan_thread 395 | 396 | def nmap_vulnthread(self, ports, type): 397 | self.bot_print_process(f'Starting NMAP VULN scan on found {type} ports: {ports}') 398 | if type == "TCP": 399 | newports = self.get_command_output(f'nmap --open -Pn --script vuln -p {ports} -oN {self.PATH + "/nmap_vuln_tcp"} -oX {self.XMLPATH+"/nmap_vuln_tcp_xml"} {self.TARGET}', "NMAP TCP Vuln Scan", False) 400 | elif type == "UDP": 401 | newports = self.get_command_output(f'nmap --open -Pn -sU --script vuln -p {ports} -oN {self.PATH + "/nmap_vuln_udp"} {self.TARGET} -oX {self.XMLPATH+"/nmap_vuln_tcp_xml"}', "NMAP UDP Vuln Scan", False) 402 | 403 | def nmap_process(self, output): 404 | output = output.split('\n') 405 | newportcounter = 0 406 | filtered_flag = False 407 | for line in output: 408 | if ("open" in line) and ("tcp" in line or "udp" in line) and "/" in line: 409 | if "filtered" not in line: 410 | filtered_flag = False 411 | parts = line.split() 412 | protocol_port = parts[0].replace('/', '-') 413 | service = line # seperate by spaces tabs etc, and get the last item 414 | if protocol_port not in self.services.keys(): 415 | self.services[protocol_port] = service 416 | newportcounter += 1 417 | else: 418 | services = self.services[protocol_port].split('|') 419 | if service not in services: 420 | newservices = ('|'.join(services)) + '|' + service 421 | self.services[protocol_port] = newservices 422 | return newportcounter 423 | 424 | def check_scripts(self, type): 425 | while True: 426 | try: 427 | if not self.NMAPONLY: 428 | for protocol_port in self.services: 429 | service = self.services[protocol_port] 430 | services = service.split('|') 431 | for serv in services: 432 | for mapped_service in self.mapped_services: 433 | servlist = serv.split() 434 | cont = False 435 | for part in servlist[:3]: 436 | if len(part) > 2 and mapped_service in part and type in protocol_port: 437 | cont = True 438 | if cont: 439 | for script in self.mapped_services[mapped_service]: 440 | if ((self.BRUTEONLY) and ("hydra" in script)) or (not self.BRUTEONLY): 441 | shouldrun = True 442 | if script in self.blacklist_services: 443 | for blacklisted in self.blacklist_services[script]: 444 | s = servlist[-1] 445 | if s.startswith(blacklisted): 446 | shouldrun = False 447 | break 448 | if shouldrun: 449 | self.start_script(protocol_port, script) 450 | else: 451 | self.start_script(protocol_port, script) 452 | break 453 | except RuntimeError: 454 | pass 455 | 456 | def start_script(self, protocol_port, script): 457 | port = int(protocol_port.split('-')[0]) 458 | if script in self.runonce_commands: 459 | if script not in self.runningscripts: 460 | self.runningscripts.append(f"{script}") 461 | cmd = self.mapped_commands[script].replace("__IP__", self.TARGET).replace("__PORT__", str(port)).replace("__PATH__", self.PATH) 462 | if "hydra" in script: 463 | self.bot_print_alert(f'Trying to bruteforce with {script}') 464 | else: 465 | self.bot_print_process(f'Starting {script}') 466 | self.bot_print_done(f"\toutfile: {self.PATH}/{script}.txt") 467 | t = threading.Thread(target=self.run_cmd, args=[cmd, script, port]) 468 | self.scriptthreads[script] = t 469 | self.scriptthreads[script].daemon = True 470 | self.scriptthreads[script].start() 471 | 472 | else: 473 | if f"{script}_{port}" not in self.runningscripts: 474 | self.runningscripts.append(f"{script}_{port}") 475 | cmd = self.mapped_commands[script].replace("__IP__", self.TARGET).replace("__PORT__", str(port)).replace("__PATH__", self.PATH) 476 | if "hydra" in script: 477 | self.bot_print_alert(f'Trying to bruteforce with {script} on port {port}') 478 | else: 479 | self.bot_print_process(f'Starting {script} on port {port}') 480 | self.bot_print_done(f"\toutfile: {self.PATH}/{script}_{port}.txt") 481 | t = threading.Thread(target=self.run_cmd, args=[cmd, script, port]) 482 | self.scriptthreads[f"{script}_{port}"] = t 483 | self.scriptthreads[f"{script}_{port}"].daemon = True 484 | self.scriptthreads[f"{script}_{port}"].start() 485 | 486 | def get_command_output(self, cmd, name, printzerofound): 487 | try: 488 | newports = -1 489 | cmdl = cmd.split(' ') 490 | result = subprocess.check_output(cmdl).decode('utf-8') 491 | newports = self.nmap_process(result) 492 | self.print_command_output(result, name) 493 | if printzerofound and newports == 0: 494 | self.bot_print_alert(f"{name} did not find any new ports!") 495 | return newports 496 | except (KeyboardInterrupt, subprocess.CalledProcessError): 497 | pass 498 | 499 | def print_command_output(self, cmd_output, name): 500 | output = "" 501 | output += colored('-'*100, 'green') + '\n' 502 | output += colored(f'- {name}\n', 'green') 503 | output += colored('-'*100, 'green') 504 | output += f'\n{cmd_output}' 505 | output += colored('-'*100, 'green') 506 | self.bprint(output) 507 | 508 | def check_stopcode(self, threadname, stopcode): 509 | if not (self.scriptthreads[threadname].is_alive()): 510 | self.processor_stopcodes.remove(stopcode) 511 | return False 512 | return True 513 | 514 | def start_processor(self, script, threadname, process_file): 515 | try: 516 | stopcode = self.random(100) 517 | self.processor_stopcodes.append(stopcode) 518 | self.processor_info_parser(script) 519 | self.main_processor(process_file, stopcode, threadname, self.process_list[script]) 520 | except KeyboardInterrupt: 521 | pass 522 | 523 | def processor_info_parser(self, script): 524 | if self.detail_list[script] not in self.reconbot_info: 525 | self.reconbot_info[self.detail_list[script]] = [] 526 | 527 | def main_processor(self, process_file, stopcode, threadname, subprocess): 528 | dumped = [] 529 | while True: 530 | time.sleep(0.5) 531 | if not self.check_stopcode(threadname, stopcode): 532 | break 533 | subprocess(process_file, dumped) 534 | 535 | def processor_hydra(self, process_file, dumped): 536 | output = open(process_file) 537 | for line in output: 538 | if "host" in line: 539 | username = line.split(" login:")[1].split(' ')[1].strip() 540 | password = line.split(" password:")[1].split(' ')[1].strip() 541 | if username+password not in dumped: 542 | dumped.append(username+password) 543 | self.reconbot_info["Credentials"].append(f"{username}:{password}") 544 | self.bot_print_finding(colored("Credentials", "red", attrs=['bold']) + colored(f" found in {process_file}: ", 'red') + colored(f"{username}", "green", attrs=['bold']) + colored(f":", "red") + colored(f"{password}\n", "green", attrs=['bold'])) 545 | 546 | def processor_gobuster(self, process_file, dumped): 547 | output = open(process_file) 548 | port = process_file.split('_')[-1].split('.txt')[0] 549 | type = 'http' 550 | if 'https' in process_file: 551 | type = 'https' 552 | for line in output: 553 | if "Status" in line and not "Status codes:" in line: 554 | path = '/'+line.split('/')[1].strip() 555 | if path not in dumped: 556 | dumped.append(path) 557 | self.reconbot_info["Paths"].append(f"[{process_file}] {path}") 558 | if self.VERBOSE: 559 | self.bot_print_finding(colored("Gobuster ", "green", attrs=['bold']) + colored(f"{path}", 'yellow', attrs=['bold']) + colored(f" \t\tFound on {self.TARGET}:{port}", "green")) 560 | self.processor_htmlcomments(f"{type}://{self.TARGET}:{port}{path.split(' (Status:')[0]}") 561 | 562 | def processor_htmlcomments(self, url): 563 | outfile = open(f"{self.PATH}/CommentFinder.txt", 'a+') 564 | r = requests.get(url, verify=False) 565 | html = r.text 566 | soup = BeautifulSoup(html, 'lxml') 567 | comments = soup.findAll(text=lambda text: isinstance(text, Comment)) 568 | if len(comments) > 0: 569 | outfile.write(f"{url}:\n") 570 | self.bot_print_finding(colored(f"CommentFinder", "green", attrs=['bold']) + colored(f"( {url} )", "green", attrs=['bold'])) 571 | for comment in comments: 572 | outfile.write(f"{comment}\n") 573 | if self.VERBOSE: 574 | self.bprint(colored(f"", 'yellow', attrs=['bold'])) 575 | if len(comments) > 0: 576 | outfile.write(f"\n") 577 | 578 | def run_cmd(self, cmd, script, port): 579 | try: 580 | if script in self.runonce_commands: 581 | threadname = script 582 | filename = f"{self.PATH}/{script}.txt" 583 | cmd_file = open(filename, "w+") 584 | else: 585 | threadname = f"{script}_{port}" 586 | filename = f"{self.PATH}/{script}_{port}.txt" 587 | cmd_file = open(filename, "w+") 588 | for process_script in self.processor_scripts: 589 | if process_script in script: 590 | t = threading.Thread(target=self.start_processor, args=[process_script, threadname, filename]) 591 | t.daemon = True 592 | t.start() 593 | subprocess.call(cmd, stdout=cmd_file, stderr=cmd_file, shell=True) 594 | except KeyboardInterrupt: 595 | self.bot_print_alert(f"Stopping {script}") 596 | 597 | def check_which(self): 598 | print("") 599 | for softwareName in self.which_checklist: 600 | if which(softwareName) is None: 601 | self.bot_print_alert(f"{softwareName} was not found! Please install for full use of reconbot") 602 | print("") 603 | 604 | 605 | def shutdown(self): 606 | self.logfile.close() 607 | 608 | def initial(self): 609 | self.check_which() 610 | # start tcp quick thread 611 | tcp_quick_thread = threading.Thread(target=self.nmap_quick) 612 | tcp_quick_thread.daemon = True 613 | tcp_quick_thread.start() 614 | self.scriptthreads["NMAP Quick / Detailed TCP Scan"] = tcp_quick_thread 615 | 616 | # start tcp full thread 617 | if not self.QUICK: 618 | tcp_full_thread = threading.Thread(target=self.nmap_full) 619 | tcp_full_thread.daemon = True 620 | tcp_full_thread.start() 621 | self.scriptthreads["NMAP Full TCP / Detailed Scan"] = tcp_full_thread 622 | 623 | # start udp quick thread 624 | udp_quick_thread = threading.Thread(target=self.nmap_udp_quick) 625 | udp_quick_thread.daemon = True 626 | udp_quick_thread.start() 627 | self.scriptthreads["NMAP Quick UDP / Detailed Scan"] = udp_quick_thread 628 | 629 | # start udp full thread 630 | if not self.QUICK: 631 | udp_full_thread = threading.Thread(target=self.nmap_udp_full) 632 | udp_full_thread.daemon = True 633 | udp_full_thread.start() 634 | self.scriptthreads["NMAP Full UDP / Detailed Scan"] = udp_full_thread 635 | 636 | vulnscan = True 637 | while vulnscan: # we wait untill the quick tcp scan, the full tcp scan and the quick udp scans are finished to start a vuln scan, udp full scans take way too long to wait for this. 638 | if not self.QUICK: 639 | if not tcp_quick_thread.is_alive() and not tcp_full_thread.is_alive() and not udp_quick_thread.is_alive(): 640 | self.nmap_vulnscan() 641 | vulnscan = False 642 | else: 643 | if not tcp_quick_thread.is_alive() and not udp_quick_thread.is_alive(): 644 | self.nmap_vulnscan() 645 | vulnscan = False 646 | 647 | 648 | 649 | def main(): 650 | 651 | example = "\nEXAMPLES\n\n" 652 | example += "reconbot 1.1.1.1 # Full recon scan of target\n" 653 | example += "reconbot -HF ./hosts # Full recon scan of targets in file\n" 654 | example += "reconbot 1.1.1.1 -p 80,443 # Recon scan of target only scanning port 80\n" 655 | example += "reconbot 1.1.1.1 -w /usr/share/wordlist/webfiles.txt # Full recon scan of target using custom web directory bruteforce wordlist\n" 656 | example += "reconbot 1.1.1.1 -U /wordlist/usernames.txt -P /wordlist/passwords.txt # Full recon scan of target using custom username and password wordlists\n" 657 | example += "reconbot 1.1.1.1 --nmaponly # Nmap only scan of target\n" 658 | example += "reconbot 1.1.1.1 --nmaponly --quick # Nmap only scan of target only using quick scans\n" 659 | example += "reconbot 1.1.1.1 -p 22,21 --bruteonly --quick -P /wordlist/passwords.txt # Bruteforce only scan of target only using quick nmap scans to discover ports\n\n" 660 | 661 | parser = parser = argparse.ArgumentParser(prog="Reconbot",description="Automated Reconnaissance Bot", epilog=example, formatter_class=argparse.RawDescriptionHelpFormatter) 662 | parser.add_argument("host", metavar='', nargs='?', help="Target") 663 | parser.add_argument("-HF", "--hostfile", metavar='', help="File containing targets to scan") 664 | parser.add_argument("-p", "--ports", metavar='', help="Ports to scan") 665 | parser.add_argument("-U", "--userlist", metavar='', help="Wordlist to use for usernames when bruteforcing") 666 | parser.add_argument("-P", "--passlist", metavar='', help="Wordlist to use for passwords when bruteforcing") 667 | parser.add_argument("-w", "--weblist", metavar='', help="Wordlist to use for web directory bruteforcing") 668 | parser.add_argument("--nmaponly", action='store_true', default=False, help="Only activate the nmap scripts") 669 | parser.add_argument("--quick", action='store_true', default=False, help="Only activate quick port scanning") 670 | parser.add_argument("--bruteonly", action='store_true', default=False, help="Only activate the hydra bruteforcing scripts") 671 | parser.add_argument("--verbose", action='store_true', default=False, help="Display extra output in the reconbot output (things like paths gobuster finds)") 672 | args = parser.parse_args() 673 | TARGET = False 674 | 675 | if args.host != None: 676 | TARGET = args.host 677 | elif args.hostfile != None: 678 | try: 679 | TARGETFILE = open(args.hostfile).read().split() 680 | except: 681 | print(colored('[', 'white', attrs=['bold']) + colored('!', 'red', attrs=['bold']) + colored('] ', 'white',attrs=['bold']) + colored(f"No file found called {args.hostfile}", 'red')) 682 | sys.exit() 683 | else: 684 | print(colored('[', 'white', attrs=['bold']) + colored('!', 'red', attrs=['bold']) + colored('] ', 'white', attrs=['bold']) + colored(f"Please supply target or targetfile (use reconbot -h for usage)", 'red')) 685 | sys.exit() 686 | 687 | 688 | if TARGET: 689 | try: 690 | rbot = reconbot(args, TARGET) 691 | rbot.run() 692 | except KeyboardInterrupt: 693 | rbot.shutdown() 694 | rbot.bot_print_alert("Keyboard Interrupt found, stopping reconbot! (If your terminal is broken after reconbot has terminated, run the 'stty sane' command)") 695 | sys.exit() 696 | else: 697 | try: 698 | for TARGET in TARGETFILE: 699 | rbot = reconbot(args, TARGET.strip()) 700 | rbot.run() 701 | except KeyboardInterrupt: 702 | rbot.shutdown() 703 | rbot.bot_print_alert("Keyboard Interrupt found, stopping reconbot! (If your terminal is broken after reconbot has terminated, run the 'stty sane' command)") 704 | sys.exit() 705 | 706 | if __name__ == "__main__": 707 | main() 708 | --------------------------------------------------------------------------------