Skip to content

Commit 8277b0e

Browse files
SeloraEkultek
authored andcommitted
Autosploit automation (#132)
* API keys token file reading error fix When reading an API key file, tokens are not stripped properly. Non-standard endlines causes an error with shodan/censys/etc APIs * Added a dry-run flag. When running without the terminal and the --dry-run flag, msfconsole will not be run. A report will still be produced. * Sanitized whitelist comparision with host file. All leading and trailing whitespaces should be removed before comparing IPs * Added an --exploit-file-to-use option. Load exploits directly from the specified file, do not prompt for exploit-file selection if this option is specified. * Added --append/--overwrite to search engines. Specifying either will skip the prompt after a search query. --overwrite will start with a blank file but will append futher searches ex: with -s -c --overwrite, both shodan and censys results will be appended to a clean file. * Search all fix for append/overwrite flags. Search results is not prompted anymore * Modified the Exploiter output. Added a tally at the end. Suppressed much of the output during a dry-run. * Bugfix, --exploit-file-to-use Output an error message to the console if the specified exploit file does not exists. * Added short arguments for --append/--overwrite * Closing program if invalid file is passed to --exploit-file-to-use
1 parent f0880ac commit 8277b0e

File tree

8 files changed

+139
-34
lines changed

8 files changed

+139
-34
lines changed

api_calls/censys.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ class CensysAPIHook(object):
1515
Censys API hook
1616
"""
1717

18-
def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None, **kwargs):
18+
def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None, save_mode=None, **kwargs):
1919
self.id = identity
2020
self.token = token
2121
self.query = query
2222
self.proxy = proxy
2323
self.user_agent = agent
2424
self.host_file = HOST_FILE
25+
self.save_mode = save_mode
2526

2627
def censys(self):
2728
"""
@@ -38,7 +39,7 @@ def censys(self):
3839
json_data = req.json()
3940
for item in json_data["results"]:
4041
discovered_censys_hosts.add(str(item["ip"]))
41-
write_to_file(discovered_censys_hosts, self.host_file)
42+
write_to_file(discovered_censys_hosts, self.host_file, mode=self.save_mode)
4243
return True
4344
except Exception as e:
4445
raise AutoSploitAPIConnectionError(str(e))

api_calls/shodan.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ class ShodanAPIHook(object):
1717
Shodan API hook, saves us from having to install another dependency
1818
"""
1919

20-
def __init__(self, token=None, query=None, proxy=None, agent=None, **kwargs):
20+
def __init__(self, token=None, query=None, proxy=None, agent=None, save_mode=None, **kwargs):
2121
self.token = token
2222
self.query = query
2323
self.proxy = proxy
2424
self.user_agent = agent
2525
self.host_file = HOST_FILE
26+
self.save_mode = save_mode
2627

2728
def shodan(self):
2829
"""
@@ -38,7 +39,7 @@ def shodan(self):
3839
json_data = json.loads(req.content)
3940
for match in json_data["matches"]:
4041
discovered_shodan_hosts.add(match["ip_str"])
41-
write_to_file(discovered_shodan_hosts, self.host_file)
42+
write_to_file(discovered_shodan_hosts, self.host_file, mode=self.save_mode)
4243
return True
4344
except Exception as e:
4445
raise AutoSploitAPIConnectionError(str(e))

api_calls/zoomeye.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ class ZoomEyeAPIHook(object):
2020
so we're going to use some 'lifted' credentials to login for us
2121
"""
2222

23-
def __init__(self, query=None, proxy=None, agent=None, **kwargs):
23+
def __init__(self, query=None, proxy=None, agent=None, save_mode=None, **kwargs):
2424
self.query = query
2525
self.host_file = HOST_FILE
2626
self.proxy = proxy
2727
self.user_agent = agent
2828
self.user_file = "{}/etc/text_files/users.lst".format(os.getcwd())
2929
self.pass_file = "{}/etc/text_files/passes.lst".format(os.getcwd())
30+
self.save_mode = save_mode
3031

3132
@staticmethod
3233
def __decode(filepath):
@@ -81,7 +82,7 @@ def zoomeye(self):
8182
discovered_zoomeye_hosts.add(ip)
8283
else:
8384
discovered_zoomeye_hosts.add(str(item["ip"][0]))
84-
write_to_file(discovered_zoomeye_hosts, self.host_file)
85+
write_to_file(discovered_zoomeye_hosts, self.host_file, mode=self.save_mode)
8586
return True
8687
except Exception as e:
8788
raise AutoSploitAPIConnectionError(str(e))

autosploit/main.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
EXPLOIT_FILES_PATH,
2020
START_SERVICES_PATH
2121
)
22-
from lib.jsonize import load_exploits
22+
from lib.jsonize import (
23+
load_exploits,
24+
load_exploit_file
25+
)
2326

2427

2528
def main():
@@ -73,8 +76,17 @@ def main():
7376
info("attempting to load API keys")
7477
loaded_tokens = load_api_keys()
7578
AutoSploitParser().parse_provided(opts)
76-
misc_info("checking if there are multiple exploit files")
77-
loaded_exploits = load_exploits(EXPLOIT_FILES_PATH)
79+
80+
loaded_exploits = []
81+
if not opts.exploitFile:
82+
misc_info("checking if there are multiple exploit files")
83+
loaded_exploits = load_exploits(EXPLOIT_FILES_PATH)
84+
else:
85+
loaded_exploits = load_exploit_file(opts.exploitFile)
86+
misc_info("Loaded {} exploits from {}.".format(
87+
len(loaded_exploits),
88+
opts.exploitFile))
89+
7890
AutoSploitParser().single_run_args(opts, loaded_tokens, loaded_exploits)
7991
else:
8092
warning("no arguments have been parsed, defaulting to terminal session. press 99 to quit and help to get help")

lib/cmdline/cmd.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ def optparser():
4141
help="use shodan.io as the search engine to gather hosts")
4242
se.add_argument("-a", "--all", action="store_true", dest="searchAll",
4343
help="search all available search engines to gather hosts")
44+
save_results_args = se.add_mutually_exclusive_group(required=False)
45+
save_results_args.add_argument("-O", "--overwrite", action="store_true", dest="overwriteHosts",
46+
help="When specified, start from scratch by overwriting the host file with new search results.")
47+
save_results_args.add_argument("-A", "--append", action="store_true", dest="appendHosts",
48+
help="When specified, append discovered hosts to the host file.")
4449

4550
req = parser.add_argument_group("requests", "arguments to edit your requests")
4651
req.add_argument("--proxy", metavar="PROTO://IP:PORT", dest="proxyConfig",
@@ -59,6 +64,10 @@ def optparser():
5964
help="set the configuration for MSF (IE -C default 127.0.0.1 8080)")
6065
exploit.add_argument("-e", "--exploit", action="store_true", dest="startExploit",
6166
help="start exploiting the already gathered hosts")
67+
exploit.add_argument("-d", "--dry-run", action="store_true", dest="dryRun",
68+
help="Do not launch metasploit's exploits. Do everything else. msfconsole is never called.")
69+
exploit.add_argument("-f", "--exploit-file-to-use", metavar="PATH", dest="exploitFile",
70+
help="Run AutoSploit with provided exploit JSON file.")
6271

6372
misc = parser.add_argument_group("misc arguments", "arguments that don't fit anywhere else")
6473
misc.add_argument("--ruby-exec", action="store_true", dest="rubyExecutableNeeded",
@@ -134,32 +143,49 @@ def single_run_args(opt, keys, loaded_modules):
134143
lib.output.error("caught IOError '{}' check the file path and try again".format(str(e)))
135144
sys.exit(0)
136145

146+
search_save_mode = None
147+
if opt.overwriteHosts:
148+
# Create a new empty file, overwriting the previous one.
149+
# Set the mode to append afterwards
150+
# This way, successive searches will start clean without
151+
# overriding each others.
152+
open(lib.settings.HOST_FILE, mode="w").close()
153+
search_save_mode = "a"
154+
elif opt.appendHosts:
155+
search_save_mode = "a"
156+
137157
if opt.searchCensys:
138158
lib.output.info(single_search_msg.format("Censys"))
139159
api_searches[2](
140160
keys["censys"][1], keys["censys"][0],
141-
opt.searchQuery, proxy=headers[0], agent=headers[1]
161+
opt.searchQuery, proxy=headers[0], agent=headers[1],
162+
save_mode=search_save_mode
142163
).censys()
143164
if opt.searchZoomeye:
144165
lib.output.info(single_search_msg.format("Zoomeye"))
145166
api_searches[0](
146-
opt.searchQuery, proxy=headers[0], agent=headers[1]
167+
opt.searchQuery, proxy=headers[0], agent=headers[1],
168+
save_mode=search_save_mode
147169
).zoomeye()
148170
if opt.searchShodan:
149171
lib.output.info(single_search_msg.format("Shodan"))
150172
api_searches[1](
151-
keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1]
173+
keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1],
174+
save_mode=search_save_mode
152175
).shodan()
153176
if opt.searchAll:
154177
lib.output.info("searching all search engines in order")
155178
api_searches[0](
156-
opt.searchQuery, proxy=headers[0], agent=headers[1]
179+
opt.searchQuery, proxy=headers[0], agent=headers[1],
180+
save_mode=search_save_mode
157181
).zoomeye()
158182
api_searches[1](
159-
keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1]
183+
keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1],
184+
save_mode=search_save_mode
160185
).shodan()
161186
api_searches[2](
162-
keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1]
187+
keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1],
188+
save_mode=search_save_mode
163189
).censys()
164190
if opt.startExploit:
165191
hosts = open(lib.settings.HOST_FILE).readlines()
@@ -170,5 +196,6 @@ def single_run_args(opt, keys, loaded_modules):
170196
loaded_modules,
171197
hosts,
172198
ruby_exec=opt.rubyExecutableNeeded,
173-
msf_path=opt.pathToFramework
199+
msf_path=opt.pathToFramework,
200+
dryRun=opt.dryRun
174201
).start_exploit()

lib/exploitation/exploiter.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@ def whitelist_wash(hosts, whitelist_file):
1515
"""
1616
remove IPs from hosts list that do not appear in WHITELIST_FILE
1717
"""
18-
whitelist_hosts = open(whitelist_file).readlines()
18+
whitelist_hosts = [x.strip() for x in open(whitelist_file).readlines() if x.strip()]
1919
lib.output.info('Found {} entries in whitelist.txt, scrubbing'.format(str(len(whitelist_hosts))))
2020
washed_hosts = []
2121
#return supplied hosts if whitelist file is empty
2222
if len(whitelist_hosts) == 0:
2323
return hosts
2424
else:
2525
for host in hosts:
26-
if host in whitelist_hosts:
26+
if host.strip() in whitelist_hosts:
2727
washed_hosts.append(host)
2828

2929
return washed_hosts
3030

31+
3132
class AutoSploitExploiter(object):
3233

3334
sorted_modules = []
@@ -41,6 +42,7 @@ def __init__(self, configuration, all_modules, hosts=None, **kwargs):
4142
self.single = kwargs.get("single", None)
4243
self.ruby_exec = kwargs.get("ruby_exec", False)
4344
self.msf_path = kwargs.get("msf_path", None)
45+
self.dry_run = kwargs.get("dryRun", False)
4446

4547
def view_sorted(self):
4648
"""
@@ -82,16 +84,24 @@ def start_exploit(self):
8284
"Failure Logs",
8385
"All Logs"])
8486

87+
lib.output.info("Launching exploits against {hosts_len} hosts:".format(hosts_len=len(self.hosts)))
88+
lib.output.info("{}".format((linesep+"\t").join(self.hosts)))
89+
90+
win_total = 0
91+
fail_total = 0
92+
8593
for host in self.hosts:
8694
current_host_path = path.join(current_run_path, host.strip())
8795
makedirs(current_host_path)
8896

8997
for mod in self.mods:
90-
lib.output.info(
91-
"launching exploit '{}' against host '{}'".format(
92-
mod.strip(), host.strip()
98+
if not self.dry_run:
99+
lib.output.info(
100+
"launching exploit '{}' against host '{}'".format(
101+
mod.strip(), host.strip()
102+
)
93103
)
94-
)
104+
95105
cmd_template = (
96106
"sudo {use_ruby} {msf_path} -r {rc_script_path} -q"
97107
)
@@ -141,13 +151,18 @@ def start_exploit(self):
141151
rc_script_path=current_rc_script_path
142152
)
143153

144-
output = lib.settings.cmdline(cmd)
154+
output = [""]
155+
if not self.dry_run:
156+
output = lib.settings.cmdline(cmd)
145157

146158
ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
147159
msf_output_lines = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[.\]', x)])
148160
msf_wins = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[\+\]', x)])
149161
msf_fails = linesep.join([ansi_escape.sub('', x) for x in output if re.search('\[-\]', x)])
150162

