diff --git a/api_calls/censys.py b/api_calls/censys.py index 390927d..2a91842 100644 --- a/api_calls/censys.py +++ b/api_calls/censys.py @@ -15,13 +15,14 @@ class CensysAPIHook(object): Censys API hook """ - def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None, **kwargs): + def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None, save_mode=None, **kwargs): self.id = identity self.token = token self.query = query self.proxy = proxy self.user_agent = agent self.host_file = HOST_FILE + self.save_mode = save_mode def censys(self): """ @@ -38,7 +39,7 @@ def censys(self): json_data = req.json() for item in json_data["results"]: discovered_censys_hosts.add(str(item["ip"])) - write_to_file(discovered_censys_hosts, self.host_file) + write_to_file(discovered_censys_hosts, self.host_file, mode=self.save_mode) return True except Exception as e: raise AutoSploitAPIConnectionError(str(e)) \ No newline at end of file diff --git a/api_calls/shodan.py b/api_calls/shodan.py index 33a9277..ff8b68f 100644 --- a/api_calls/shodan.py +++ b/api_calls/shodan.py @@ -17,12 +17,13 @@ class ShodanAPIHook(object): Shodan API hook, saves us from having to install another dependency """ - def __init__(self, token=None, query=None, proxy=None, agent=None, **kwargs): + def __init__(self, token=None, query=None, proxy=None, agent=None, save_mode=None, **kwargs): self.token = token self.query = query self.proxy = proxy self.user_agent = agent self.host_file = HOST_FILE + self.save_mode = save_mode def shodan(self): """ @@ -38,7 +39,7 @@ def shodan(self): json_data = json.loads(req.content) for match in json_data["matches"]: discovered_shodan_hosts.add(match["ip_str"]) - write_to_file(discovered_shodan_hosts, self.host_file) + write_to_file(discovered_shodan_hosts, self.host_file, mode=self.save_mode) return True except Exception as e: raise AutoSploitAPIConnectionError(str(e)) diff --git a/api_calls/zoomeye.py b/api_calls/zoomeye.py index baf4ccf..eea3b01 100644 --- a/api_calls/zoomeye.py +++ b/api_calls/zoomeye.py @@ -20,13 +20,14 @@ class ZoomEyeAPIHook(object): so we're going to use some 'lifted' credentials to login for us """ - def __init__(self, query=None, proxy=None, agent=None, **kwargs): + def __init__(self, query=None, proxy=None, agent=None, save_mode=None, **kwargs): self.query = query self.host_file = HOST_FILE self.proxy = proxy self.user_agent = agent self.user_file = "{}/etc/text_files/users.lst".format(os.getcwd()) self.pass_file = "{}/etc/text_files/passes.lst".format(os.getcwd()) + self.save_mode = save_mode @staticmethod def __decode(filepath): @@ -81,7 +82,7 @@ def zoomeye(self): discovered_zoomeye_hosts.add(ip) else: discovered_zoomeye_hosts.add(str(item["ip"][0])) - write_to_file(discovered_zoomeye_hosts, self.host_file) + write_to_file(discovered_zoomeye_hosts, self.host_file, mode=self.save_mode) return True except Exception as e: raise AutoSploitAPIConnectionError(str(e)) diff --git a/autosploit/main.py b/autosploit/main.py index e3838ce..2e95f67 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -19,7 +19,10 @@ EXPLOIT_FILES_PATH, START_SERVICES_PATH ) -from lib.jsonize import load_exploits +from lib.jsonize import ( + load_exploits, + load_exploit_file +) def main(): @@ -73,8 +76,17 @@ def main(): info("attempting to load API keys") loaded_tokens = load_api_keys() AutoSploitParser().parse_provided(opts) - misc_info("checking if there are multiple exploit files") - loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) + + loaded_exploits = [] + if not opts.exploitFile: + misc_info("checking if there are multiple exploit files") + loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) + else: + loaded_exploits = load_exploit_file(opts.exploitFile) + misc_info("Loaded {} exploits from {}.".format( + len(loaded_exploits), + opts.exploitFile)) + AutoSploitParser().single_run_args(opts, loaded_tokens, loaded_exploits) else: warning("no arguments have been parsed, defaulting to terminal session. press 99 to quit and help to get help") diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 55425a2..f662d85 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -41,6 +41,11 @@ def optparser(): help="use shodan.io as the search engine to gather hosts") se.add_argument("-a", "--all", action="store_true", dest="searchAll", help="search all available search engines to gather hosts") + save_results_args = se.add_mutually_exclusive_group(required=False) + save_results_args.add_argument("-O", "--overwrite", action="store_true", dest="overwriteHosts", + help="When specified, start from scratch by overwriting the host file with new search results.") + save_results_args.add_argument("-A", "--append", action="store_true", dest="appendHosts", + help="When specified, append discovered hosts to the host file.") req = parser.add_argument_group("requests", "arguments to edit your requests") req.add_argument("--proxy", metavar="PROTO://IP:PORT", dest="proxyConfig", @@ -59,6 +64,10 @@ def optparser(): help="set the configuration for MSF (IE -C default 127.0.0.1 8080)") exploit.add_argument("-e", "--exploit", action="store_true", dest="startExploit", help="start exploiting the already gathered hosts") + exploit.add_argument("-d", "--dry-run", action="store_true", dest="dryRun", + help="Do not launch metasploit's exploits. Do everything else. msfconsole is never called.") + exploit.add_argument("-f", "--exploit-file-to-use", metavar="PATH", dest="exploitFile", + help="Run AutoSploit with provided exploit JSON file.") misc = parser.add_argument_group("misc arguments", "arguments that don't fit anywhere else") misc.add_argument("--ruby-exec", action="store_true", dest="rubyExecutableNeeded", @@ -134,32 +143,49 @@ def single_run_args(opt, keys, loaded_modules): lib.output.error("caught IOError '{}' check the file path and try again".format(str(e))) sys.exit(0) + search_save_mode = None + if opt.overwriteHosts: + # Create a new empty file, overwriting the previous one. + # Set the mode to append afterwards + # This way, successive searches will start clean without + # overriding each others. + open(lib.settings.HOST_FILE, mode="w").close() + search_save_mode = "a" + elif opt.appendHosts: + search_save_mode = "a" + if opt.searchCensys: lib.output.info(single_search_msg.format("Censys")) api_searches[2]( keys["censys"][1], keys["censys"][0], - opt.searchQuery, proxy=headers[0], agent=headers[1] + opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).censys() if opt.searchZoomeye: lib.output.info(single_search_msg.format("Zoomeye")) api_searches[0]( - opt.searchQuery, proxy=headers[0], agent=headers[1] + opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).zoomeye() if opt.searchShodan: lib.output.info(single_search_msg.format("Shodan")) api_searches[1]( - keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1] + keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).shodan() if opt.searchAll: lib.output.info("searching all search engines in order") api_searches[0]( - opt.searchQuery, proxy=headers[0], agent=headers[1] + opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).zoomeye() api_searches[1]( - keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1] + keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).shodan() api_searches[2]( - keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1] + keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], + save_mode=search_save_mode ).censys() if opt.startExploit: hosts = open(lib.settings.HOST_FILE).readlines() @@ -170,5 +196,6 @@ def single_run_args(opt, keys, loaded_modules): loaded_modules, hosts, ruby_exec=opt.rubyExecutableNeeded, - msf_path=opt.pathToFramework + msf_path=opt.pathToFramework, + dryRun=opt.dryRun ).start_exploit() diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 7836e92..97e3e6e 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -15,7 +15,7 @@ def whitelist_wash(hosts, whitelist_file): """ remove IPs from hosts list that do not appear in WHITELIST_FILE """ - whitelist_hosts = open(whitelist_file).readlines() + whitelist_hosts = [x.strip() for x in open(whitelist_file).readlines() if x.strip()] lib.output.info('Found {} entries in whitelist.txt, scrubbing'.format(str(len(whitelist_hosts)))) washed_hosts = [] #return supplied hosts if whitelist file is empty @@ -23,11 +23,12 @@ def whitelist_wash(hosts, whitelist_file): return hosts else: for host in hosts: - if host in whitelist_hosts: + if host.strip() in whitelist_hosts: washed_hosts.append(host) return washed_hosts + class AutoSploitExploiter(object): sorted_modules = [] @@ -41,6 +42,7 @@ def __init__(self, configuration, all_modules, hosts=None, **kwargs): self.single = kwargs.get("single", None) self.ruby_exec = kwargs.get("ruby_exec", False) self.msf_path = kwargs.get("msf_path", None) + self.dry_run = kwargs.get("dryRun", False) def view_sorted(self): """ @@ -82,16 +84,24 @@ def start_exploit(self): "Failure Logs", "All Logs"]) + lib.output.info("Launching exploits against {hosts_len} hosts:".format(hosts_len=len(self.hosts))) + lib.output.info("{}".format((linesep+"\t").join(self.hosts))) + + win_total = 0 + fail_total = 0 + for host in self.hosts: current_host_path = path.join(current_run_path, host.strip()) makedirs(current_host_path) for mod in self.mods: - lib.output.info( - "launching exploit '{}' against host '{}'".format( - mod.strip(), host.strip() + if not self.dry_run: + lib.output.info( + "launching exploit '{}' against host '{}'".format( + mod.strip(), host.strip() + ) ) - ) + cmd_template = ( "sudo {use_ruby} {msf_path} -r {rc_script_path} -q" ) @@ -141,13 +151,18 @@ def start_exploit(self): rc_script_path=current_rc_script_path ) - output = lib.settings.cmdline(cmd) + output = [""] + if not self.dry_run: + output = lib.settings.cmdline(cmd) ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') msf_output_lines = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[.\]', x)]) msf_wins = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[\+\]', x)]) msf_fails = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[-\]', x)]) + win_total += len(msf_wins) + fail_total += len(msf_fails) + csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) csv_file.writerow([rhost, today_printable, @@ -157,3 +172,17 @@ def start_exploit(self): msf_wins, msf_fails, msf_output_lines]) + + print() + lib.output.info("********RESULTS**********") + + if self.dry_run: + lib.output.info("\tDRY RUN!") + lib.output.info("\t0 exploits run against {} hosts.".format(len(self.hosts))) + else: + lib.output.info("\t{} exploits run against {} hosts.".format(len(self.mods), len(self.hosts))) + lib.output.info("\t{} exploit successful (Check report.csv to validate!).".format(win_total)) + lib.output.info("\t{} exploit failed.".format(fail_total)) + + lib.output.info("\tExploit run saved to {}".format(str(current_run_path))) + lib.output.info("\tReport saved to {}".format(str(report_path))) diff --git a/lib/jsonize.py b/lib/jsonize.py index c32cdca..11d65e8 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -20,6 +20,27 @@ def random_file_name(acceptable=string.ascii_letters, length=7): return ''.join(list(retval)) +def load_exploit_file(path, node="exploits"): + """ + load exploits from a given file + """ + + selected_file_path = path + + retval = [] + try: + with open(selected_file_path) as exploit_file: + # loading it like this has been known to cause Unicode issues later on down + # the road + _json = json.loads(exploit_file.read()) + for item in _json[node]: + # so we'll reload it into a ascii string before we save it into the file + retval.append(str(item)) + except IOError as e: + lib.settings.close(e) + return retval + + def load_exploits(path, node="exploits"): """ load exploits from a given path, depending on how many files are loaded into diff --git a/lib/settings.py b/lib/settings.py index 2baac3a..2fce057 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -108,20 +108,33 @@ def check_services(service_name): return True - -def write_to_file(data_to_write, filename, mode="a+"): +def write_to_file(data_to_write, filename, mode=None): """ write data to a specified file, if it exists, ask to overwrite """ global stop_animation if os.path.exists(filename): - stop_animation = True - is_append = lib.output.prompt("would you like to (a)ppend or (o)verwrite the file") - if is_append == "o": - mode = "w" - elif is_append != "a": - lib.output.warning("invalid input provided ('{}'), appending to file".format(is_append)) + if not mode: + stop_animation = True + is_append = lib.output.prompt("would you like to (a)ppend or (o)verwrite the file") + if is_append.lower() == "o": + mode = "w" + elif is_append.lower() == "a": + mode = "a+" + else: + lib.output.error("invalid input provided ('{}'), appending to file".format(is_append)) + lib.output.error("Search results NOT SAVED!") + + if mode == "w": + lib.output.warning("Overwriting to {}".format(filename)) + if mode == "a": + lib.output.info("Appending to {}".format(filename)) + + else: + # File does not exists, mode does not matter + mode = "w" + with open(filename, mode) as log: if isinstance(data_to_write, (tuple, set, list)): for item in list(data_to_write): @@ -132,7 +145,7 @@ def write_to_file(data_to_write, filename, mode="a+"): return filename -def load_api_keys(path="{}/etc/tokens".format(CUR_DIR)): +def load_api_keys(unattended=False, path="{}/etc/tokens".format(CUR_DIR)): """ load the API keys from their .key files @@ -156,8 +169,8 @@ def load_api_keys(path="{}/etc/tokens".format(CUR_DIR)): else: lib.output.info("{} API token loaded from {}".format(key.title(), API_KEYS[key][0])) api_tokens = { - "censys": (open(API_KEYS["censys"][0]).read(), open(API_KEYS["censys"][1]).read()), - "shodan": (open(API_KEYS["shodan"][0]).read(), ) + "censys": (open(API_KEYS["censys"][0]).read().rstrip(), open(API_KEYS["censys"][1]).read().rstrip()), + "shodan": (open(API_KEYS["shodan"][0]).read().rstrip(), ) } return api_tokens