def apply(self, user_input): # Check if the user explicitly asked to bypass this check if "!bypass" in user_input: user_input = user_input.replace("!bypass", "") # Remove this option from the command line return ProcessorAction.FORWARD, user_input # Verify if the command is being proxyfied. commands = get_commands(user_input) for c in self.PROXY_COMMANDS: if c in commands: # The user remembered to issue a proxy command. All is well. return ProcessorAction.FORWARD, user_input # The command is not proxified. Should it be? for c in self.NETWORK_COMMANDS: if c in commands: # The command should probably be proxyfied. children = get_children() for child in children: # There is already a network command running in FFM. This means (for instance) that the user # SSH'd into another machine, in which case there is no risk of leaking the local IP address. if os.path.basename(child) in self.NETWORK_COMMANDS: return ProcessorAction.FORWARD, user_input write_str("FFM blocked a command which might need to be proxied. Add \"!bypass\" to the " "command line to override or use:\r\n * " + "\r\n * ".join(self.PROXY_COMMANDS) + "\r\n", LogLevel.ERROR) return ProcessorAction.CANCEL, None # No dangerous command was issued, proceed. return ProcessorAction.FORWARD, user_input
def apply(self, user_input): # Add the proxy commands to the tokens: torify ssh is considered to be an SSH call. separators = CMDLINE_SEPARATORS + tuple( context.config["AssertTorify"]["proxy_commands"].split()) if "ssh" not in get_commands(user_input, separators=separators): return ProcessorAction.FORWARD, user_input ssh_cmdline = get_arguments(user_input, "ssh") # Block the command if the username is leaking if context.config["SSHOptions"]["require_explicit_username"]: if "@" not in ssh_cmdline: write_str( "FFM blocked a command that may leak your local username. " "Please specify the remote user explicitly.\r\n", LogLevel.ERROR) return ProcessorAction.CANCEL, None # Add the -T option if it is missing if context.config["SSHOptions"]["force_disable_pty_allocation"]: if not re.search(r'\-[a-zA-Z]*T', ssh_cmdline): # Check if the -T option is present write_str( "Notice: automatically adding the -T option to the ssh command!\r\n", LogLevel.WARNING) return ProcessorAction.FORWARD, (user_input.replace( ssh_cmdline, "%s %s" % (ssh_cmdline, "-T"), 1)) return ProcessorAction.FORWARD, user_input # Nothing done return ProcessorAction.FORWARD, user_input
def apply(self, user_input): # Add the proxy commands to the tokens: torify rdesktop is considered to be a rdesktop call. separators = CMDLINE_SEPARATORS + tuple( context.config["AssertTorify"]["proxy_commands"].split()) if "rdesktop" not in get_commands(user_input, separators=separators): return ProcessorAction.FORWARD, user_input rdesktop_cmdline = get_arguments(user_input, "rdesktop") # Use argparse to look for the interesting arguments in the rdesktop command: parser = SilentArgumentParser() parser.add_argument("-u") try: args, _ = parser.parse_known_args(rdesktop_cmdline.split()) except RuntimeError: # The rdesktop command line seems invalid. Let SSH display its error message / usage. return ProcessorAction.FORWARD, user_input # Block the command if the username is leaking if context.config["RdesktopOptions"]["require_explicit_username"]: if not args.u: write_str( "FFM blocked a command that may leak your local username. " "Please specify the remote user explicitly with the -u option.\r\n", LogLevel.ERROR) return ProcessorAction.CANCEL, None return ProcessorAction.FORWARD, user_input
def execute(self): write_str("List of commands available:\r\n") strings = [] for c in COMMAND_LIST: strings.append("\t%s: %s\r\n" % (c.name(), c.description())) # Sort the plugins by alphabetical order. for s in sorted(strings): write_str(s)
def register_plugin(plugin): if not issubclass(plugin, Command): write_str( "Tried to register %s which is not a valid command!\r\n" % str(plugin), LogLevel.ERROR) return elif plugin in COMMAND_LIST: write_str("Tried to register %s twice!\r\n" % str(plugin), LogLevel.ERROR) return else: COMMAND_LIST.add(plugin)
def apply(self, user_input): # Add the proxy commands to the tokens: torify ssh is considered to be an SSH call. separators = CMDLINE_SEPARATORS + tuple(context.config["AssertTorify"]["proxy_commands"].split()) if "ssh" not in get_commands(user_input, separators=separators): return ProcessorAction.FORWARD, user_input ssh_cmdline = get_arguments(user_input, "ssh") options_added = [] # Use argparse to look for the interesting arguments in the SSH command: parser = SilentArgumentParser() parser.add_argument("-l", nargs='?', default=None) parser.add_argument("-T", action="store_true") parser.add_argument("-o", nargs='+') parser.add_argument("-i", nargs='+') parser.add_argument("-v", action="store_true") parser.add_argument("-N", action="store_true") parser.add_argument("-D") parser.add_argument("-L") parser.add_argument("-R") parser.add_argument("positional", nargs='+') try: args, _ = parser.parse_known_args(ssh_cmdline.split()) except RuntimeError: # The SSH command line seems invalid. Let SSH display its error message / usage. return ProcessorAction.FORWARD, user_input # Block the command if the username is leaking if context.config["SSHOptions"]["require_explicit_username"]: if not any("@" in arg for arg in args.positional) and not args.l: write_str("FFM blocked a command that may leak your local username. " "Please specify the remote user explicitly.\r\n", LogLevel.ERROR) return ProcessorAction.CANCEL, None # Add -oPubkeyAuthentication=no to prevent SSH keys from leaking. if context.config["SSHOptions"]["prevent_ssh_key_leaks"]: if not args.i and ( not args.o or not any("PubkeyAuthentication" in option for option in args.o) ): options_added.append("-oPubkeyAuthentication=no") # Add the -T option if it is missing if context.config["SSHOptions"]["force_disable_pty_allocation"]: if not args.T: options_added.append("-T") if options_added: user_input = user_input.replace(ssh_cmdline, "%s %s" % (ssh_cmdline, " ".join(options_added)), 1) write_str("Notice: the following options were added to the SSH command: %s.\r\n" % ", ".join(options_added), LogLevel.WARNING) return ProcessorAction.FORWARD, user_input
def apply(self, user_input): # Add the proxy commands to the tokens: torify ssh is considered to be an SSH call. separators = CMDLINE_SEPARATORS + tuple(AssertTorify.PROXY_COMMANDS) if "ssh" not in get_commands(user_input, separators=separators): return ProcessorAction.FORWARD, user_input ssh_cmdline = get_arguments(user_input, "ssh") if not re.search(r'\-[a-zA-Z]*T', ssh_cmdline): # Check if the -T option is present write_str( "Notice: automatically adding the -T option to the ssh command!\r\n", LogLevel.WARNING) return ProcessorAction.FORWARD, (user_input.replace( ssh_cmdline, "%s %s" % (ssh_cmdline, "-T"), 1)) return ProcessorAction.FORWARD, user_input
def test_standard_case(self): f = "unittest_ffm.log" cmdline = " !log %s" % f self.assertTrue(parse_commands(cmdline)) self.assertIsNotNone(self.context.log) self.assertEqual(f, self.context.log.name) message = "Unit test!" write_str(message) cmdline = "!log off" self.assertTrue(parse_commands(cmdline)) self.assertIsNone(self.context.log) self.assertTrue(os.path.exists(f)) with open(f, "r") as fd: self.assertEqual(message, fd.read()) os.remove(f)
def parse_commands(command_line): for c in COMMAND_LIST: if re.match(c.regexp(), command_line): # TODO: parse better? args = command_line.split() try: command_instance = c(*args) except RuntimeError as e: # The constructor throws: show the command usage. c.usage() if str(e): write_str("%s\r\n" % str(e), LogLevel.WARNING) else: try: command_instance.execute() except Exception as e: write_str("Command failed with error: %s\r\n" % str(e), LogLevel.WARNING) return True # No commands match, don't do anything. return False
def register_processor(plugin): if not issubclass(plugin, Processor): write_str( "Tried to register %s which is not a valid command!\r\n" % str(plugin), LogLevel.ERROR) return if plugin.type() == ProcessorType.INPUT: destination = INPUT_PROCESSOR_LIST elif plugin.type() == ProcessorType.OUTPUT: destination = OUTPUT_PROCESSOR_LIST else: write_str("The processor's type (%s) is unsupported." % plugin.type()) return # The plugin name needs to be checked on the class name, as the full path may vary # depending on where the plugin is imported. plugin_name = str(plugin).split(".")[-1] for p in destination: if str(p).split(".")[-1] == plugin_name: write_str("Tried to register %s twice!\r\n" % str(plugin), LogLevel.ERROR) return else: destination.add(plugin)
def usage(): write_str("Usage: !list\r\n")