def nmap_scan(): print(utils.normal_message(), "Starting scan of", config.current_target) # Check if Nmap is installed - critical program. If this fails, the program will exit utils.program_installed("nmap", True) out_file = os.path.join( config.get_module_cache("nmap", config.current_target), "nmap.xml") if config.args.verbose: print(utils.normal_message(), "Nmap data will be written to", out_file) nmap_args = ['nmap', '-sC', '-sV', '-oX', out_file, config.current_target] if config.args.scan_udp: print(utils.normal_message(), "Scanning UDP ports, this may take a long time") nmap_args.append('-sU') print(utils.normal_message(), "Scanning open ports on", config.current_target + "...", end=' ') with Spinner(): output = subprocess.check_output(nmap_args).decode('UTF-8') print("") # if config.args.show_output: # print("") # print(output) print(utils.normal_message(), "Scan complete") parse_nmap_scan(out_file)
def execute(self, ip: str, port: int) -> None: """ Enumerates files and directories on the given web server :param ip: IP to use :param port: Port to use """ self.create_loot_space(ip, port) # List of dictionary results Loot.loot[ip][str(port)][self.loot_name] = [] url = self.get_url(ip, port) filename = os.path.join(config.get_module_cache(self.name, ip), "enum-{PORT}.log".format(PORT=port)) wordlist_path = config.get_module_value(self.name, "wordlist", "/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt") if not os.path.exists(wordlist_path): msg = utils.terminal_width_string( "Unable to find Gobuster wordlist at {PATH}".format(PATH=wordlist_path) ) self.logger.error(msg) return extensions = config.get_module_value(self.name, "extensions", ".php,.txt") with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader: # Arguments: # -e - expanded URL (whole path is shown) # -z - Don't display the progress (X/Y Z%) # -q - Don't print the banner for Gobuster # -k - Skip SSL Cert verification # -x - File extension(s) to scan for. Value loaded from config.ini with .php,.txt as default # -u - URL # -w - Wordlist command = "gobuster dir -z -q -e -k -u {URL} -w {WORDLIST} -x {EXTENSIONS}" \ .format(URL=url, WORDLIST=wordlist_path, EXTENSIONS=extensions) process = subprocess.Popen(command, stdout=writer) # While the process return code is None while process.poll() is None: time.sleep(0.5) responses = reader.read().decode("UTF-8").splitlines() for response in responses: if response.strip() is not "": # Get the response code (last three chars but one) code = int(response[-4:-1]) # Get this as a human readable response human_readable_code = utils.get_http_code(code) # Get the directory response_dir = response.split('(')[0].strip() # Add to the loot result = {"Path": response_dir, "Code": code, "Code Value": human_readable_code} Loot.loot[ip][str(port)][self.loot_name].append(result) print(utils.warning_message(), "Found directory at {PATH} with {CODE} ({CODE_VALUE}" .format(PATH=response_dir, CODE=code, CODE_VALUE=human_readable_code))
def execute(self, ip: str, port: int) -> None: """ Scan the web server using Nikto :param ip: IP to use :param port: Port to use """ self.create_loot_space(ip, port) url = self.get_url(ip, port) output_filename = os.path.join(get_module_cache(self.name, ip, ""), "nmap") filename = os.path.join(get_module_cache(self.name, ip, ""), "nmap.log") self.logger.debug( "Writing XML output to {PATH}.xml|.nmap|.gnmap".format( PATH=output_filename)) self.logger.info("Starting Nmap scan of {TARGET}".format(TARGET=ip)) with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader: # Arguments: # -host - the host to scan # -Format - the format of the output file # -o - the output path # -ask no - don't do anything which requires user input # -sC -sV command = "nikto -host {URL} -Format xml -o {OUTPUT} -ask no"\ .format(URL=url, OUTPUT=output_filename) process = subprocess.Popen(command, stdout=writer) # While the process return code is None while process.poll() is None: time.sleep(0.5) # output = reader.read().decode("UTF-8").splitlines() xmldoc = minidom.parse(output_filename) nikto_items = xmldoc.getElementsByTagName('item') for item in nikto_items: print( utils.warning_message(), item.getElementsByTagName("description") [0].firstChild.wholeText)
def download_file(self, ip: str, port: str, ftp_client: ftplib.FTP, filename: str) -> None: """ Download the specified file :param ip: IP of the FTP server :param port: Port of the server :param ftp_client: Reference to FTP client :param filename: Filename path we want """ try: file_size = ftp_client.size(filename) / 1024 / 1024 except ftplib.error_perm: self.logger.error( "Permission denied to access {FILE}".format(FILE=filename)) return self.logger.debug("Downloading {FILE} size {SIZE}".format( FILE=filename, SIZE="{:.1f}mb".format(file_size))) #with Spinner(): local_filename = os.path.join( config.get_module_cache(self.name, ip, port), filename) if not os.path.exists(os.path.dirname(local_filename)): os.mkdir(os.path.dirname(local_filename)) file = open(local_filename, 'wb') # TODO: Sometimes hangs when it reaches a file that it can't download. Find a fix (parse ftp.dir?) try: ftp_client.retrbinary('RETR ' + filename, file.write) self.logger.info( "Downloaded {FILE} ({SIZE}mb) to FTP cache".format( FILE=filename, SIZE="{:.1f}mb".format(file_size))) except ftplib.error_perm: self.logger.error( "Permission denied to access {FILE}".format(FILE=filename)) file.close()
def test_get_module_cache_no_port(): path = config.get_module_cache("Test", "127.0.0.1") assert "Test" in path assert path.endswith("Test")
def test_get_module_cache(): path = config.get_module_cache("Test", "127.0.0.1", "22") assert "Test" in path assert "22" in path
def execute(self, ip: str, port: int) -> None: """ Get all of the webpage links (non recursive for now) :param ip: IP to use :param port: Port to use :return: """ self.create_loot_space(ip, port) Loot.loot[ip][str(port)][self.loot_name]["Internal"] = [] Loot.loot[ip][str(port)][self.loot_name]["External"] = [] if port == 443: url = "https://{IP}".format(IP=ip) elif port == 80: url = "http://{IP}".format(IP=ip) else: url = "http://{IP}:{PORT}".format(IP=ip, PORT=port) try: response = requests.get(url, allow_redirects=True) for link in BeautifulSoup(response.text, features="html.parser", parse_only=SoupStrainer('a')): if link.has_attr('href'): parse = urlparse(link['href']) loot_url = parse[1] + parse[2] if self.is_internal_url(ip, parse[1]): if loot_url not in Loot.loot[ip][str(port)][ self.loot_name]["Internal"]: self.logger.debug( "{URL} is an internal URL".format( URL=loot_url)) Loot.loot[ip][str(port)][ self.loot_name]["Internal"].append(loot_url) with open( os.path.join( config.get_module_cache( self.name, ip, str(port)), "internal.txt"), "a") as file: file.write("{URL}\n".format(URL=loot_url)) else: if loot_url not in Loot.loot[ip][str(port)][ self.loot_name]["External"]: self.logger.debug( "{URL} is an external URL".format( URL=loot_url)) Loot.loot[ip][str(port)][ self.loot_name]["External"].append(loot_url) with open( os.path.join( config.get_module_cache( self.name, ip, str(port)), "external.txt"), "a") as file: file.write("{URL}\n".format(URL=loot_url)) self.logger.info( "Found {INTERNAL} internal links and {EXTERNAL} external links" .format( INTERNAL=len( Loot.loot[ip][str(port)][self.loot_name]["Internal"]), EXTERNAL=len( Loot.loot[ip][str(port)][self.loot_name]["External"]))) except requests.exceptions.ConnectionError: self.logger.error("Unable to connect to {URL}".format(URL=url))
def execute(self, ip: str, port: int) -> None: # Don't specify filename on output_filename as all formats are specified output_filename = os.path.join(get_module_cache(self.name, ip, ""), "nmap") filename = os.path.join(get_module_cache(self.name, ip, ""), "nmap.log") self.logger.debug("Writing output to {PATH}.xml|.nmap|.gnmap".format(PATH=output_filename)) # TODO: Optional UDP scan self.logger.info("Starting Nmap scan of {TARGET}".format(TARGET=ip)) with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader: # Arguments: # -v - Verbose output # -sT - TCP scan # -sV - Version detection # -oA - Output in all formats command = "nmap -v -sT -sV -oA {OUTPUT_FILE} {TARGET}".format(TARGET=ip, OUTPUT_FILE=output_filename) process = subprocess.Popen(command, stdout=writer) # While the process return code is None while process.poll() is None: # Parse the program as it runs output = reader.read().decode("UTF-8").splitlines() for line in output: # If we've got an open port, print it if line.startswith("Discovered open port "): port = line[21:line.index("/")] print(utils.warning_message(), "Discovered port open on {PORT}".format(PORT=port)) # Get time remaining so there is still some output if " done; ETC: " in line: # TODO: Regex instead of this disgusting mess percentage_index = line.index("%") # Get 5 characters before the percentage (xx.xx and the percent) percentage = line[percentage_index-5:percentage_index+1] # Get time remaining in format x:xx:xx time_left = line.replace(" remaining)", "") time_left = time_left[-7:] print(utils.warning_message(), "Scan {PERC} complete - {TIME} left" .format(PERC=percentage, TIME=time_left)) # Wait for 0.25 seconds time.sleep(0.25) self.logger.info("Finished Nmap scan of {TARGET}".format(TARGET=ip)) # Parse the XML output xml = minidom.parse("{FILE}.xml".format(FILE=output_filename)) # Get the host scanned hostslist = xml.getElementsByTagName('hosts') # We only scan one host at a time if int(hostslist[0].attributes['down'].value) > 0: # Unreachable, return rest of the processing self.logger.warning("{TARGET} was unreachable".format(TARGET=ip)) return # Get all of the open ports port_list = xml.getElementsByTagName('port') self.logger.info("{PORT_COUNT} ports are open".format(PORT_COUNT=len(port_list))) # Get all of the CPE versions detected # cpe_list = list(dict.fromkeys([x.firstChild.nodeValue for x in xml.getElementsByTagName('cpe')])) # for cpe in cpe_list: # self.logger.info("Detected {CPE}".format(CPE=CPE(cpe).human())) # searchsploit_nmap_scan(out_file) # Loop through the open ports for open_port in port_list: # Get the service type for svc in open_port.getElementsByTagName('service'): # Parse the values service = svc.attributes['name'].value port = open_port.attributes['portid'].value # If the IP/port is not in the Loot dictionary, add it if ip not in Loot.loot: Loot.loot[ip] = {} if port not in Loot.loot[ip]: Loot.loot[ip][port] = {} # Add to the event Queue so we get a notification EventQueue.push(service=service, port=int(port))