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/ ...@@ -75,3 +75,8 @@ venv/
# vim # vim
*.swp *.swp
*.swo *.swo
# Pipenv files
Pipfile
Pipfile.lock
...@@ -45,7 +45,7 @@ class ExploitOptionsAggregator(type): ...@@ -45,7 +45,7 @@ class ExploitOptionsAggregator(type):
for key, value in iteritems(attrs): for key, value in iteritems(attrs):
if isinstance(value, Option): if isinstance(value, Option):
value.label = key 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__": elif key == "__info__":
attrs["_{}{}".format(name, key)] = value attrs["_{}{}".format(name, key)] = value
del attrs[key] del attrs[key]
...@@ -106,7 +106,7 @@ class Exploit(BaseExploit): ...@@ -106,7 +106,7 @@ class Exploit(BaseExploit):
) )
threads.append(thread) threads.append(thread)
print_status("{} thread is starting...".format(thread.name)) # print_status("{} thread is starting...".format(thread.name))
thread.start() thread.start()
start = time.time() start = time.time()
...@@ -119,9 +119,9 @@ class Exploit(BaseExploit): ...@@ -119,9 +119,9 @@ class Exploit(BaseExploit):
for thread in threads: for thread in threads:
thread.join() 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): def multi(fn):
......
...@@ -11,10 +11,15 @@ from routersploit.core.exploit.utils import ( ...@@ -11,10 +11,15 @@ from routersploit.core.exploit.utils import (
class Option(object): class Option(object):
""" Exploit attribute that is set by the end user """ """ 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.label = None
self.description = description self.description = description
try:
self.advanced = bool(advanced)
except ValueError:
raise OptionValidationError("Invalid value. Cannot cast '{}' to boolean.".format(advanced))
if default: if default:
self.__set__("", default) self.__set__("", default)
else: else:
...@@ -54,7 +59,7 @@ class OptPort(Option): ...@@ -54,7 +59,7 @@ class OptPort(Option):
class OptBool(Option): class OptBool(Option):
""" Option Bool attribute """ """ Option Bool attribute """
def __init__(self, default, description=""): def __init__(self, default, description="", advanced=False):
self.description = description self.description = description
if default: if default:
...@@ -64,6 +69,11 @@ class OptBool(Option): ...@@ -64,6 +69,11 @@ class OptBool(Option):
self.value = default 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): def __set__(self, instance, value):
if value == "true": if value == "true":
self.value = True self.value = True
...@@ -142,7 +152,7 @@ class OptWordlist(Option): ...@@ -142,7 +152,7 @@ class OptWordlist(Option):
class OptEncoder(Option): class OptEncoder(Option):
""" Option Encoder attribute """ """ Option Encoder attribute """
def __init__(self, default, description=""): def __init__(self, default, description="", advanced=False):
self.description = description self.description = description
if default: if default:
...@@ -152,6 +162,11 @@ class OptEncoder(Option): ...@@ -152,6 +162,11 @@ class OptEncoder(Option):
self.display_value = "" self.display_value = ""
self.value = None 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): def __set__(self, instance, value):
encoder = instance.get_encoder(value) encoder = instance.get_encoder(value)
......
...@@ -2,6 +2,7 @@ from __future__ import print_function ...@@ -2,6 +2,7 @@ from __future__ import print_function
import atexit import atexit
import itertools import itertools
import pkgutil
import os import os
import sys import sys
import getopt import getopt
...@@ -80,10 +81,17 @@ class BaseInterpreter(object): ...@@ -80,10 +81,17 @@ class BaseInterpreter(object):
""" Split line into command and argument. """ Split line into command and argument.
:param line: line to parse :param line: line to parse
:return: (command, argument) :return: (command, argument, named_arguments)
""" """
kwargs = dict()
command, _, arg = line.strip().partition(" ") 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 @property
def prompt(self): def prompt(self):
...@@ -110,19 +118,17 @@ class BaseInterpreter(object): ...@@ -110,19 +118,17 @@ class BaseInterpreter(object):
printer_queue.join() printer_queue.join()
while True: while True:
try: try:
command, args = self.parse_line(input(self.prompt)) command, args, kwargs = self.parse_line(input(self.prompt))
if not command: if not command:
continue continue
command_handler = self.get_command_handler(command) command_handler = self.get_command_handler(command)
command_handler(args) command_handler(args, **kwargs)
except RoutersploitException as err: except RoutersploitException as err:
print_error(err) print_error(err)
except EOFError: except (EOFError, KeyboardInterrupt, SystemExit):
print_info() print_info()
print_status("routersploit stopped") print_error("RouterSploit stopped")
break break
except KeyboardInterrupt:
print_info()
finally: finally:
printer_queue.join() printer_queue.join()
...@@ -140,7 +146,7 @@ class BaseInterpreter(object): ...@@ -140,7 +146,7 @@ class BaseInterpreter(object):
end_index = readline.get_endidx() - stripped end_index = readline.get_endidx() - stripped
if start_index > 0: if start_index > 0:
cmd, args = self.parse_line(line) cmd, args, _ = self.parse_line(line)
if cmd == "": if cmd == "":
complete_function = self.default_completer complete_function = self.default_completer
else: else:
...@@ -209,7 +215,8 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -209,7 +215,8 @@ class RoutersploitInterpreter(BaseInterpreter):
self.raw_prompt_template = None self.raw_prompt_template = None
self.module_prompt_template = None self.module_prompt_template = None
self.prompt_hostname = "rsf" 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.global_commands = sorted(["use ", "exec ", "help", "exit", "show ", "search "])
self.module_commands = ["run", "back", "set ", "setg ", "check"] self.module_commands = ["run", "back", "set ", "setg ", "check"]
...@@ -221,8 +228,6 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -221,8 +228,6 @@ class RoutersploitInterpreter(BaseInterpreter):
self.modules_count.update([module.split('.')[0] for module in self.modules]) 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.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.__parse_prompt()
self.banner = """ ______ _ _____ _ _ _ self.banner = """ ______ _ _____ _ _ _
...@@ -236,7 +241,7 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -236,7 +241,7 @@ class RoutersploitInterpreter(BaseInterpreter):
Embedded Devices Embedded Devices
Codename : I Knew You Were Trouble Codename : I Knew You Were Trouble
Version : 3.4.0 Version : 3.4.1
Homepage : https://www.threat9.com - @threatnine Homepage : https://www.threat9.com - @threatnine
Join Slack : https://www.threat9.com/slack Join Slack : https://www.threat9.com/slack
...@@ -260,28 +265,40 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -260,28 +265,40 @@ 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 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): 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 = "" module = ""
set_opts = [] set_opts = []
try: try:
opts, args = getopt.getopt(argv, "hxm:s:", ["module=", "set="]) opts, args = getopt.getopt(argv[1:], "hm:s:", ["help=", "module=", "set="])
except getopt.GetoptError: except getopt.GetoptError:
print_info("{} -m <module> -s \"<option> <value>\"".format(sys.argv[0])) print_info("{} -m <module> -s \"<option> <value>\"".format(argv[0]))
sys.exit(2) printer_queue.join()
return
for opt, arg in opts: for opt, arg in opts:
if opt == "-h": if opt in ("-h", "--help"):
print_info("{} -x -m <module> -s \"<option> <value>\"".format(sys.argv[0])) print_info("{} -m <module> -s \"<option> <value>\"".format(argv[0]))
sys.exit(0) printer_queue.join()
elif opt == "-x": return
noninteractive = True
elif opt in ("-m", "--module"): elif opt in ("-m", "--module"):
module = arg module = arg
elif opt in ("-s", "--set"): elif opt in ("-s", "--set"):
set_opts.append(arg) set_opts.append(arg)
if noninteractive: if not len(module):
print_error('A module is required when running non-interactively')
printer_queue.join()
return
self.command_use(module) self.command_use(module)
for opt in set_opts: for opt in set_opts:
...@@ -289,7 +306,10 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -289,7 +306,10 @@ class RoutersploitInterpreter(BaseInterpreter):
self.command_exploit() self.command_exploit()
sys.exit(0) # Wait for results if needed
printer_queue.join()
return
@property @property
def module_metadata(self): def module_metadata(self):
...@@ -364,7 +384,7 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -364,7 +384,7 @@ class RoutersploitInterpreter(BaseInterpreter):
@module_required @module_required
def command_run(self, *args, **kwargs): def command_run(self, *args, **kwargs):
print_status("Running module...") print_status("Running module {}...".format(self.current_module))
try: try:
self.current_module.run() self.current_module.run()
except KeyboardInterrupt: except KeyboardInterrupt:
...@@ -435,6 +455,24 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -435,6 +455,24 @@ class RoutersploitInterpreter(BaseInterpreter):
try: try:
opt_description = self.current_module.exploit_attributes[opt_key][1] opt_description = self.current_module.exploit_attributes[opt_key][1]
opt_display_value = self.current_module.exploit_attributes[opt_key][0] 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): except (KeyError, AttributeError):
pass pass
else: else:
...@@ -465,6 +503,22 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -465,6 +503,22 @@ class RoutersploitInterpreter(BaseInterpreter):
print_info() print_info()
@module_required @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 def _show_devices(self, *args, **kwargs): # TODO: cover with tests
try: try:
devices = self.current_module._Exploit__info__['devices'] devices = self.current_module._Exploit__info__['devices']
...@@ -554,18 +608,71 @@ class RoutersploitInterpreter(BaseInterpreter): ...@@ -554,18 +608,71 @@ class RoutersploitInterpreter(BaseInterpreter):
os.system(args[0]) os.system(args[0])
def command_search(self, *args, **kwargs): 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: try:
print_error("Please specify search keyword. e.g. 'search cisco'") 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 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: for module in self.modules:
if keyword in module: if mod_type not in str(module):
module = humanize_path(module) continue
print_info( if mod_detail not in str(module):
"{}\033[31m{}\033[0m{}".format(*module.partition(keyword)) 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): def command_exit(self, *args, **kwargs):
raise EOFError raise EOFError
from os import path import os
from routersploit.core.exploit import * from routersploit.core.exploit import *
from routersploit.core.exploit.exploit import Protocol from routersploit.core.exploit.exploit import Protocol
...@@ -19,14 +20,34 @@ class Exploit(Exploit): ...@@ -19,14 +20,34 @@ class Exploit(Exploit):
target = OptIP("", "Target IPv4 or IPv6 address") 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") 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") ftp_ssl = OptBool(False, "FTPS enabled: true/false")
ssh_port = OptPort(22, "Target SSH port (default: 22)") ssh_use = OptBool(True, "Check SSH service: true/false")
telnet_port = OptPort(23, "Target Telnet port (default: 23)") 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") threads = OptInteger(8, "Number of threads")
...@@ -34,17 +55,22 @@ class Exploit(Exploit): ...@@ -34,17 +55,22 @@ class Exploit(Exploit):
self.vulnerabilities = [] self.vulnerabilities = []
self.creds = [] self.creds = []
self.not_verified = [] self.not_verified = []
self._exploits_directories = [path.join(utils.MODULES_DIR, "exploits", 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 = [path.join(utils.MODULES_DIR, "creds", 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): def run(self):
self.vulnerabilities = [] self.vulnerabilities = []
self.creds = [] self.creds = []
self.not_verified = [] self.not_verified = []
# 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 # vulnerabilities
print_info() print_info()
print_info("\033[94m[*]\033[0m", "Starting vulnerablity check...".format(self.target)) print_info("\033[94m[*]\033[0m", "{} Starting vulnerablity check...".format(self.target))
modules = [] modules = []
for directory in self._exploits_directories: for directory in self._exploits_directories:
...@@ -54,6 +80,7 @@ class Exploit(Exploit): ...@@ -54,6 +80,7 @@ class Exploit(Exploit):
data = LockedIterator(modules) data = LockedIterator(modules)
self.run_threads(self.threads, self.exploits_target_function, data) self.run_threads(self.threads, self.exploits_target_function, data)
if self.check_creds:
# default creds # default creds
print_info() print_info()
print_info("\033[94m[*]\033[0m", "{} Starting default credentials check...".format(self.target)) print_info("\033[94m[*]\033[0m", "{} Starting default credentials check...".format(self.target))
...@@ -99,21 +126,46 @@ class Exploit(Exploit): ...@@ -99,21 +126,46 @@ class Exploit(Exploit):
else: else:
exploit.target = self.target exploit.target = self.target
# Avoid checking specific protocol - reduce network impact
if exploit.target_protocol == Protocol.HTTP: if exploit.target_protocol == Protocol.HTTP:
if not self.http_use:
continue
exploit.port = self.http_port exploit.port = self.http_port
if self.http_ssl: if self.http_ssl:
exploit.ssl = "true" exploit.ssl = "true"
exploit.target_protocol = Protocol.HTTPS exploit.target_protocol = Protocol.HTTPS
elif exploit.target_protocol is Protocol.FTP: elif exploit.target_protocol is Protocol.FTP:
if not self.ftp_use:
continue
exploit.port = self.ftp_port exploit.port = self.ftp_port
if self.ftp_ssl: if self.ftp_ssl:
exploit.ssl = "true" exploit.ssl = "true"
exploit.target_protocol = Protocol.FTPS exploit.target_protocol = Protocol.FTPS
elif exploit.target_protocol is Protocol.TELNET: elif exploit.target_protocol is Protocol.TELNET:
if not self.telnet_use:
continue
exploit.port = self.telnet_port 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"]: # elif exploit.target_protocol not in ["tcp", "udp"]:
# exploit.target_protocol = "custom" # exploit.target_protocol = "custom"
......
...@@ -17,10 +17,15 @@ LOGGER.setLevel(logging.DEBUG) ...@@ -17,10 +17,15 @@ LOGGER.setLevel(logging.DEBUG)
LOGGER.addHandler(log_handler) LOGGER.addHandler(log_handler)
def routersploit(): def routersploit(argv):
rsf = RoutersploitInterpreter() rsf = RoutersploitInterpreter()
if len(argv[1:]):
rsf.nonInteractive(argv)
else:
rsf.start() rsf.start()
if __name__ == "__main__": 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