def detect_service(openport): for service in openport.getElementsByTagName('service'): port = int(openport.attributes['portid'].value) service_type = service.attributes['name'].value try: service_name = service.attributes['product'].value except KeyError: service_name = service_type print(utils.normal_message(), service_name, "is open on port", port) # Ignore the port if its in the list of ports to skip if port not in config.args.skipPorts: # Some kind of SSH server if service_type == "ssh": print(utils.warning_message(), service_name, "is recognised by nmap as an ssh server") """scripts_ran = service.getElementsByTagName('script') for script in scripts_ran: print(script.attributes['id']) if script.attributes['id'] == 'fingerprint-strings': print(script.getElementsByTagName('elem')[0].text)""" # MySQL server if service_name == "mysql": print(utils.warning_message(), service_name, "is recognised by nmap as a MySQL server...") else: print( utils.warning_message(), "Skipping", service_name, "(port", str(port) + ") as it has been specified " "as a port to skip") print("")
def detect_os(cpe_list): for cpe in cpe_list: cpe_os_type = "cpe:/o" if cpe.startswith(cpe_os_type): print(utils.normal_message(), "Target OS appears to be", cpe_utils.CPE(cpe).human()) if cpe_utils.CPE(cpe).matches(cpe_utils.CPE("cpe:/o:microsoft:windows")) \ and platform.system() == "linux": print(utils.warning_message(), "Target machine is running Microsoft Windows") print(utils.warning_message(), "Will commence enumeration using enum4linux") print(utils.error_message(), "enum4linux not yet implemented")
def remove_files_over_size(self, ftp_client, files, size=1024 * 1024 * 50) -> (list, list): """ Remove any files over the specified size :param ftp_client: Reference to the FTP client :param files: Files we have retrieved from the FTP server :param size: The maximum size. Any files BELOW this will be accepted, any EQUAL or ABOVE will be rejected :return: Two lists, containing sanitised and large files respectively """ sanitised_files = [] large_files = [] for file in files: try: # If the file is smaller than 50MiB if ftp_client.size(file) < size: sanitised_files.append(file) else: large_files.append(file) except ftplib.error_perm: print( utils.warning_message(), "Don't have permission to access", utils.color(file, None, None, "bold") + ",", "could be a directory or a file we don't have permission to access" ) self.logger.info("{FILE_COUNT} file(s) under 50mb".format( FILE_COUNT=len(sanitised_files))) self.logger.info("{FILE_COUNT} file(s) bigger or equal to 50mb".format( FILE_COUNT=len(large_files))) return sanitised_files, large_files
def format(self, record: logging.LogRecord) -> str: """ Custom formatter for the stdout logging :param record: The Log to format :return: The formatted string """ # TODO: Fix cyclic imports from core.utils import warning_message, error_message, normal_message from core.ArgHandler import get_verbose, get_very_verbose if record.levelno == logging.WARNING: if get_verbose() or get_very_verbose(): return warning_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) else: return warning_message() + ' {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) if record.levelno >= logging.ERROR: return error_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage()) return normal_message() + ' [{NAME}] {MESSAGE}'.format(NAME=record.name, MESSAGE=record.getMessage())
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 scan_target(target: str) -> None: """ Scan the target passed :param target: Target IP/hostname to scan """ try: # See if the target is an IP network ip_network = ipaddress.ip_network(target, strict=False) if ip_network.version is 6: raise NotImplementedError("IPv6 addresses are not yet supported") for x in range(ip_network.num_addresses): ip = ip_network[x] tgt = Target(None, ip) ModuleProvider.analyse(tgt) tgt.stop_timer() except ValueError: # It's not an IP address or a subnet, # so most likely a hostname if target.startswith("www."): www_warning = utils.terminal_width_string( "Target starts with \"www.\" - this is not recommended as it can lead to false positives in modules " " - for example, when checking URLs for internal links. Do you want to remove \"www.\" from the URL?" ) print(utils.warning_message(), www_warning) agree = utils.input_message("[Y]es or [N]o: ") if agree.lower() == "y": target = target.replace("www.", "") print(utils.normal_message(), "Removed \"www.\" from target, now \"{TARGET}\"".format(TARGET=target)) else: print(utils.warning_message(), "Retaining \"www.\" in target") hostname_info = socket.getaddrinfo(target, None, socket.AF_INET) ip = ipaddress.ip_address(hostname_info[0][4][0]) tgt = Target(target, ip) ModuleProvider.analyse(tgt) tgt.stop_timer() print()
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 init(): """ Initialise all of the needed prerequisites for Lancer. This should: - Register the signal handler for Ctrl+C - Load the config file - Parse command line arguments - Show the header - Check that we're on a supported Python version - Show an option to update the VirtualTerminal registry key if on Win 10 - Show a warning that localisation support is not yet implemented if there is a non-default -l parameter - Display a legal disclaimer about using Lancer for illegal use - Warn if the cache is over 500mb in size - Clear the cache if we want to """ # Register the signal handler for a more graceful Ctrl+C signal.signal(signal.SIGINT, utils.signal_handler) # Load the config file config.load_config() # Parse the arguments ArgHandler.parse_arguments(sys.argv[1:]) # Display the header utils.display_header() time.sleep(1.25) # Check we're on a supported Python version utils.python_version() # Update the Windows virtual terminal if necessary # If we're on Windows 10, import winutils if platform.system().lower() == "windows" and platform.release() == "10": from core.winutils import update_windows_virtual_terminal update_windows_virtual_terminal() # Language warning - not yet implemented if ArgHandler.get_language_code() != 'en': print(utils.error_message(), "Multi-language support is not yet implemented...") # Show a legal disclaimer disclaimer = utils.terminal_width_string( "Legal Disclaimer: Usage of Lancer for attacking targets without prior mutual" " authorisation is illegal. It is the end user's responsibility to adhere to all local" " and international laws. The developers of this tool assume no liability and are not" " responsible for any misuse or damage caused by the use of this program." ) print(utils.error_message(), disclaimer) print() # Cache warning # If it is more than 1GB, we display an error-style warning root_directory = Path(config.get_cache_path()) size = sum(f.stat().st_size for f in root_directory.glob('**/*') if f.is_file()) / 1048576 # Bytes -> MB if size >= 2048: print(utils.error_message(), "Cache is {SIZE}gb in size. It is recommended to clear it with --clear-cache." .format(SIZE="{:.1f}".format(size/1024))) # If it is more than 500, we display a warning elif size >= 512: print(utils.warning_message(), "Cache is {SIZE}mb in size. You can clear it with --clear-cache." .format(SIZE="{:.1f}".format(size))) # Clear the cache if ArgHandler.get_clear_cache(): files = os.listdir(config.get_cache_path()) for filename in files: file_path = os.path.join(config.get_cache_path(), filename) if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path) and file_path != config.get_current_cache_path(): shutil.rmtree(file_path) print(utils.normal_message(), "Removed {NUM} items from the cache".format(NUM=len(files))) # Check if we are admin, display a relevant message if utils.is_user_admin(): print(utils.normal_message(), "Lancer running with elevated permissions") else: non_admin_warning = utils.terminal_width_string("Lancer doesn't appear to being run with elevated" " permissions. Some functionality may not work" " correctly") print(utils.warning_message(), non_admin_warning) # Display warning about your IP address ip_address = utils.terminal_width_string( "Your IP Address has been detected as {IP}. This can be changed with -a [IP]" ) print(utils.normal_message(), ip_address.format(IP=get_ip())) print() # Preload all of the modules ModuleProvider.load()
def test_warning_message(): out = utils.warning_message() assert "[*]" in out
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))