diff --git a/.github/.translations/README-de.md b/.github/.translations/README-de.md index e158bb1..4d28d97 100644 --- a/.github/.translations/README-de.md +++ b/.github/.translations/README-de.md @@ -152,7 +152,7 @@ misc arguments: Falls du AutoSploit auf einem System mit macOS ausführen willst, musst du das Programm trotz der Kompatibilität mit macOS in einer virtuellen Maschine ausführen, sodass es erfolgreich ausgeführt werden kann. Um dies zu tun, sind folgende Schritte nötig; ```bash -sudo -s << '_EOF' +sudo -s << '_EOF' pip2 install virtualenv --user git clone https://github.com/NullArray/AutoSploit.git virtualenv @@ -173,12 +173,13 @@ AutoSploit benötigt die folgenden Python 2.7 Module: ``` requests psutil +beautifulsoup4 ``` Wenn dir auffällt, dass du diese nicht installiert hast, kannst du sie über Pip installieren, wie nachfolgend gezeigt. ```bash -pip install requests psutil +pip install requests psutil beautifulsoup4 ``` oder diff --git a/.github/.translations/README-fr.md b/.github/.translations/README-fr.md index 05e28e1..e9c4c50 100644 --- a/.github/.translations/README-fr.md +++ b/.github/.translations/README-fr.md @@ -3,7 +3,7 @@ Comme vous pouvez l'imaginer au vu du nom de ce projet, AutoSploit automatise l'exploitation d'hôtes distantes connectées à internet. Les adresses des hôtes à attaquer sont collectées automatiquement grâce à l'aide de Shodan, Censys et Zoomeye. Vous pouvez également utiliser vos propres listes de cibles. Les modules Metasploit disponibles ont été sélectionnés afin de faciliter l'obtention d'exécution de code à distance ( Remote Code Execution, ou RCE ), qui permettent ensuite de créer des sessions terminal inversées ( reverse shell ) ou meterpreter ( via metasploit ). -**Ne soyez pas stupides** +**Ne soyez pas stupides** Recevoir les connexions de vos victimes directement sur votre ordinateur n'est pas vraiment une bonne idée. Vous devriez considérer l'option de dépenser quelques euros dans un VPS ( ou VPN ). @@ -127,12 +127,13 @@ AutoSploit exige la présence des modules Python2.7 suivants. ``` requests psutil +beautifulsoup4 ``` Si vous ne les avez pas, vous pouvez les installer avec les commandes ci-dessous ( dans le dossier d'AutoSploit ): ```bash -pip install requests psutil +pip install requests psutil beautifulsoup4 ``` ou diff --git a/Docker/Dockerfile b/Docker/Dockerfile index ae03a59..ad482c8 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,17 +1,23 @@ FROM kalilinux/kali-linux-docker -RUN apt update && apt install -y postgresql \ - apache2 \ - python-pip \ - python-dev \ - build-essential \ - git \ - metasploit-framework +RUN apt update \ + && apt install -y \ + apache2 \ + build-essential \ + git \ + metasploit-framework \ + postgresql \ + python-dev \ + python-pip + +RUN git clone https://github.com/NullArray/AutoSploit.git \ + && pip install -r AutoSploit/requirements.txt -RUN git clone https://github.com/NullArray/AutoSploit.git && pip install requests psutil COPY database.yml /root/.msf4/database.yml + WORKDIR AutoSploit + EXPOSE 80 443 4444 ENTRYPOINT ["python", "autosploit.py"] -#ENTRYPOINT ["bash"] +# ENTRYPOINT ["bash"] diff --git a/api_calls/censys.py b/api_calls/censys.py index 2a91842..1c29d3e 100644 --- a/api_calls/censys.py +++ b/api_calls/censys.py @@ -24,7 +24,7 @@ def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None self.host_file = HOST_FILE self.save_mode = save_mode - def censys(self): + def search(self): """ connect to the Censys API and pull all IP addresses from the provided query """ diff --git a/api_calls/shodan.py b/api_calls/shodan.py index ff8b68f..5db7a1a 100644 --- a/api_calls/shodan.py +++ b/api_calls/shodan.py @@ -25,7 +25,7 @@ def __init__(self, token=None, query=None, proxy=None, agent=None, save_mode=Non self.host_file = HOST_FILE self.save_mode = save_mode - def shodan(self): + def search(self): """ connect to the API and grab all IP addresses associated with the provided query """ diff --git a/api_calls/zoomeye.py b/api_calls/zoomeye.py index eea3b01..6bc2232 100644 --- a/api_calls/zoomeye.py +++ b/api_calls/zoomeye.py @@ -54,7 +54,7 @@ def __get_auth(self): token = json.loads(req.content) return token - def zoomeye(self): + def search(self): """ connect to the API and pull all the IP addresses that are associated with the given query diff --git a/autosploit/main.py b/autosploit/main.py index 151c15e..60396d3 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -69,9 +69,7 @@ def main(): ) if choice.lower().startswith("y"): try: - if "darwin" in platform_running.lower(): - cmdline("{} darwin".format(START_SERVICES_PATH)) - elif "linux" in platform_running.lower(): + if "linux" in platform_running.lower(): cmdline("{} linux".format(START_SERVICES_PATH)) else: close("your platform is not supported by AutoSploit at this time", status=2) @@ -111,16 +109,12 @@ def main(): 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 type `help` to view the help menus" - ) misc_info("checking if there are multiple exploit files") loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) info("attempting to load API keys") loaded_tokens = load_api_keys() - terminal = AutoSploitTerminal(loaded_tokens) - terminal.terminal_main_display(loaded_exploits) + terminal = AutoSploitTerminal(loaded_tokens, loaded_exploits) + terminal.terminal_main_display(loaded_tokens) except Exception as e: import traceback diff --git a/etc/scripts/start_services.sh b/etc/scripts/start_services.sh index 161888a..b34f143 100755 --- a/etc/scripts/start_services.sh +++ b/etc/scripts/start_services.sh @@ -11,21 +11,10 @@ function startPostgreSQLLinux () { sudo systemctl start postgresql > /dev/null 2>&1 } -function startApacheOSX () { - sudo apachectl start > /dev/null 2>&1 -} - -function startPostgreSQLOSX () { - brew services restart postgresql > /dev/null 2>&1 -} - function main () { if [ $1 == "linux" ]; then startApacheLinux; startPostgreSQLLinux; - elif [ $1 == "darwin" ]; then - startApacheOSX; - startPostgreSQLOSX; else echo "[*] invalid operating system"; fi diff --git a/etc/text_files/gen b/etc/text_files/gen new file mode 100644 index 0000000..6fe17af --- /dev/null +++ b/etc/text_files/gen @@ -0,0 +1,18 @@ +Usage of AutoSploit for attacking targets without prior mutual consent is illegal in pretty much every sense of the word. It is the +end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible +for any misuse or damage caused by this program or any component thereof. + +Developers do not encourage nor condone any illegal activity; + +In OffSec/RedTeam engagements it is important however to mind your operational security. With that in mind, please consider the following: + + - Use AutoSploit on a VPS through a proxy(chain) or Tor + - Keep calm and wipe/data-poison the logs or use tools to do so + - Never connect from your local IP address + - Keep a low profile, AutoSploit is loud + + +In closing, knowledge is not illegal and anybody that tells you learning is wrong is a fool. +Get as much out of this program as we got from writing it. Remember though, common sense and a sense of ethics go a long way. + +Thank you. diff --git a/lib/banner.py b/lib/banner.py index a53debe..5567e5b 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.3" +VERSION = "3.0" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 467cd5a..4ea9cb7 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -141,7 +141,25 @@ def single_run_args(opt, keys, loaded_modules): "You should take this ethical lesson into consideration " "before you continue with the use of this tool:\n\n{}\n".format(ethic)) if opt.downloadModules is not None: - print "downloading MODULES!" + import re + + modules_to_download = opt.downloadModules + links_list = "{}/etc/text_files/links.txt".format(lib.settings.CUR_DIR) + possibles = open(links_list).readlines() + for module in modules_to_download: + searcher = re.compile("{}".format(module)) + for link in possibles: + if searcher.search(link) is not None: + filename = lib.settings.download_modules(link.strip()) + download_filename = "{}.json".format(link.split("/")[-1].split(".")[0]) + download_path = "{}/etc/json".format(os.getcwd()) + current_files = os.listdir(download_path) + if download_filename not in current_files: + full_path = "{}/{}".format(download_path, download_filename) + lib.jsonize.text_file_to_dict(filename, filename=full_path) + lib.output.info("downloaded into: {}".format(download_path)) + else: + lib.output.warning("file already downloaded, skipping") if opt.exploitList: try: lib.output.info("converting {} to JSON format".format(opt.exploitList)) @@ -169,33 +187,33 @@ def single_run_args(opt, keys, loaded_modules): keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).censys() + ).search() if opt.searchZoomeye: lib.output.info(single_search_msg.format("Zoomeye")) api_searches[0]( opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).zoomeye() + ).search() 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], save_mode=search_save_mode - ).shodan() + ).search() if opt.searchAll: lib.output.info("searching all search engines in order") api_searches[0]( opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).zoomeye() + ).search() api_searches[1]( keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).shodan() + ).search() api_searches[2]( keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).censys() + ).search() if opt.startExploit: hosts = open(lib.settings.HOST_FILE).readlines() if opt.whitelist: diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index b67041b..4b0cb95 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -1,4 +1,5 @@ import re +import os import sys import json import platform @@ -165,5 +166,9 @@ def request_issue_creation(path, arguments, error_message): lib.output.error( "someone has already created this issue here: {}".format(find_url(identifier)) ) + try: + os.remove(path) + except: + pass else: lib.output.info("the issue has been logged to a file in path: '{}'".format(path)) \ No newline at end of file diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 1c675c5..a175611 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -29,7 +29,7 @@ def whitelist_wash(hosts, whitelist_file): washed_hosts.append(host) return washed_hosts - except Exception: + except IOError: lib.output.warning("unable to whitewash host list, does the file exist?") return hosts @@ -78,7 +78,11 @@ def start_exploit(self, sep="*" * 10): today_printable = datetime.datetime.today().strftime("%Y-%m-%d_%Hh%Mm%Ss") current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable) - makedirs(current_run_path) + try: + makedirs(current_run_path) + except OSError: + current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable + "(1)") + makedirs(current_run_path) report_path = path.join(current_run_path, "report.csv") with open(report_path, 'w') as f: @@ -127,14 +131,14 @@ def start_exploit(self, sep="*" * 10): "set rhost {rhost}\n" "set rhosts {rhosts}\n" "run -z\n" - "exit\n" + "exit -y\n" ) - module_name=mod.strip() - workspace=self.configuration[0] - lhost=self.configuration[1] - lport=self.configuration[2] - rhost=host.strip() + module_name = mod.strip() + workspace = self.configuration[0] + lhost = self.configuration[1] + lport = self.configuration[2] + rhost = host.strip() current_rc_script_path = path.join(current_host_path, mod.replace("/", '-').strip()) with open(current_rc_script_path, 'w') as f: diff --git a/lib/jsonize.py b/lib/jsonize.py index 5613baf..bcf6ec1 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -58,7 +58,7 @@ def load_exploits(path, node="exploits"): try: selected_file = file_list[int(action) - 1] selected = True - except Exception: + except Except: lib.output.warning("invalid selection ('{}'), select from below".format(action)) selected = False else: diff --git a/lib/settings.py b/lib/settings.py index 5fa540b..078c8b4 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -6,6 +6,7 @@ import platform import getpass import tempfile +import readline import distutils.spawn from subprocess import ( PIPE, @@ -16,9 +17,58 @@ import lib.output import lib.banner +import lib.jsonize + +class AutoSploitCompleter(object): + + """ + object to create an auto completer for the terminal + """ + + def __init__(self, opts): + self.opts = sorted(opts) + self.possibles = [] + + def complete_text(self, text, state): + if state == 0: + if text: + self.possibles = [m for m in self.opts if m.startswith(text)] + else: + self.possibles = self.opts[:] + try: + return self.possibles[state] + except IndexError: + return None + + +TERMINAL_HELP_MESSAGE = """ +COMMAND: SUMMARY: +--------- -------- +view/show Show the already gathered hosts +mem[ory]/history Display the command history +exploit/run/attack Run the exploits on the already gathered hosts +search/api/gather Search the API's for hosts +exit/quit Exit the terminal session +single Load a single host into the file +personal/custom Load a custom host file +tokens/reset Reset API tokens if needed +external View loaded external commands +help/? Display this help +""" + +# current directory CUR_DIR = "{}".format(os.getcwd()) +# home +HOME = "{}/.autosploit_home".format(os.path.expanduser("~")) + +# backup the current hosts file +HOST_FILE_BACKUP = "{}/backups".format(HOME) + +# autosploit command history file path +HISTORY_FILE_PATH = "{}/.history".format(HOME) + # path to the file containing all the discovered hosts HOST_FILE = "{}/hosts.txt".format(CUR_DIR) try: @@ -49,7 +99,7 @@ PLATFORM_PROMPT = "\n{}@\033[36mPLATFORM\033[0m$ ".format(getpass.getuser()) # the prompt that will be used most of the time -AUTOSPLOIT_PROMPT = "\n\033[31m{}\033[0m@\033[36mautosploit\033[0m# ".format(getpass.getuser()) +AUTOSPLOIT_PROMPT = "\033[31m{}\033[0m@\033[36mautosploit\033[0m# ".format(getpass.getuser()) # all the paths to the API tokens API_KEYS = { @@ -74,7 +124,7 @@ TOKEN_PATH = "{}/etc/text_files/auth.key".format(CUR_DIR) # location of error files -ERROR_FILES_LOCATION = "{}/.autosploit_errors".format(os.path.expanduser("~")) +ERROR_FILES_LOCATION = "{}/.autosploit_errors".format(HOME) # terminal options AUTOSPLOIT_TERM_OPTS = { @@ -83,14 +133,58 @@ 99: "quit" } +# global variable for the search animation stop_animation = False -def validate_ip_addr(provided): +def load_external_commands(): + """ + create a list of external commands from provided directories + """ + paths = ["/bin", "/usr/bin"] + loaded_externals = [] + for f in paths: + for cmd in os.listdir(f): + if not os.path.isdir("{}/{}".format(f, cmd)): + loaded_externals.append(cmd) + return loaded_externals + + +def backup_host_file(current, path): + """ + backup the current hosts file + """ + import datetime + import shutil + + if not os.path.exists(path): + os.makedirs(path) + new_filename = "{}/hosts_{}_{}.txt".format( + path, + lib.jsonize.random_file_name(length=22), + str(datetime.datetime.today()).split(" ")[0] + ) + shutil.copyfile(current, new_filename) + return new_filename + + +def auto_completer(keywords): + """ + function to initialize the auto complete utility + """ + completer = AutoSploitCompleter(keywords) + readline.set_completer(completer.complete_text) + readline.parse_and_bind('tab: complete') + + +def validate_ip_addr(provided, home_ok=False): """ validate an IP address to see if it is real or not """ - not_acceptable = ("0.0.0.0", "127.0.0.1", "255.255.255.255") + if not home_ok: + not_acceptable = ("0.0.0.0", "127.0.0.1", "255.255.255.255") + else: + not_acceptable = ("255.255.255.255",) if provided not in not_acceptable: try: socket.inet_aton(provided) @@ -186,7 +280,7 @@ def load_api_keys(unattended=False, path="{}/etc/tokens".format(CUR_DIR)): return api_tokens -def cmdline(command): +def cmdline(command, is_msf=True): """ send the commands through subprocess """ @@ -200,7 +294,10 @@ def cmdline(command): stdout_buff = [] for stdout_line in iter(proc.stdout.readline, b''): stdout_buff += [stdout_line.rstrip()] - print("(msf)>> {}".format(stdout_line).rstrip()) + if is_msf: + print("(msf)>> {}".format(stdout_line).rstrip()) + else: + print("{}".format(stdout_line).rstrip()) return stdout_buff @@ -328,3 +425,41 @@ def save_error_to_file(error_info, error_message, error_class): "Traceback (most recent call):\n " + error_info.strip() + "\n{}: {}".format(error_class, error_message) ) return file_path + + +def download_modules(link): + """ + download new module links + """ + import re + import requests + import tempfile + + lib.output.info('downloading: {}'.format(link)) + retval = "" + req = requests.get(link) + content = req.content + split_data = content.split(" ") + searcher = re.compile("exploit/\w+/\w+") + storage_file = tempfile.NamedTemporaryFile(delete=False) + for item in split_data: + if searcher.search(item) is not None: + retval += item + "\n" + with open(storage_file.name, 'a+') as tmp: + tmp.write(retval) + return storage_file.name + + +def find_similar(command, internal, external): + """ + find commands similar to the one provided + """ + retval = [] + first_char = command[0] + for inter in internal: + if inter.startswith(first_char): + retval.append(inter) + for exter in external: + if exter.startswith(first_char): + retval.append(exter) + return retval diff --git a/lib/term/terminal.py b/lib/term/terminal.py index cccf24c..a871e01 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,13 +1,18 @@ import os -import sys +import datetime import lib.settings import lib.output import lib.errors -import lib.exploitation.exploiter +import lib.jsonize import api_calls.shodan import api_calls.zoomeye import api_calls.censys +import lib.exploitation.exploiter +try: + raw_input +except: + input = raw_input class AutoSploitTerminal(object): @@ -16,354 +21,541 @@ class AutoSploitTerminal(object): class object for the main terminal of the program """ - def __init__(self, tokens): + internal_terminal_commands = [ + # viewing gathered hosts + "view", "show", + # displaying memory + "mem", "memory", "history", + # attacking targets + "exploit", "run", "attack", + # search API's + "search", "api", "gather", + # quit the terminal + "exit", "quit", + # single hosts + "single", + # custom hosts list + "custom", "personal", + # display help + "?", "help", + # display external commands + "external", + # reset API tokens + "reset", "tokens", + # easter eggs! + "idkwhatimdoing", "ethics", "skid" + ] + external_terminal_commands = lib.settings.load_external_commands() + api_call_pointers = { + "shodan": api_calls.shodan.ShodanAPIHook, + "zoomeye": api_calls.zoomeye.ZoomEyeAPIHook, + "censys": api_calls.censys.CensysAPIHook + } + + def __init__(self, tokens, modules): + self.history = [] + self.quit_terminal = False self.tokens = tokens - self.usage_path = lib.settings.USAGE_AND_LEGAL_PATH - self.sep = "-" * 30 - self.host_path = lib.settings.HOST_FILE + self.history_dir = "{}/{}".format(lib.settings.HISTORY_FILE_PATH, datetime.date.today()) + self.full_history_path = "{}/autosploit.history".format(self.history_dir) + self.modules = modules try: - open(lib.settings.HOST_FILE).readlines() + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() except IOError: - lib.output.warning("no hosts file present, you need to gather some hosts") - self.host_path = lib.settings.HOST_FILE - - @staticmethod - def help_menu_full(): - seperator = "-" * 30 - help_choices = [ - (0, "usage"), - (1, "view"), - (2, "single"), - (3, "exit"), - (4, "gather"), - (5, "exploit"), - (6, "custom"), - ] - print("\n{}\nPossible help choices:".format(seperator)) - for i, _ in enumerate(help_choices): - print("{}- `help {}`".format(" " * 3, help_choices[i][1])) - print("{}\n".format(seperator)) - - def usage_and_legal(self): - """ - shows a display of the output and legal information that resides - in the etc/text_files/general file. + lib.output.warning("no hosts file present") + self.loaded_hosts = open(lib.settings.HOST_FILE, "a+").readlines() - option 1 must be provided to display - """ - lib.output.info("preparing to display usage and legal") - with open(self.usage_path) as usage: - print(usage.read().strip()) + def __reload(self): + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() - def help(self, command): + def reflect_memory(self, max_memory=100): """ - print the help of the commands + reflect the command memory out of the history file """ - help_dict = { - "usage": self.usage_and_legal, - "view": self.view_gathered_hosts, - "single": self.add_single_host, - "exit": self.quit, - "gather": self.gather_hosts, - "exploit": self.exploit_gathered_hosts, - "custom": self.custom_host_list - } - for key in help_dict.keys(): - if command == key: - lib.output.info("help found for provided argument:") - print(self.sep) - print(help_dict[key].__doc__) - print(self.sep) - break - else: - lib.output.warning("unable to find help for provided command '{}'".format(command)) - lib.output.info("available helps '{}'".format( - ", ".join([k for k in help_dict.keys()]) - )) + if os.path.exists(self.history_dir): + tmp = [] + try: + with open(self.full_history_path) as history: + for item in history.readlines(): + tmp.append(item.strip()) + except: + pass + if len(tmp) == 0: + lib.output.warning("currently no history") + elif len(tmp) > max_memory: + import shutil - def view_gathered_hosts(self): + history_file_backup_path = "{}.{}.old".format( + self.full_history_path, + lib.jsonize.random_file_name(length=12) + ) + shutil.copy(self.full_history_path, history_file_backup_path) + os.remove(self.full_history_path) + open(self.full_history_path, 'a+').close() + lib.output.misc_info("history file to large, backed up under '{}'".format(history_file_backup_path)) + else: + for cmd in tmp: + self.history.append(cmd) + + def do_display_history(self): + """ + display the history from the history files """ - print a list of all available hosts in the hosts.txt file + for i, item in enumerate(self.history, start=1): + if len(list(str(i))) == 2: + spacer1, spacer2 = " ", " " + elif len(list(str(i))) == 3: + spacer1, spacer2 = " ", " " + else: + spacer1, spacer2 = " ", " " + print("{}{}{}{}".format(spacer1, i, spacer2, item)) - option 5 must be provided + def get_choice(self): + """ + get the provided choice and return a tuple of options and the choice """ - lib.output.info("loading gathered hosts from '{}'".format(self.host_path)) + original_choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) try: - with open(self.host_path) as hosts: - for host in hosts.readlines(): - # should take care of some Unicode errors that occur - lib.output.info(str(host.strip())) - except IOError: - lib.output.warning("hosts file doesn't exist, looks like you haven't gathered any") + choice_checker = original_choice.split(" ")[0] + except: + choice_checker = original_choice + if choice_checker in self.internal_terminal_commands: + retval = ("internal", original_choice) + elif choice_checker in self.external_terminal_commands: + retval = ("external", original_choice) + else: + retval = ("unknown", original_choice) + return retval - def add_single_host(self): + def do_display_external(self): """ - add a singular host to the hosts.txt file and check if the host - will resolve to a true IP address, if it is not a true IP address - you will be re-prompted for an IP address - - option 4 must be provided + display all external commands """ - provided = False - while not provided: - new_host = lib.output.prompt("enter the host IP you wish to add", lowercase=False) - if not lib.settings.validate_ip_addr(new_host): - lib.output.warning("provided host does not appear to be a true IP, try again") - else: - with open(self.host_path, "a+") as hosts: - hosts.write(new_host + os.linesep) - lib.output.info("successfully wrote provided host to {}".format(self.host_path)) - break + print(" ".join(self.external_terminal_commands)) - def quit(self, status): + def do_terminal_command(self, command): """ - quits the terminal and exits the program entirely - - option 99 must be provided + run a terminal command """ - lib.output.error("aborting terminal session") - assert isinstance(status, int) - sys.exit(status) + lib.settings.cmdline(command, is_msf=False) - def gather_hosts(self, query, given_choice=None, proxy=None, agent=None): + def do_token_reset(self, api, token, username): """ - gather hosts from either Shodan, Zoomeye, Censys, or multiple - by providing a comma between integers. + Explanation: + ------------ + Reset the API tokens when needed, this will overwrite the existing + API token with a provided one + + Parameters: + ----------- + :param api: name of the API to reset + :param token: the token that will overwrite the current token + :param username: if resetting Censys this will be the user ID token - option 2 must be provided + Examples: + --------- + Censys -> reset/tokens censys + Shodan -> reset.tokens shodan """ - choice_dict = { - 1: api_calls.shodan.ShodanAPIHook, - 2: api_calls.zoomeye.ZoomEyeAPIHook, - 3: api_calls.censys.CensysAPIHook - } - searching = False - if given_choice is None: - lib.output.info("please choose an API to gather from (choosing two or more " - "separate by comma IE; 1,2)") - for i, api in enumerate(lib.settings.API_URLS.keys(), start=1): - print("{}. {}".format(i, api.title())) - choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) + if api.lower() == "censys": + lib.output.info("resetting censys API credentials") + with open(lib.settings.API_KEYS["censys"][0], 'w') as token_: + token_.write(token) + with open(lib.settings.API_KEYS["censys"][1], 'w') as username_: + username_.write(username) else: - choice = given_choice - while not searching: - # TODO[2]:// bug in the animation, if the user chooses one search engine to search - # the animation does not stop when the user chooses a single search engine, instead - # the user will see the animation continuously until they either: - # A) exit the terminal - # B) search another search engine - try: - # something in here needs to change (see TODO[2]) - choice = int(choice) - if choice == 1: - choice_dict[choice]( - self.tokens["shodan"][0], query, proxy=proxy, agent=agent - ).shodan() - break - elif choice == 2: - choice_dict[choice](query, proxy=proxy, agent=agent).zoomeye() - break - elif choice == 3: - choice_dict[choice]( - self.tokens["censys"][1], self.tokens["censys"][0], query, - proxy=proxy, agent=agent - ).censys() - break - else: - lib.output.warning("invalid option provided, going back to main menu") - break - except (ValueError, KeyError): - if "," in choice: - for i in choice.split(","): - if int(i) in choice_dict.keys(): - self.gather_hosts(query, given_choice=int(i), proxy=proxy, agent=agent) - else: - lib.output.warning("invalid option, skipping") - break - break - else: - lib.output.warning("must be integer between 1-{} not string".format(len(lib.settings.API_URLS.keys()))) - self.gather_hosts(query, proxy=proxy, agent=agent) - except Exception as e: - lib.settings.stop_animation = True - lib.output.error("unable to search API got error: {}".format(str(e))) - break - - def exploit_gathered_hosts(self, loaded_mods, hosts=None): + with open(lib.settings.API_KEYS["shodan"][0], 'w') as token_: + token_.write(token) + lib.output.warning("program must be restarted for the new tokens to initialize") + + def do_api_search(self, requested_api_data, query, tokens): """ - exploit already gathered hosts from the hosts.txt file + Explanation: + ------------ + Search the API with a provided query for potentially exploitable hosts. - option 6 must be provided + Parameters: + ----------- + :param requested_api_data: data to be used with the API tuple of info + :param query: the query to be searched + :param tokens: an argument dict that will contain the token information + + Examples: + --------- + search/api/gather shodan[,censys[,zoomeye]] windows 10 """ - ruby_exec = False - msf_path = None - whitelist_file = lib.output.prompt("specify full path to a whitelist file, otherwise hit enter", lowercase=False) - if hosts is None: - if whitelist_file is not "" and not whitelist_file.isspace(): - # If whitelist is specified, return a washed hosts list - host_file = lib.exploitation.exploiter.whitelist_wash(open(self.host_path).readlines(), whitelist_file) - else: - try: - host_file = open(self.host_path).readlines() - except Exception: - sys.stdout.flush() - lib.output.error("no host file is present, did you gather hosts?") - return - else: - if whitelist_file is not "" and not whitelist_file.isspace(): - # If whitelist is specified, return a washed hosts list - host_file = lib.exploitation.exploiter.whitelist_wash(open(hosts).readlines(), whitelist_file) + acceptable_api_names = ("shodan", "censys", "zoomeye") + api_checker = lambda l: all(i.lower() in acceptable_api_names for i in l) + + try: + if len(query) < 1: + query = "".join(query) else: - host_file = open(hosts).readlines() - if not lib.settings.check_for_msf(): - msf_path = lib.output.prompt( - "it appears that MSF is not in your PATH, provide the full path to msfconsole" - ) - ruby_exec = True - lib.output.info( - "you will need to do some configuration to MSF.\n" - "please keep in mind that sending connections back to " - "your local host is probably not a smart idea." - ) - configuration = ( - lib.output.prompt("enter your workspace name", lowercase=False), - lib.output.prompt("enter your LHOST", lowercase=False), - lib.output.prompt("enter your LPORT", lowercase=False) - ) - exploiter = lib.exploitation.exploiter.AutoSploitExploiter( - configuration, - loaded_mods, - hosts=host_file, - ruby_exec=ruby_exec, - msf_path=msf_path - ) + query = " ".join(query) + except: + query = query + + if query == "" or query.isspace(): + lib.output.warning("looks like you forgot the query") + return try: - sorted_mods = exploiter.sort_modules_by_query() - choice = lib.output.prompt( - "a total of {} modules have been sorted by relevance, would you like to display them[y/N]".format( - len(sorted_mods) - ) + api_list = requested_api_data.split(",") + except: + api_list = [requested_api_data] + prompt_for_save = len(open(lib.settings.HOST_FILE).readlines()) != 0 + if prompt_for_save: + save_mode = lib.output.prompt( + "would you like to [a]ppend or [o]verwrite the file[a/o]", lowercase=True ) + if save_mode.startswith("o"): + backup = lib.settings.backup_host_file(lib.settings.HOST_FILE, lib.settings.HOST_FILE_BACKUP) + lib.output.misc_info("current host file backed up under: '{}'".format(backup)) + save_mode = "w" + else: + if not any(save_mode.startswith(s) for s in ("a", "o")): + lib.output.misc_info("provided option is not valid, defaulting to 'a'") + save_mode = "a+" + else: + save_mode = "a+" - if not choice.lower().strip().startswith("y"): - mods = lib.output.prompt("use relevant modules[y/N]") - if mods.lower().startswith("n"): - lib.output.info( - "starting exploitation with all loaded modules (total of {})".format(len(loaded_mods))) - exploiter.start_exploit() - elif mods.lower().startswith("y"): - lib.output.info("starting exploitation with sorted modules (total of {})".format(len(sorted_mods))) - exploiter.start_exploit() + proxy = lib.output.prompt("enter your proxy or press enter for none", lowercase=False) + if proxy.isspace() or proxy == "": + proxy = {"http": "", "https": ""} + else: + proxy = {"http": proxy, "https": proxy} + agent = lib.output.prompt("use a [r]andom User-Agent or the [d]efault one[r/d]", lowercase=True) + if agent.startswith("r"): + agent = {"User-Agent": lib.settings.grab_random_agent()} + elif agent.startswith("d"): + agent = {"User-Agent": lib.settings.DEFAULT_USER_AGENT} + else: + lib.output.warning("invalid option, using default") + agent = {"User-Agent": lib.settings.DEFAULT_USER_AGENT} + for api in api_list: + res = api_checker([api]) + if not res: + lib.output.error( + "API: '{}' is not a valid API, will be skipped".format(api) + ) else: - exploiter.view_sorted() - mods = lib.output.prompt("use relevant modules[y/N]") - if mods.lower().startswith("n"): - lib.output.info( - "starting exploitation with all loaded modules (total of {})".format(len(loaded_mods))) - exploiter.start_exploit() - elif mods.lower().startswith("y"): - lib.output.info("starting exploitation with sorted modules (total of {})".format(len(sorted_mods))) - exploiter.start_exploit() - except AttributeError: - lib.output.warning("unable to sort modules by relevance") - - def custom_host_list(self, mods): + with open(lib.settings.QUERY_FILE_PATH, "a+") as tmp: + tmp.write(query) + lib.output.info( + "starting search on API {} using query: '{}'".format(api, query) + ) + try: + self.api_call_pointers[api.lower()]( + token=tokens["shodan"][0] if api == "shodan" else tokens["censys"][0], + identity=tokens["censys"][1] if api == "censys" else "", + query=query, + save_mode=save_mode, + proxy=proxy, + agent=agent + ).search() + except lib.errors.AutoSploitAPIConnectionError as e: + lib.settings.stop_animation = True + lib.output.error("error searching API: '{}', error message: '{}'".format(api, str(e))) + lib.settings.stop_animation = True + + def do_display_usage(self): + """ + display the full help menu """ - provided a custom host list that will be used for exploitation + print(lib.settings.TERMINAL_HELP_MESSAGE) - option 3 must be provided + def do_view_gathered(self): """ - provided_host_file = lib.output.prompt("enter the full path to your host file", lowercase=False) - self.exploit_gathered_hosts(mods, hosts=provided_host_file) + view the gathered hosts + """ + if len(self.loaded_hosts) != 0: + for host in self.loaded_hosts: + lib.output.info(host.strip()) + else: + lib.output.warning("currently no gathered hosts") + + def do_add_single_host(self, ip): + """ + Explanation: + ------------ + Add a single host by IP address + + Parameters: + ----------- + :param ip: IP address to be added + + Examples: + --------- + single 89.76.12.124 + """ + validated_ip = lib.settings.validate_ip_addr(ip) + if not validated_ip: + lib.output.error("provided IP '{}' is invalid, try again".format(ip)) + else: + with open(lib.settings.HOST_FILE, "a+") as hosts: + hosts.write(ip + "\n") + lib.output.info("host '{}' saved to hosts file".format(ip)) - def terminal_main_display(self, loaded_mods): + def do_quit_terminal(self, save_history=True): """ - main output of the terminal + quit the terminal and save the command history """ + self.quit_terminal = True + if save_history: + if not os.path.exists(self.history_dir): + os.makedirs(self.history_dir) + lib.output.misc_info("saving history") + with open(self.full_history_path, "a+") as hist: + for item in self.history: + hist.write(item + "\n") + lib.output.info("exiting terminal session") - def __config_headers(): - proxy = lib.output.prompt("enter your proxy (blank for none)", lowercase=False) - agent = lib.output.prompt( - "do you want to use a (p)ersonal user agent, a (r)andom one, or (d)efault" + def do_exploit_targets(self, workspace_info): + """ + Explanation: + ------------ + Exploit the already gathered hosts inside of the hosts.txt file + + Parameters: + ----------- + :param workspace_info: a tuple of workspace information + + Examples: + --------- + exploit/run/attack 127.0.0.1 9065 default [whitewash list] + """ + if workspace_info[-1] is not None: + lib.output.misc_info("doing whitewash on hosts file") + lib.exploitation.exploiter.whitelist_wash( + open(lib.settings.HOST_FILE).readlines(), + workspace_info[-1] ) - if proxy == "" or proxy.isspace(): - proxy = None - if agent.lower().startswith("p"): - agent = lib.output.prompt("enter your User-Agent", lowercase=False) - elif agent.lower().startswith("r"): - agent = lib.settings.grab_random_agent() - elif agent.lower().startswith("d"): - agent = None + else: + if not lib.settings.check_for_msf(): + msf_path = lib.output.prompt( + "metasploit is not in your PATH, provide the full path to it", lowercase=False + ) + ruby_exec = True else: - lib.output.warning("invalid argument, default will be selected") - agent = None - proxy, agent = lib.settings.configure_requests(proxy=proxy, agent=agent) - return proxy, agent + msf_path = None + ruby_exec = False + + sort_mods = lib.output.prompt( + "sort modules by relevance to last query[y/N]", lowercase=True + ) + + try: + if sort_mods.lower().startswith("y"): + mods_to_use = lib.exploitation.exploiter.AutoSploitExploiter( + None, None + ).sort_modules_by_query() + else: + mods_to_use = self.modules + except Exception: + lib.output.error("error sorting modules defaulting to all") + mods_to_use = self.modules + + view_modules = lib.output.prompt("view sorted modules[y/N]", lowercase=True) + if view_modules.startswith("y"): + for mod in mods_to_use: + lib.output.misc_info(mod.strip()) + lib.output.prompt("press enter to start exploitation phase") + lib.output.info("starting exploitation phase") + lib.exploitation.exploiter.AutoSploitExploiter( + configuration=workspace_info[0:3], + all_modules=mods_to_use, + hosts=open(lib.settings.HOST_FILE).readlines(), + msf_path=msf_path, + ruby_exec=ruby_exec + ).start_exploit() + + def do_load_custom_hosts(self, file_path): + """ + Explanation: + ----------- + Load a custom exploit file, this is useful to attack already gathered hosts + instead of trying to gather them again from the backup host files inside + of the `.autosploit_home` directory + + Parameters: + ----------- + :param file_path: the full path to the loadable hosts file - selected = False + Examples: + --------- + custom/personal /some/path/to/myfile.txt + """ + import shutil try: - while not selected: - for i in lib.settings.AUTOSPLOIT_TERM_OPTS.keys(): - print("{}. {}".format(i, lib.settings.AUTOSPLOIT_TERM_OPTS[i].title())) - choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) - # TODO[3] this is ugly so it needs to change + open("{}".format(file_path)).close() + except IOError: + lib.output.error("file does not exist, check the path and try again") + return + lib.output.warning("overwriting hosts file with provided, and backing up current") + backup_path = lib.settings.backup_host_file(lib.settings.HOST_FILE, lib.settings.HOST_FILE_BACKUP) + shutil.copy(file_path, lib.settings.HOST_FILE) + lib.output.info("host file replaced, backup stored under '{}'".format(backup_path)) + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + + def terminal_main_display(self, tokens, extra_commands=None, save_history=True): + """ + terminal main display + """ + lib.output.warning( + "no arguments have been passed, dropping into terminal session. " + "to get help type `help` to quit type `exit/quit` to get help on " + "a specific command type `command help`" + ) + + if extra_commands is not None: + for command in extra_commands: + self.external_terminal_commands.append(command) + self.reflect_memory() + while not self.quit_terminal: + try: + lib.settings.auto_completer(self.internal_terminal_commands) try: - choice = int(choice) - if choice == 99: - print(self.sep) - self.quit(0) - print(self.sep) - elif choice == 6: - print(self.sep) - self.exploit_gathered_hosts(loaded_mods) - print(self.sep) - elif choice == 5: - print(self.sep) - self.view_gathered_hosts() - print(self.sep) - elif choice == 4: - print(self.sep) - self.add_single_host() - print(self.sep) - elif choice == 3: - print(self.sep) - self.custom_host_list(loaded_mods) - print(self.sep) - elif choice == 2: - print(self.sep) - query = lib.output.prompt("enter your search query", lowercase=False) - try: - with open(lib.settings.QUERY_FILE_PATH, "w") as _query: - _query.write(query) - except AttributeError: - import tempfile # oooops - filename = tempfile.NamedTemporaryFile(delete=False).name - with open(filename, "w") as _query: - _query.write(query) - lib.settings.QUERY_FILE_PATH = filename - proxy, agent = __config_headers() - # possibly needs to change here (see TODO[2]) - self.gather_hosts(query, proxy=proxy, agent=agent) - print(self.sep) - elif choice == 1: - print(self.sep) - self.usage_and_legal() - else: - lib.output.warning("invalid option provided") - except ValueError: - if not choice == "help": - if "help" in choice: - try: - help_arg = choice.split(" ") - self.help(help_arg[-1]) - except: - lib.output.error("choice must be integer not string") + choice_type, choice = self.get_choice() + if choice_type == "unknown": + sims = lib.settings.find_similar( + choice, + self.internal_terminal_commands, + self.external_terminal_commands + ) + if len(sims) != 0: + max_sims_display = 7 + print( + "no command '{}' found, but there {} {} similar command{}".format( + choice, + "are" if len(sims) > 1 else "is", + len(sims), + "s" if len(sims) > 1 else "" + ) + ) + if len(sims) > max_sims_display: + print("will only display top {} results".format(max_sims_display)) + for i, cmd in enumerate(sims, start=1): + if i == max_sims_display: + break + print(cmd) + print("{}: command not found".format(choice)) else: - lib.output.warning("option must be integer not string") - elif choice == "help": - AutoSploitTerminal.help_menu_full() + print("{} command not found".format(choice)) + self.history.append(choice) + elif choice_type == "external": + self.do_terminal_command(choice) + self.history.append(choice) + else: + try: + choice_data_list = choice.split(" ") + if choice_data_list[-1] == "": + choice_data_list = None + except: + choice_data_list = None + if choice == "?" or choice == "help": + self.do_display_usage() + elif any(c in choice for c in ("external",)): + self.do_display_external() + elif any(c in choice for c in ("history", "mem", "memory")): + self.do_display_history() + elif any(c in choice for c in ("exit", "quit")): + self.do_quit_terminal(save_history=save_history) + elif any(c in choice for c in ("view", "gathered")): + self.do_view_gathered() + elif "single" in choice: + if "help" in choice_data_list: + print(self.do_add_single_host.__doc__) + + if choice_data_list is None or len(choice_data_list) == 1: + lib.output.error("must provide host IP after `single` keyword (IE single 89.65.78.123)") + else: + self.do_add_single_host(choice_data_list[-1]) + elif any(c in choice for c in ("exploit", "run", "attack")): + if "help" in choice_data_list: + print(self.do_exploit_targets.__doc__) + if len(choice_data_list) < 4: + lib.output.error( + "must provide at least LHOST, LPORT, workspace name with `{}` keyword " + "(IE {} 127.0.0.1 9076 default [whitelist-path])".format( + choice, choice + ) + ) + else: + if lib.settings.validate_ip_addr(choice_data_list[1], home_ok=True): + try: + workspace = ( + choice_data_list[1], choice_data_list[2], + choice_data_list[3], choice_data_list[4] + ) + except IndexError: + workspace = ( + choice_data_list[1], choice_data_list[2], + choice_data_list[3], None + ) + self.do_exploit_targets(workspace) + else: + lib.output.warning( + "heuristics could not validate provided IP address, " + "did you type it right?" + ) + elif any(c in choice for c in ("personal", "custom")): + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + if len(choice_data_list) == 1: + lib.output.error("must provide full path to file after `{}` keyword".format(choice)) + else: + self.do_load_custom_hosts(choice_data_list[-1]) + elif any(c in choice for c in ("search", "api", "gather")): + if "help" in choice_data_list: + print(self.do_api_search.__doc__) + + if len(choice_data_list) < 3: + lib.output.error( + "must provide a list of API names after `{}` keyword and query " + "(IE {} shodan,censys apache2)".format( + choice, choice + ) + ) + else: + self.do_api_search(choice_data_list[1], choice_data_list[2:], tokens) + elif any(c in choice for c in ("idkwhatimdoing", "ethics", "skid")): + import random + + if choice == "ethics" or choice == "idkwhatimdoing": + ethics_file = "{}/etc/text_files/ethics.lst".format(os.getcwd()) + other_file = "{}/etc/text_files/gen".format(os.getcwd()) + with open(ethics_file) as ethics: + ethic = random.choice(ethics.readlines()).strip() + lib.output.info("take this ethical lesson into consideration before proceeding:") + print("\n{}\n".format(ethic)) + lib.output.warning(open(other_file).read()) + else: + lib.output.warning("hack to learn, don't learn to hack") + elif any(c in choice for c in ("tokens", "reset")): + acceptable_api_names = ("shodan", "censys") + + if "help" in choice_data_list: + print(self.do_token_reset.__doc__) - except KeyboardInterrupt: - print("\n") - self.terminal_main_display(loaded_mods) + if len(choice_data_list) < 3: + lib.output.error( + "must supply API name with `{}` keyword along with " + "new token (IE {} shodan mytoken123 [userID (censys)])".format( + choice, choice + ) + ) + else: + if choice_data_list[1].lower() in acceptable_api_names: + try: + api, token, username = choice_data_list[1], choice_data_list[2], choice_data_list[3] + except IndexError: + api, token, username = choice_data_list[1], choice_data_list[2], None + self.do_token_reset(api, token, username) + else: + lib.output.error("cannot reset {} API credentials".format(choice)) + self.history.append(choice) + self.__reload() + except KeyboardInterrupt: + lib.output.warning("use the `exit/quit` command to end terminal session") + except IndexError: + pass \ No newline at end of file