163+
win_total += len(msf_wins)
164+
fail_total += len(msf_fails)
165+
151166
csv_file = csv.writer(f, quoting=csv.QUOTE_ALL)
152167
csv_file.writerow([rhost,
153168
today_printable,
@@ -157,3 +172,17 @@ def start_exploit(self):
157172
msf_wins,
158173
msf_fails,
159174
msf_output_lines])
175+
176+
print()
177+
lib.output.info("********RESULTS**********")
178+
179+
if self.dry_run:
180+
lib.output.info("\tDRY RUN!")
181+
lib.output.info("\t0 exploits run against {} hosts.".format(len(self.hosts)))
182+
else:
183+
lib.output.info("\t{} exploits run against {} hosts.".format(len(self.mods), len(self.hosts)))
184+
lib.output.info("\t{} exploit successful (Check report.csv to validate!).".format(win_total))
185+
lib.output.info("\t{} exploit failed.".format(fail_total))
186+
187+
lib.output.info("\tExploit run saved to {}".format(str(current_run_path)))
188+
lib.output.info("\tReport saved to {}".format(str(report_path)))

lib/jsonize.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ def random_file_name(acceptable=string.ascii_letters, length=7):
2020
return ''.join(list(retval))
2121

2222

23+
def load_exploit_file(path, node="exploits"):
24+
"""
25+
load exploits from a given file
26+
"""
27+
28+
selected_file_path = path
29+
30+
retval = []
31+
try:
32+
with open(selected_file_path) as exploit_file:
33+
# loading it like this has been known to cause Unicode issues later on down
34+
# the road
35+
_json = json.loads(exploit_file.read())
36+
for item in _json[node]:
37+
# so we'll reload it into a ascii string before we save it into the file
38+
retval.append(str(item))
39+
except IOError as e:
40+
lib.settings.close(e)
41+
return retval
42+
43+
2344
def load_exploits(path, node="exploits"):
2445
"""
2546
load exploits from a given path, depending on how many files are loaded into

lib/settings.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,33 @@ def check_services(service_name):
108108
return True
109109

110110

111-
112-
def write_to_file(data_to_write, filename, mode="a+"):
111+
def write_to_file(data_to_write, filename, mode=None):
113112
"""
114113
write data to a specified file, if it exists, ask to overwrite
115114
"""
116115
global stop_animation
117116

118117
if os.path.exists(filename):
119-
stop_animation = True
120-
is_append = lib.output.prompt("would you like to (a)ppend or (o)verwrite the file")
121-
if is_append == "o":
122-
mode = "w"
123-
elif is_append != "a":
124-
lib.output.warning("invalid input provided ('{}'), appending to file".format(is_append))
118+
if not mode:
119+
stop_animation = True
120+
is_append = lib.output.prompt("would you like to (a)ppend or (o)verwrite the file")
121+
if is_append.lower() == "o":
122+
mode = "w"
123+
elif is_append.lower() == "a":
124+
mode = "a+"
125+
else:
126+
lib.output.error("invalid input provided ('{}'), appending to file".format(is_append))
127+
lib.output.error("Search results NOT SAVED!")
128+
129+
if mode == "w":
130+
lib.output.warning("Overwriting to {}".format(filename))
131+
if mode == "a":
132+
lib.output.info("Appending to {}".format(filename))
133+
134+
else:
135+
# File does not exists, mode does not matter
136+
mode = "w"
137+
125138
with open(filename, mode) as log:
126139
if isinstance(data_to_write, (tuple, set, list)):
127140
for item in list(data_to_write):
@@ -132,7 +145,7 @@ def write_to_file(data_to_write, filename, mode="a+"):
132145
return filename
133146

134147

135-
def load_api_keys(path="{}/etc/tokens".format(CUR_DIR)):
148+
def load_api_keys(unattended=False, path="{}/etc/tokens".format(CUR_DIR)):
136149

137150
"""
138151
load the API keys from their .key files
@@ -156,8 +169,8 @@ def load_api_keys(path="{}/etc/tokens".format(CUR_DIR)):
156169
else:
157170
lib.output.info("{} API token loaded from {}".format(key.title(), API_KEYS[key][0]))
158171
api_tokens = {
159-
"censys": (open(API_KEYS["censys"][0]).read(), open(API_KEYS["censys"][1]).read()),
160-
"shodan": (open(API_KEYS["shodan"][0]).read(), )
172+
"censys": (open(API_KEYS["censys"][0]).read().rstrip(), open(API_KEYS["censys"][1]).read().rstrip()),
173+
"shodan": (open(API_KEYS["shodan"][0]).read().rstrip(), )
161174
}
162175
return api_tokens
163176

0 commit comments

Comments
 (0)