import string import uuid import random from util import iDict import exception # Nothing is validating the Redir section in the xml. I may get nonexistent # params. # __all__ = ['RedirectionManager', 'LocalRedirection', 'RemoteRedirection'] def str2bool(x): if x is None: return False if x.lower() in ('true', 't', '1'): return True else: return False def strip_xpath(x): if not x: return x if x.startswith('/'): return "" else: return x def is_identifier(x): if not x: return False else: if x[0] in string.digits: return False else: return True def rand_port(): RAND_PORT_MIN = 1025 RAND_PORT_MAX = 65535 return str(random.randint(RAND_PORT_MIN, RAND_PORT_MAX)) class LocalRedirection(object): """ listenaddr: Name of parameter to give the IP addr of listening tunnel on local LP listenport: Name of parameter to give the port of the listening tunnel on local LP destaddr: Name of parameter that contains the IP of the target computer destport: Name of parameter that contains the port on the target computer """ def __init__(self, protocol, listenaddr, listenport, destaddr, destport, closeoncompletion="false", name="", srcport=None, srcportlist=None, srcportrange=None, *trashargs, **trashkargs): self.protocol = protocol # XXX - The redirection section really shouldn't have XPath in it. self.listenaddr = strip_xpath(listenaddr) self.listenport = strip_xpath(listenport) self.destaddr = strip_xpath(destaddr) self.destport = strip_xpath(destport) self.closeoncompletion = str2bool(closeoncompletion) self.name = name self.srcport = strip_xpath(srcport) self.srcportlist = srcportlist self.srcportrange = srcportrange def __repr__(self): return str(self.__dict__) class RemoteRedirection(object): def __init__(self, protocol, listenaddr, destport, closeoncompletion="false", name="", listenport=None, listenportlist=None, listenportrange=None, listenportcount=None, destaddr="0.0.0.0", *trashargs, **trashkargs): self.protocol = protocol self.listenaddr = strip_xpath(listenaddr) self.listenport = strip_xpath(listenport) self.destaddr = strip_xpath(destaddr) self.destport = strip_xpath(destport) self.closeoncompletion = str2bool(closeoncompletion) self.name = name # Need conversion? self.listenportlist = listenportlist self.listenportrange = listenportrange self.listenportcount = listenportcount def __repr__(self): return str(self.__dict__) class RedirectionManager: """This is something of a misnomer. This is really a redirection manager rather than a redirection object. This is responsible for taking the defined tunnels in the plug-in's XML and 'swapping out' the parameters as they pertain to redirection. A sample redirection section appears as follows: For the "throwing" (launch) tunnel, we: 1: Ask for/retrieve the "Destination IP" and "Destination Port", which default to the "TargetIp" and "TargetPort" parameters 2: Ask for the "Listen IP" (listenaddr) and "Listen Port" (listenport) and then swap them in "TargetIp" and "TargetPort" 3: After execution, restore the proper session parameters * (listenaddr, listenport) = l(destaddr, destport) For the "callin" tunnel, we: 1: Ask for/retrieve the "Destination IP" and Destination Port", which default to the "TargetIp" and the "ListenPort" parameters 2: Ask for the "Listen IP" (listenaddr) and "Listen Port" (listenport) and then swap them into "TargetIp" and "CallinPort" parameters 3: After execution, restore the proper session parameters * (listenaddr, listenport) = l(destaddr, destport) For the "callback" tunnel, we: 1: Ask for the Listen IP and Listen Port for which the payload will callback. This is most likely the last hop redirector IP and a port on it 2: Ask for the Destination IP and Destination Port, which will likely be the operator workstation. Store the Destination port as "CallbackLocalPort", basically ignoring the DestinationIp 3: After execution, restore the proper session parameters * (destaddr, destport) = l(listenaddr, listenport) """ def __init__(self, io): self.io = io self.active = False # A place to store parameters for the session. We push the info, run the plug-in # (with redirection), and then pop the info to restore it self.session_cache = {} def on(self): self.active = True def off(self): self.active = False def is_active(self): return self.active def get_status(self): if self.active: return "ON" else: return "OFF" def get_session(self, id): return self.session_cache.get(id) def pre_exec(self, plugin): if not plugin.canRedirect(): return 0 if self.is_active(): self.io.print_msg("Redirection ON") return self.config_redirect(plugin, True) else: self.io.print_msg("Redirection OFF") return self.config_redirect(plugin, False) def post_exec(self, plugin, id): if id == 0: return # if plugin doesn't do redir, return try: stored_session_data = self.session_cache.pop(id) except KeyError: return # Restore the old information to the session for key,val in stored_session_data['params'].items(): plugin.set(key, val) def print_session(self, id): try: session = self.session_cache[id] except KeyError: return self.io.print_global_redir(session) """ Pre plugin execution """ def conv_param(self, val, params, session_data={}): """Resolve a value from one of session, params, or the hard value""" try: # First try to find the session parameter if val in session_data: return session_data[val] # Then try to find the context-specific parameter if is_identifier(val): return params[val] except: return None # If it is neither a session or context parameter, return the value as is return val def prompt_redir_fake(self, msg, default): done = None while not done: try: line = self.io.prompt_user(msg, default) except exception.PromptHelp, err: self.io.print_warning('No help available') except exception.PromptErr, err: raise except exception.CmdErr, err: self.io.print_error(err.getErr()) if line: return line def prompt_redir(self, plugin, var, msg, default): """Prompt for a redirect value and set it in Truantchild""" done = None while not done: try: line = self.io.prompt_user(msg, default) plugin.set(var, line) done = plugin.hasValidValue(var) except exception.PromptHelp, err: self.io.print_warning('No help available') except exception.PromptErr, err: raise except exception.CmdErr, err: self.io.print_error(err.getErr()) return plugin.get(var) def straight_remote(self, r, plugin): params = iDict(plugin.getParameters()) lport = self.conv_param(r.listenport, params) dport = self.conv_param(r.destport, params) laddr = self.conv_param(r.listenaddr, params) if None in (lport, dport, laddr): return # Do we need to choose a random local port? # XXX - This won't happen unless lport is 0 if not lport or lport == "0": lport = rand_port() # Store off the old values so that we can restore them after the # plug-in executes cache = {r.listenaddr : plugin.get(r.listenaddr), r.listenport : plugin.get(r.listenport), r.destport : plugin.get(r.destport)} self.io.print_success("Remote Tunnel - %s" % r.name) try: # Modify the plugin and report success callbackip = self.prompt_redir(plugin, r.listenaddr, 'Listen IP', laddr) callbackport = self.prompt_redir(plugin, r.listenport, 'Listen Port', lport) plugin.set(r.destport, callbackport) self.io.print_success("(%s) Remote %s:%s" % (r.protocol, callbackip, callbackport)) except exception.PromptErr: self.io.print_error("Aborted by user") for (var,val) in cache.items(): try: plugin.set(var, val) except: self.io.print_error("Error setting '%s' - May be in an inconsistent state" % var) raise def straight_local(self, l, plugin): """Effectively just print the straight path to the target""" params = iDict(plugin.getParameters()) laddr = self.conv_param(l.listenaddr, params) lport = self.conv_param(l.listenport, params) if not laddr or not lport: return # HACK HACK # The logic here was previously wrong, which meant that people didn't have to be careful # about their redirection sections. Until we get them fixed, we need a hack that will # allow these invalid redirection sections if we try it the valid way and fail enable_hack = False try: cache = {l.destaddr : plugin.get(l.destaddr), l.destport : plugin.get(l.destport)} laddr = self.conv_param(l.destaddr, params) lport = self.conv_param(l.destport, params) except exception.CmdErr: enable_hack = True cache = {l.destaddr : plugin.get(l.listenaddr), l.destport : plugin.get(l.listenport)} self.io.print_success("Local Tunnel - %s" % l.name) try: if not enable_hack: targetip = self.prompt_redir(plugin, l.destaddr, 'Destination IP', laddr) targetport = self.prompt_redir(plugin, l.destport, 'Destination Port', lport) self.io.print_success("(%s) Local %s:%s" % (l.protocol, targetip, targetport)) else: targetip = self.prompt_redir(plugin, l.listenaddr, 'Destination IP', laddr) targetport = self.prompt_redir(plugin, l.listenport, 'Destination Port', lport) self.io.print_success("(%s) Local %s:%s" % (l.protocol, targetip, targetport)) except exception.PromptErr: self.io.print_error("Aborted by user") for (var,val) in cache.items(): try: plugin.set(var, val) except: self.io.print_error("Error setting '%s' - May be in an inconsistent state" % var) raise except Exception as e: self.io.print_error("Error: {0}".format(str(type(e)))) def redirect_remote(self, r, plugin, session_data): """(destaddr, destport) = r-xform(listenaddr, listenport) * Each of the identifiers above specifies a variable for the plug-in (1) Prompt for Listen IP - Likely the ultimate redirector's IP (2) Prompt for Listen Port - Likely the ultimate redirector's port (3) Prompt for Destination - Likely 0.0.0.0 (4) Prompt for Destination Port - Likely a local port Lookup the variables specified by listenaddr and listenport, transform them with a given transform function, and substitute the resulting values into the variables specified by destaddr and destport. The plug-in will then have to open a port to listen on using the variables specified by the destnation IP and destination port """ params = iDict(plugin.getParameters()) lport = self.conv_param(r.listenport, params, session_data['params']) dport = self.conv_param(r.destport, params, session_data['params']) laddr = self.conv_param(r.listenaddr, params, session_data['params']) daddr = self.conv_param(r.destaddr, params, session_data['params']) if None in (lport, dport, laddr, daddr): for p,n in (laddr, r.listenaddr), (lport, r.listenport), (daddr, r.destaddr), (dport, r.destport): if p == None: self.io.print_warning("Parameter %s referenced by tunnel %s not found. This tunnel will " "be ignored" % (n, r.name)) return if not lport or lport == "0": lport = rand_port() self.io.print_success("Remote Tunnel - %s" % r.name) # # Prompt the user for the listenaddr and listenport # callbackip = self.prompt_redir(plugin, r.listenaddr, 'Listen IP', laddr) callbackport = self.prompt_redir(plugin, r.listenport, 'Listen Port', lport) # # Do the substitution # session_data['params'][r.listenaddr] = callbackip session_data['params'][r.listenport] = callbackport # Get the other end of the tunnel, where the connection will eventually be made. # This will likely be, but does not have to be, the local workstation callbacklocalip = self.prompt_redir_fake('Destination IP', daddr) if not dport: dport = callbackport callbacklocalport = self.prompt_redir(plugin, r.destport, 'Destination Port', dport) session_data['params'][r.destport] = callbacklocalport session_data['remote'].append(RemoteRedirection(r.protocol, callbackip, callbacklocalport, listenport=callbackport, destaddr=callbacklocalip, name=r.name)) self.io.print_success("(%s) Remote %s:%s -> %s:%s" % (r.protocol, callbackip, callbackport, callbacklocalip, callbacklocalport)) def redirect_local(self, l, plugin, session_data): """ targetip = Destination IP (on the target) targetport = Destination Port (on the target) redirip = IP of the LP redirport = Port on the LP """ # listenaddr - name of variable containing the LP IP # listenport - name of variable containing the LP Port # destaddr - name of variable containing the Target IP # destport - name of variable containing the Target Port # targetip - IP of the target # targetport - Port of the target # redirip - IP of the LP # redirport - Port of the LP params = iDict(plugin.getParameters()) # Get the defaults for the user prompt laddr = self.conv_param(l.listenaddr, params, session_data['params']) lport = self.conv_param(l.listenport, params, session_data['params']) daddr = self.conv_param(l.destaddr, params, session_data['params']) dport = self.conv_param(l.destport, params, session_data['params']) if None in (laddr, lport, daddr, dport): for p,n in (laddr, l.listenaddr), (lport, l.listenport), (daddr, l.destaddr), (dport, l.destport): if p == None: self.io.print_warning("Parameter %s referenced by tunnel %s not found. This tunnel will " "be ignored" % (n, l.name)) return self.io.print_success("Local Tunnel - %s" % l.name) # # Get the destination IP and port for the target # targetip = self.prompt_redir_fake('Destination IP', daddr) targetport = self.prompt_redir_fake('Destination Port', dport) # # Get the redirection addresses # redirip = self.prompt_redir(plugin, l.listenaddr, 'Listen IP', '127.0.0.1') if not dport: dport = targetport redirport = self.prompt_redir(plugin, l.listenport, 'Listen Port', lport) # # # session_data['params'][l.listenaddr] = targetip session_data['params'][l.listenport] = targetport # # Record the redirection tunnel # session_data['local'].append(LocalRedirection(l.protocol, redirip, redirport, targetip, targetport, name=l.name)) self.io.print_success("(%s) Local %s:%s -> %s:%s" % (l.protocol, redirip, redirport, targetip, targetport)) def config_redirect(self, plugin, do_redir): """Configure whether the plug-in should perform redirection plugin - An instance of a plugin do_redir - Should we do redirection? (True or False)""" redir = plugin.getRedirection() # Make a new session dictionary here session_data = { 'params' : {}, # 'remote' : [], # 'local' : [] # } if do_redir: id = uuid.uuid4() else: id = 0 try: self.io.newline() self.io.print_success("Configure Plugin Local Tunnels") for l in redir['local']: if do_redir: self.redirect_local(l, plugin, session_data) else: self.straight_local(l, plugin) self.io.newline() self.io.print_success("Configure Plugin Remote Tunnels") for r in redir['remote']: if do_redir: self.redirect_remote(r, plugin, session_data) else: self.straight_remote(r, plugin) except exception.PromptErr: for key,val in session_data['params'].items(): plugin.set(key, val) raise self.io.newline() # Store info into the cache so that we can restore it in post_exec if id: self.session_cache[id] = session_data return id