Commit 77151248 by Ben Mz Committed by Marcin Bury

Enhance search & setup options to reduce network impact (#587)

* add advanced options to scanners, and so modules in order to restrict tests and reduce network impact

* avoid committing Pipfile

* correct help which was not working anymore

* enhance search capabilities

* enhance search with existing options based on current modules

* remove display test

* simplify the non interactive execution process

* correct lint errors

* correct lint errors

* correct OptEncoder error, missing advanced parameter
parent 01aac5a4
......@@ -75,3 +75,8 @@ venv/
# vim
*.swp
*.swo
# Pipenv files
Pipfile
Pipfile.lock
......@@ -45,7 +45,7 @@ class ExploitOptionsAggregator(type):
for key, value in iteritems(attrs):
if isinstance(value, Option):
value.label = key
attrs["exploit_attributes"].update({key: [value.display_value, value.description]})
attrs["exploit_attributes"].update({key: [value.display_value, value.description, value.advanced]})
elif key == "__info__":
attrs["_{}{}".format(name, key)] = value
del attrs[key]
......@@ -106,7 +106,7 @@ class Exploit(BaseExploit):
)
threads.append(thread)
print_status("{} thread is starting...".format(thread.name))
# print_status("{} thread is starting...".format(thread.name))
thread.start()
start = time.time()
......@@ -119,9 +119,9 @@ class Exploit(BaseExploit):
for thread in threads:
thread.join()
print_status("{} thread is terminated.".format(thread.name))
# print_status("{} thread is terminated.".format(thread.name))
print_status("Elapsed time: {} seconds".format(time.time() - start))
print_status("Elapsed time: {0:.4f} seconds".format(round(time.time() - start, 2)))
def multi(fn):
......
......@@ -11,10 +11,15 @@ from routersploit.core.exploit.utils import (
class Option(object):
""" Exploit attribute that is set by the end user """
def __init__(self, default, description=""):
def __init__(self, default, description="", advanced=False):
self.label = None
self.description = description
try:
self.advanced = bool(advanced)
except ValueError:
raise OptionValidationError("Invalid value. Cannot cast '{}' to boolean.".format(advanced))
if default:
self.__set__("", default)
else:
......@@ -54,7 +59,7 @@ class OptPort(Option):
class OptBool(Option):
""" Option Bool attribute """
def __init__(self, default, description=""):
def __init__(self, default, description="", advanced=False):
self.description = description
if default:
......@@ -64,6 +69,11 @@ class OptBool(Option):
self.value = default
try:
self.advanced = bool(advanced)
except ValueError:
raise OptionValidationError("Invalid value. Cannot cast '{}' to boolean.".format(advanced))
def __set__(self, instance, value):
if value == "true":
self.value = True
......@@ -142,7 +152,7 @@ class OptWordlist(Option):
class OptEncoder(Option):
""" Option Encoder attribute """
def __init__(self, default, description=""):
def __init__(self, default, description="", advanced=False):
self.description = description
if default:
......@@ -152,6 +162,11 @@ class OptEncoder(Option):
self.display_value = ""
self.value = None
try:
self.advanced = bool(advanced)
except ValueError:
raise OptionValidationError("Invalid value. Cannot cast '{}' to boolean.".format(advanced))
def __set__(self, instance, value):
encoder = instance.get_encoder(value)
......
......@@ -2,6 +2,7 @@ from __future__ import print_function
import atexit
import itertools
import pkgutil
import os
import sys
import getopt
......@@ -80,10 +81,17 @@ class BaseInterpreter(object):
""" Split line into command and argument.
:param line: line to parse
:return: (command, argument)
:return: (command, argument, named_arguments)
"""
kwargs = dict()
command, _, arg = line.strip().partition(" ")
return command, arg.strip()
args = arg.strip().split()
for word in args:
if '=' in word:
(key, value) = word.split('=', 1)
kwargs[key.lower()] = value
arg = arg.replace(word, '')
return command, ' '.join(arg.split()), kwargs
@property
def prompt(self):
......@@ -110,19 +118,17 @@ class BaseInterpreter(object):
printer_queue.join()
while True:
try:
command, args = self.parse_line(input(self.prompt))
command, args, kwargs = self.parse_line(input(self.prompt))
if not command:
continue
command_handler = self.get_command_handler(command)
command_handler(args)
command_handler(args, **kwargs)
except RoutersploitException as err:
print_error(err)
except EOFError:
except (EOFError, KeyboardInterrupt, SystemExit):
print_info()
print_status("routersploit stopped")
print_error("RouterSploit stopped")
break
except KeyboardInterrupt:
print_info()
finally:
printer_queue.join()
......@@ -140,7 +146,7 @@ class BaseInterpreter(object):
end_index = readline.get_endidx() - stripped
if start_index > 0:
cmd, args = self.parse_line(line)
cmd, args, _ = self.parse_line(line)
if cmd == "":
complete_function = self.default_completer
else:
......@@ -209,7 +215,8 @@ class RoutersploitInterpreter(BaseInterpreter):
self.raw_prompt_template = None
self.module_prompt_template = None
self.prompt_hostname = "rsf"
self.show_sub_commands = ("info", "options", "devices", "all", "encoders", "creds", "exploits", "scanners", "wordlists")
self.show_sub_commands = ("info", "options", "advanced", "devices", "all", "encoders", "creds", "exploits", "scanners", "wordlists")
self.search_sub_commands = ("type", "device", "language", "payload", "vendor")
self.global_commands = sorted(["use ", "exec ", "help", "exit", "show ", "search "])
self.module_commands = ["run", "back", "set ", "setg ", "check"]
......@@ -221,8 +228,6 @@ class RoutersploitInterpreter(BaseInterpreter):
self.modules_count.update([module.split('.')[0] for module in self.modules])
self.main_modules_dirs = [module for module in os.listdir(MODULES_DIR) if not module.startswith("__")]
self.__handle_if_noninteractive(sys.argv[1:])
self.__parse_prompt()
self.banner = """ ______ _ _____ _ _ _
......@@ -236,7 +241,7 @@ class RoutersploitInterpreter(BaseInterpreter):
Embedded Devices
Codename : I Knew You Were Trouble
Version : 3.4.0
Version : 3.4.1
Homepage : https://www.threat9.com - @threatnine
Join Slack : https://www.threat9.com/slack
......@@ -260,36 +265,51 @@ class RoutersploitInterpreter(BaseInterpreter):
self.module_prompt_template = module_prompt_template if all(map(lambda x: x in module_prompt_template, ['{host}', "{module}"])) else module_prompt_default_template
def __handle_if_noninteractive(self, argv):
noninteractive = False
""" Keep old method for backward compat only """
self.nonInteractive(argv)
def nonInteractive(self, argv):
""" Execute specific command and return result without launching the interactive CLI
:return:
"""
module = ""
set_opts = []
try:
opts, args = getopt.getopt(argv, "hxm:s:", ["module=", "set="])
opts, args = getopt.getopt(argv[1:], "hm:s:", ["help=", "module=", "set="])
except getopt.GetoptError:
print_info("{} -m <module> -s \"<option> <value>\"".format(sys.argv[0]))
sys.exit(2)
print_info("{} -m <module> -s \"<option> <value>\"".format(argv[0]))
printer_queue.join()
return
for opt, arg in opts:
if opt == "-h":
print_info("{} -x -m <module> -s \"<option> <value>\"".format(sys.argv[0]))
sys.exit(0)
elif opt == "-x":
noninteractive = True
if opt in ("-h", "--help"):
print_info("{} -m <module> -s \"<option> <value>\"".format(argv[0]))
printer_queue.join()
return
elif opt in ("-m", "--module"):
module = arg
elif opt in ("-s", "--set"):
set_opts.append(arg)
if noninteractive:
self.command_use(module)
if not len(module):
print_error('A module is required when running non-interactively')
printer_queue.join()
return
for opt in set_opts:
self.command_set(opt)
self.command_use(module)
self.command_exploit()
for opt in set_opts:
self.command_set(opt)
self.command_exploit()
# Wait for results if needed
printer_queue.join()
sys.exit(0)
return
@property
def module_metadata(self):
......@@ -364,7 +384,7 @@ class RoutersploitInterpreter(BaseInterpreter):
@module_required
def command_run(self, *args, **kwargs):
print_status("Running module...")
print_status("Running module {}...".format(self.current_module))
try:
self.current_module.run()
except KeyboardInterrupt:
......@@ -435,6 +455,24 @@ class RoutersploitInterpreter(BaseInterpreter):
try:
opt_description = self.current_module.exploit_attributes[opt_key][1]
opt_display_value = self.current_module.exploit_attributes[opt_key][0]
if self.current_module.exploit_attributes[opt_key][2]:
continue
except (KeyError, IndexError, AttributeError):
pass
else:
yield opt_key, opt_display_value, opt_description
@module_required
def get_opts_adv(self, *args):
""" Generator returning module's advanced Option attributes (option_name, option_value, option_description)
:param args: Option names
:return:
"""
for opt_key in args:
try:
opt_description = self.current_module.exploit_attributes[opt_key][1]
opt_display_value = self.current_module.exploit_attributes[opt_key][0]
except (KeyError, AttributeError):
pass
else:
......@@ -465,6 +503,22 @@ class RoutersploitInterpreter(BaseInterpreter):
print_info()
@module_required
def _show_advanced(self, *args, **kwargs):
target_names = ["target", "port", "ssl", "rhost", "rport", "lhost", "lport"]
target_opts = [opt for opt in self.current_module.options if opt in target_names]
module_opts = [opt for opt in self.current_module.options if opt not in target_opts]
headers = ("Name", "Current settings", "Description")
print_info("\nTarget options:")
print_table(headers, *self.get_opts(*target_opts))
if module_opts:
print_info("\nModule options:")
print_table(headers, *self.get_opts_adv(*module_opts))
print_info()
@module_required
def _show_devices(self, *args, **kwargs): # TODO: cover with tests
try:
devices = self.current_module._Exploit__info__['devices']
......@@ -554,18 +608,71 @@ class RoutersploitInterpreter(BaseInterpreter):
os.system(args[0])
def command_search(self, *args, **kwargs):
keyword = args[0]
mod_type = ''
mod_detail = ''
mod_vendor = ''
existing_modules = [name for _, name, _ in pkgutil.iter_modules([MODULES_DIR])]
devices = [name for _, name, _ in pkgutil.iter_modules([os.path.join(MODULES_DIR, 'exploits')])]
languages = [name for _, name, _ in pkgutil.iter_modules([os.path.join(MODULES_DIR, 'encoders')])]
payloads = [name for _, name, _ in pkgutil.iter_modules([os.path.join(MODULES_DIR, 'payloads')])]
if not keyword:
print_error("Please specify search keyword. e.g. 'search cisco'")
try:
keyword = args[0].strip("'\"").lower()
except IndexError:
keyword = ''
if not (len(keyword) or len(kwargs.keys())):
print_error("Please specify at least search keyword. e.g. 'search cisco'")
print_error("You can specify options. e.g. 'search type=exploits device=routers vendor=linksys WRT100 rce'")
return
for (key, value) in kwargs.items():
if key == 'type':
if value not in existing_modules:
print_error("Unknown module type.")
return
# print_info(' - Type :\t{}'.format(value))
mod_type = "{}.".format(value)
elif key in ['device', 'language', 'payload']:
if key == 'device' and (value not in devices):
print_error("Unknown exploit type.")
return
elif key == 'language' and (value not in languages):
print_error("Unknown encoder language.")
return
elif key == 'payload' and (value not in payloads):
print_error("Unknown payload type.")
return
# print_info(' - {}:\t{}'.format(key.capitalize(), value))
mod_detail = ".{}.".format(value)
elif key == 'vendor':
# print_info(' - Vendor:\t{}'.format(value))
mod_vendor = ".{}.".format(value)
for module in self.modules:
if keyword in module:
module = humanize_path(module)
print_info(
"{}\033[31m{}\033[0m{}".format(*module.partition(keyword))
)
if mod_type not in str(module):
continue
if mod_detail not in str(module):
continue
if mod_vendor not in str(module):
continue
if not all(word in str(module) for word in keyword.split()):
continue
found = humanize_path(module)
if len(keyword):
for word in keyword.split():
found = found.replace(word, "\033[31m{}\033[0m".format(word))
print_info(found)
@stop_after(2)
def complete_search(self, text, *args, **kwargs):
if text:
return [command for command in self.search_sub_commands if command.startswith(text)]
else:
return self.search_sub_commands
def command_exit(self, *args, **kwargs):
raise EOFError
from os import path
import os
from routersploit.core.exploit import *
from routersploit.core.exploit.exploit import Protocol
......@@ -19,14 +20,34 @@ class Exploit(Exploit):
target = OptIP("", "Target IPv4 or IPv6 address")
http_port = OptPort(80, "Target Web Interface Port")
vendor = OptString("any", "Vendor concerned (default: any)")
check_exploits = OptBool(True, "Check exploits against target: true/false", advanced=True)
check_creds = OptBool(True, "Check factory credentials against target: true/false", advanced=True)
http_use = OptBool(True, "Check HTTP[s] service: true/false")
http_port = OptPort(80, "Target Web Interface Port", advanced=True)
http_ssl = OptBool(False, "HTTPS enabled: true/false")
ftp_port = OptPort(21, "Target FTP port (default: 21)")
ftp_use = OptBool(True, "Check FTP[s] service: true/false")
ftp_port = OptPort(21, "Target FTP port (default: 21)", advanced=True)
ftp_ssl = OptBool(False, "FTPS enabled: true/false")
ssh_port = OptPort(22, "Target SSH port (default: 22)")
telnet_port = OptPort(23, "Target Telnet port (default: 23)")
ssh_use = OptBool(True, "Check SSH service: true/false")
ssh_port = OptPort(22, "Target SSH port (default: 22)", advanced=True)
telnet_use = OptBool(True, "Check Telnet service: true/false")
telnet_port = OptPort(23, "Target Telnet port (default: 23)", advanced=True)
snmp_use = OptBool(True, "Check SNMP service: true/false")
snmp_community = OptString("public", "Target SNMP community name (default: public)", advanced=True)
snmp_port = OptPort(161, "Target SNMP port (default: 161)", advanced=True)
tcp_use = OptBool(True, "Check custom TCP services", advanced=True)
# tcp_port = OptPort(None, "Restrict TCP custom service tests to specific port (default: None)")
udp_use = OptBool(True, "Check custom UDP services", advanced=True)
# udp_port = OptPort(None, "Restrict UDP custom service tests to specific port (default: None)")
threads = OptInteger(8, "Number of threads")
......@@ -34,36 +55,42 @@ class Exploit(Exploit):
self.vulnerabilities = []
self.creds = []
self.not_verified = []
self._exploits_directories = [path.join(utils.MODULES_DIR, "exploits", module) for module in self.modules]
self._creds_directories = [path.join(utils.MODULES_DIR, "creds", module) for module in self.modules]
self._exploits_directories = [os.path.join(utils.MODULES_DIR, "exploits", module) for module in self.modules]
self._creds_directories = [os.path.join(utils.MODULES_DIR, "creds", module) for module in self.modules]
def run(self):
self.vulnerabilities = []
self.creds = []
self.not_verified = []
# vulnerabilities
print_info()
print_info("\033[94m[*]\033[0m", "Starting vulnerablity check...".format(self.target))
# Update list of directories with specific vendor if needed
if self.vendor != 'any':
self._exploits_directories = [os.path.join(utils.MODULES_DIR, "exploits", module, self.vendor) for module in self.modules]
if self.check_exploits:
# vulnerabilities
print_info()
print_info("\033[94m[*]\033[0m", "{} Starting vulnerablity check...".format(self.target))
modules = []
for directory in self._exploits_directories:
for module in utils.iter_modules(directory):
modules.append(module)
modules = []
for directory in self._exploits_directories:
for module in utils.iter_modules(directory):
modules.append(module)
data = LockedIterator(modules)
self.run_threads(self.threads, self.exploits_target_function, data)
data = LockedIterator(modules)
self.run_threads(self.threads, self.exploits_target_function, data)
# default creds
print_info()
print_info("\033[94m[*]\033[0m", "{} Starting default credentials check...".format(self.target))
modules = []
for directory in self._creds_directories:
for module in utils.iter_modules(directory):
modules.append(module)
if self.check_creds:
# default creds
print_info()
print_info("\033[94m[*]\033[0m", "{} Starting default credentials check...".format(self.target))
modules = []
for directory in self._creds_directories:
for module in utils.iter_modules(directory):
modules.append(module)
data = LockedIterator(modules)
self.run_threads(self.threads, self.creds_target_function, data)
data = LockedIterator(modules)
self.run_threads(self.threads, self.creds_target_function, data)
# results:
print_info()
......@@ -99,21 +126,46 @@ class Exploit(Exploit):
else:
exploit.target = self.target
# Avoid checking specific protocol - reduce network impact
if exploit.target_protocol == Protocol.HTTP:
if not self.http_use:
continue
exploit.port = self.http_port
if self.http_ssl:
exploit.ssl = "true"
exploit.target_protocol = Protocol.HTTPS
elif exploit.target_protocol is Protocol.FTP:
if not self.ftp_use:
continue
exploit.port = self.ftp_port
if self.ftp_ssl:
exploit.ssl = "true"
exploit.target_protocol = Protocol.FTPS
elif exploit.target_protocol is Protocol.TELNET:
if not self.telnet_use:
continue
exploit.port = self.telnet_port
elif exploit.target_protocol is Protocol.SSH:
if not self.ssh_use:
continue
exploit.port = self.ssh_port
elif exploit.target_protocol is Protocol.SNMP:
if not self.snmp_use:
continue
exploit.port = self.ssh_port
elif exploit.target_protocol is Protocol.TCP:
if not self.tcp_use:
continue
elif exploit.target_protocol is Protocol.UDP:
if not self.udp_use:
continue
# elif exploit.target_protocol not in ["tcp", "udp"]:
# exploit.target_protocol = "custom"
......
......@@ -17,10 +17,15 @@ LOGGER.setLevel(logging.DEBUG)
LOGGER.addHandler(log_handler)
def routersploit():
def routersploit(argv):
rsf = RoutersploitInterpreter()
rsf.start()
if len(argv[1:]):
rsf.nonInteractive(argv)
else:
rsf.start()
if __name__ == "__main__":
routersploit()
try:
routersploit(sys.argv)
except (KeyboardInterrupt, SystemExit):
pass
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment