#!/usr/bin/env python3
"""BabooSSH interactive interface
This module contains the whole user interface for BabooSSH, extending
cmd2 and providing completion & command syntax help. It also loads the
extensions on startup.
Typical usage example:
from baboossh.shell import Shell
Shell().cmdloop()
"""
import os
import re
import argparse
import shutil
import cmd2
import tabulate
from baboossh.utils import WORKSPACES_DIR
from baboossh.extensions import Extensions
from baboossh.workspace import Workspace
def yes_no(prompt, default=None, list_val=None):
"""Simple Yes/No prompt to ask questions
Args:
prompt (str): The question to ask
default (bool): The default answer
list_val ([]): A list of values to output with "l" key
Returns:
A `bool` with `True` for yes else `False`
"""
if list_val is None:
if default is None:
choices = "[y, n]"
elif default:
choices = "[Y, n]"
else:
choices = "[y, N]"
else:
if default is None:
choices = "[y, n, l, ?]"
elif default:
choices = "[Y, n, l, ?]"
else:
choices = "[y, N, l, ?]"
answer = ""
while answer not in ["y", "n"]:
answer = input(prompt+" "+choices+" ").lower()
if answer == "?":
print(" y => Yes")
print(" n => No")
print(" l => List values")
print(" ? => Show help")
elif list_val is not None and answer == "l":
for elt in list_val:
print(" "+str(elt))
elif answer == "" and default is not None:
answer = "y" if default else "n"
return answer == "y"
Extensions.load()
[docs]class Shell(cmd2.Cmd):
"""BabooSSH Shell interface
This class extends cmd2.Cmd to build the user interface.
Attributes:
intro (str): The banner printed on program start
prompt (str): The default prompt
workspace (Workspace): The current open baboossh.Workspace
debug (bool): Boolean for debug output
"""
__CMD_CAT_OBJ = "Object management"
__CMD_CAT_CON = "Connecting hosts"
__CMD_CAT_WSP = "Workspace management"
#################################################################
################### GETTERS ###################
#################################################################
def __get_option_creds(self):
return self.workspace.get_objects(creds=True, scope=True)
def __get_option_host(self):
return self.workspace.get_objects(hosts=True, scope=True)
def __get_arg_workspaces(self):
return [name for name in os.listdir(WORKSPACES_DIR) if os.path.isdir(os.path.join(WORKSPACES_DIR, name))]
def __get_option_gateway(self):
return self.workspace.get_objects(local=True, hosts=True, scope=True)
def __get_option_user(self):
return self.workspace.get_objects(users=True, scope=True)
def __get_option_endpoint(self):
return self.workspace.get_objects(endpoints=True, scope=True)
def __get_option_endpoint_tag(self):
return self.workspace.get_objects(endpoints=True, scope=True, tags=True)
def __get_option_payload(self):
return Extensions.payloads.keys()
def __get_option_connection(self):
return self.workspace.get_objects(connections=True, scope=True)
def __get_search_fields_endpoint(self):
return self.workspace.search_fields("Endpoint")
def __get_search_fields_host(self):
return self.workspace.search_fields("Host")
def __get_open_tunnels(self):
return self.workspace.get_objects(tunnels=True)
def __get_run_targets(self):
return self.workspace.get_objects(connections=True, hosts=True, endpoints=True, scope=True)
def __get_host_or_local(self):
return self.workspace.get_objects(local=True, hosts=True, scope=True)
def __get_endpoint_or_host(self):
return self.workspace.get_objects(hosts=True, endpoints=True, scope=True)
def __get_tag(self):
return self.workspace.get_objects(tags=True)
#################################################################
################### WORKSPACE ###################
#################################################################
def __workspace_list(self, stmt):
print("Existing workspaces :")
workspaces = [name for name in os.listdir(WORKSPACES_DIR) if os.path.isdir(os.path.join(WORKSPACES_DIR, name))]
for workspace in workspaces:
if workspace == self.workspace.name:
print(" -["+workspace+"]")
else:
print(" - "+workspace)
def __workspace_add(self, stmt):
name = vars(stmt)['name']
#Check if name was given
if re.match(r'^[\w_\.-]+$', name) is None:
print('Invalid characters in workspace name. Allowed characters are letters, numbers and ._-')
return
#Check if workspace already exists
if os.path.exists(os.path.join(WORKSPACES_DIR, name)):
print("Workspace already exists")
return
try:
new_workspace = Workspace.create(name)
except (OSError, ValueError) as exc:
print("Workspace creation failed: "+str(exc))
else:
self.workspace = new_workspace
def __workspace_use(self, stmt):
name = vars(stmt)['name']
#Check if workspace already exists
if not os.path.exists(os.path.join(WORKSPACES_DIR, name)):
print("Workspace does not exist")
return
try:
new_workspace = Workspace(name)
except ValueError as exc:
print("Workspace change failed: "+str(exc))
else:
self.workspace = new_workspace
def __workspace_del(self, stmt):
name = vars(stmt)['name']
#Check if workspace already exists
if not os.path.exists(os.path.join(WORKSPACES_DIR, name)):
print("Workspace does not exist")
return
if self.workspace.name == name:
print("Cannot delete current workspace, please change workspace first.")
return
if not yes_no("Are you sure you want to delete workspace "+name+"?", default=False):
return
shutil.rmtree(os.path.join(WORKSPACES_DIR, name))
print("Workspace deleted !")
__parser_wspace = argparse.ArgumentParser(prog="workspace")
__subparser_wspace = __parser_wspace.add_subparsers(title='Actions', help='Available actions')
__parser_wspace_list = __subparser_wspace.add_parser("list", help='List workspaces')
__parser_wspace_add = __subparser_wspace.add_parser("add", help='Add a new workspace')
__parser_wspace_add.add_argument('name', help='New workspace name')
__parser_wspace_use = __subparser_wspace.add_parser("use", help='Change current workspace')
__parser_wspace_use.add_argument('name', help='Name of workspace to use', choices_method=__get_arg_workspaces)
__parser_wspace_del = __subparser_wspace.add_parser("delete", help='Delete workspace')
__parser_wspace_del.add_argument('name', help='Name of workspace to delete', choices_method=__get_arg_workspaces)
__parser_wspace_list.set_defaults(func=__workspace_list)
__parser_wspace_add.set_defaults(func=__workspace_add)
__parser_wspace_use.set_defaults(func=__workspace_use)
__parser_wspace_del.set_defaults(func=__workspace_del)
[docs] @cmd2.with_argparser(__parser_wspace)
@cmd2.with_category(__CMD_CAT_WSP)
def do_workspace(self, stmt: argparse.Namespace):
'''Create, list, delete and use workspaces.
Each workspace is a container for every object available in BabooSSH.
Having several workspaces allows you to segregate various environments,
keeping your findings and your loot organised.
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__workspace_list(stmt)
#################################################################
################### HOSTS ###################
#################################################################
def __host_print(self, hosts):
data = []
for host in hosts:
endpoints = ""
for endpoint in host.endpoints:
if endpoints == "":
endpoints = str(endpoint)
else:
endpoints = endpoints + ", "+str(endpoint)
scope = "o" if host.scope else ""
data.append([scope, host.name, host.distance, endpoints])
print(tabulate.tabulate(data, headers=["", "Name", "Dist", "Endpoints"]))
def __host_list(self, stmt):
print("Current hosts in workspace:")
show_all = getattr(stmt, 'all', False)
hosts = self.workspace.get_objects(hosts=True, scope=None if show_all else True)
if not hosts:
print("No hosts in current workspace")
return
self.__host_print(hosts)
def __host_search(self, stmt):
show_all = getattr(stmt, 'all', False)
tag = getattr(stmt, 'tag', None)
field = vars(stmt)['field']
allowed_fields = self.__get_search_fields_host()
if field not in allowed_fields:
print("Invalid field specified, use one of "+str(allowed_fields)+".")
return
val = vars(stmt)['val']
hosts = self.workspace.host_search(field, val, show_all, add_tag=tag)
print("Search result for hosts:")
if not hosts:
print("No results")
return
self.__host_print(hosts)
def __host_del(self, stmt):
host = getattr(stmt, 'host', None)
self.workspace.host_del(host)
def __host_tag(self, stmt):
host = vars(stmt)['host']
tagname = vars(stmt)['tagname']
self.workspace.host_tag(host, tagname)
def __host_untag(self, stmt):
host = vars(stmt)['host']
tagname = vars(stmt)['tagname']
self.workspace.host_untag(host, tagname)
__parser_host = argparse.ArgumentParser(prog="host")
__subparser_host = __parser_host.add_subparsers(title='Actions', help='Available actions')
__parser_host_list = __subparser_host.add_parser("list", help='List hosts')
__parser_host_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_host_search = __subparser_host.add_parser("search", help='Search a host')
__parser_host_search.add_argument('field', help='Field to search in', choices_method=__get_search_fields_host)
__parser_host_search.add_argument('val', help='Value to search')
__parser_host_search.add_argument("-t", "--tag", help="Add tag to search results", choices_method=__get_tag)
__parser_host_del = __subparser_host.add_parser("delete", help='Delete host')
__parser_host_del.add_argument('host', help='Host name', choices_method=__get_option_host)
__parser_host_tag = __subparser_host.add_parser("tag", help='Tag an host')
__parser_host_tag.add_argument('host', help='Host', choices_method=__get_option_host)
__parser_host_tag.add_argument('tagname', help='The tag name to add', choices_method=__get_tag)
__parser_host_untag = __subparser_host.add_parser("untag", help='Tag an host')
__parser_host_untag.add_argument('host', help='Host', choices_method=__get_option_host)
__parser_host_untag.add_argument('tagname', help='The tag name to add', choices_method=__get_tag)
__parser_host_list.set_defaults(func=__host_list)
__parser_host_search.set_defaults(func=__host_search)
__parser_host_del.set_defaults(func=__host_del)
__parser_host_tag.set_defaults(func=__host_tag)
__parser_host_untag.set_defaults(func=__host_untag)
[docs] @cmd2.with_argparser(__parser_host)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_host(self, stmt):
'''Search, list and delete hosts.
You can list or delete hosts, and use them as pivots to force using a
specific path. Host addition is performed automatically when you
successfully connect to an endpoint for the first time.
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__host_list(stmt)
#################################################################
################### ENDPOINTS ###################
#################################################################
def __endpoint_print(self, endpoints):
data = []
for endpoint in endpoints:
scope = "o" if endpoint.scope else ""
conn = endpoint.connection
if conn is None:
conn = ""
host = endpoint.host
if host is None:
host = ""
if endpoint.reachable is None:
reachable = "?"
else:
reachable = str(endpoint.reachable)
if endpoint.distance is None:
distance = ""
else:
distance = str(endpoint.distance)
first = True
taglist = ""
for tag in endpoint.tags:
taglist = taglist + ("!"+tag if first else ", !"+tag)
first = False
data.append([scope, endpoint, host, reachable, distance, conn, taglist])
print(tabulate.tabulate(data, headers=["", "Endpoint", "Host", "Reachable", "Dist", "Working connection", "Tags"]))
def __endpoint_list(self, stmt):
print("Current endpoints in workspace:")
show_all = getattr(stmt, 'all', False)
reachable = getattr(stmt, 'reachable', None)
conn = getattr(stmt, 'conn', None)
endpoints = self.workspace.get_objects(endpoints=True, scope=None if show_all else True)
if not endpoints:
print("No endpoints in current workspace")
return
endpoint_list = []
for endpoint in endpoints:
if reachable is not None:
flag_reachable = reachable == "true"
if endpoint.reachable != flag_reachable:
continue
if conn is not None:
flag_conn = conn == "true"
if (endpoint.connection is None) == flag_conn:
continue
endpoint_list.append(endpoint)
self.__endpoint_print(endpoint_list)
def __endpoint_add(self, stmt):
ip_add = vars(stmt)['ip']
port = str(vars(stmt)['port'])
try:
self.workspace.endpoint_add(ip_add, port)
except Exception as exc:
print("Endpoint addition failed: "+str(exc))
else:
print("Endpoint "+ip_add+":"+port+" added.")
def __endpoint_del(self, stmt):
endpoint = vars(stmt)['endpoint']
self.workspace.endpoint_del(endpoint)
def __endpoint_tag(self, stmt):
endpoint = vars(stmt)['endpoint']
tagname = vars(stmt)['tagname']
self.workspace.endpoint_tag(endpoint, tagname)
def __endpoint_untag(self, stmt):
endpoint = vars(stmt)['endpoint']
tagname = vars(stmt)['tagname']
self.workspace.endpoint_untag(endpoint, tagname)
def __endpoint_search(self, stmt):
show_all = getattr(stmt, 'all', False)
tag = getattr(stmt, 'tag', None)
field = vars(stmt)['field']
allowed_fields = self.__get_search_fields_endpoint()
if field not in allowed_fields:
print("Invalid field specified, use one of "+str(allowed_fields)+".")
return
val = vars(stmt)['val']
endpoints = self.workspace.endpoint_search(field, val, show_all, add_tag=tag)
print("Search result for endpoints:")
if not endpoints:
print("No results")
return
self.__endpoint_print(endpoints)
__parser_endpoint = argparse.ArgumentParser(prog="endpoint")
__subparser_endpoint = __parser_endpoint.add_subparsers(title='Actions', help='Available actions')
__parser_endpoint_list = __subparser_endpoint.add_parser("list", help='List endpoints')
__parser_endpoint_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_endpoint_list.add_argument("-r", "--reachable", help="Show only reachable endpoints", nargs='?', choices=["true", "false"], const="true")
__parser_endpoint_list.add_argument("-c", "--conn", help="Show only endpoints with connection", nargs='?', choices=["true", "false"], const="true")
__parser_endpoint_add = __subparser_endpoint.add_parser("add", help='Add a new endpoint')
__parser_endpoint_add.add_argument('ip', help='New endpoint ip')
__parser_endpoint_add.add_argument('port', help='New endpoint port', type=int, default=22, nargs='?')
__parser_endpoint_search = __subparser_endpoint.add_parser("search", help='Search an endpoint')
__parser_endpoint_search.add_argument("-a", "--all", help="Include out of scope elements in search", action="store_true")
__parser_endpoint_search.add_argument('field', help='Field to search in', choices_method=__get_search_fields_endpoint)
__parser_endpoint_search.add_argument('val', help='Value to search')
__parser_endpoint_search.add_argument("-t", "--tag", help="Add tag to search results", choices_method=__get_tag)
__parser_endpoint_del = __subparser_endpoint.add_parser("delete", help='Set target endpoint')
__parser_endpoint_del.add_argument('endpoint', help='Endpoint', choices_method=__get_option_endpoint_tag)
__parser_endpoint_tag = __subparser_endpoint.add_parser("tag", help='Tag an endpoint')
__parser_endpoint_tag.add_argument('endpoint', help='Endpoint', choices_method=__get_option_endpoint)
__parser_endpoint_tag.add_argument('tagname', help='The tag name to add', choices_method=__get_tag)
__parser_endpoint_untag = __subparser_endpoint.add_parser("untag", help='Tag an endpoint')
__parser_endpoint_untag.add_argument('endpoint', help='Endpoint', choices_method=__get_option_endpoint)
__parser_endpoint_untag.add_argument('tagname', help='The tag name to add', choices_method=__get_tag)
__parser_endpoint_list.set_defaults(func=__endpoint_list)
__parser_endpoint_add.set_defaults(func=__endpoint_add)
__parser_endpoint_search.set_defaults(func=__endpoint_search)
__parser_endpoint_del.set_defaults(func=__endpoint_del)
__parser_endpoint_tag.set_defaults(func=__endpoint_tag)
__parser_endpoint_untag.set_defaults(func=__endpoint_untag)
[docs] @cmd2.with_argparser(__parser_endpoint)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_endpoint(self, stmt):
'''Create, list, search and delete endpoints.
An endpoint is a couple of an IP and a port on which a SSH service
should be running. Once added, an endpoint must be reached using "probe"
and then connected using "connect".
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__endpoint_list(stmt)
#################################################################
################### USERS ###################
#################################################################
def __user_list(self, stmt):
print("Current users in workspace:")
show_all = getattr(stmt, 'all', False)
users = self.workspace.get_objects(users=True, scope=None if show_all else True)
if not users:
print("No users in current workspace")
return
data = []
for user in users:
scope = "o" if user.scope else ""
data.append([scope, user])
print(tabulate.tabulate(data, headers=["", "Username"]))
def __user_add(self, stmt):
name = vars(stmt)['name']
try:
self.workspace.user_add(name)
except Exception as exc:
print("User addition failed: "+str(exc))
else:
print("User "+name+" added.")
def __user_del(self, stmt):
name = vars(stmt)['name']
self.workspace.user_del(name)
__parser_user = argparse.ArgumentParser(prog="user")
__subparser_user = __parser_user.add_subparsers(title='Actions', help='Available actions')
__parser_user_list = __subparser_user.add_parser("list", help='List users')
__parser_user_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_user_add = __subparser_user.add_parser("add", help='Add a new user')
__parser_user_add.add_argument('name', help='New user name')
__parser_user_del = __subparser_user.add_parser("delete", help='Delete a user')
__parser_user_del.add_argument('name', help='User name', choices_method=__get_option_user)
__parser_user_list.set_defaults(func=__user_list)
__parser_user_add.set_defaults(func=__user_add)
__parser_user_del.set_defaults(func=__user_del)
[docs] @cmd2.with_argparser(__parser_user)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_user(self, stmt):
'''Create, list and delete users.
A user is a username used to authenticate on an endpoint. Once a user
is added to the workspace, it can be used with "set" and "connect".
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__user_list(stmt)
#################################################################
################### CREDS ###################
#################################################################
def __creds_types(self, stmt):
print("Supported credential types:")
data = []
for key in Extensions.auths:
data.append([key, Extensions.auths[key].descr()])
print(tabulate.tabulate(data, headers=["Key", "Description"]))
def __creds_list(self, stmt):
show_all = getattr(stmt, 'all', False)
creds = self.workspace.get_objects(creds=True, scope=None if show_all else True)
if not creds:
print("No creds in current workspace")
return
data = []
for cred in creds:
scope = "o" if cred.scope else ""
data.append([scope, "#"+str(cred.id), cred.obj.getKey(), cred.obj.toList()])
print(tabulate.tabulate(data, headers=["", "ID", "Type", "Value"]))
def __creds_show(self, stmt):
creds_id = vars(stmt)['id']
self.workspace.creds_show(creds_id)
def __creds_edit(self, stmt):
creds_id = vars(stmt)['id']
self.workspace.creds_edit(creds_id)
def __creds_del(self, stmt):
creds_id = vars(stmt)['id']
self.workspace.creds_del(creds_id)
def __creds_add(self, stmt):
creds_type = vars(stmt)['type']
try:
creds_id = self.workspace.creds_add(creds_type, stmt)
except Exception as exc:
print("Credentials addition failed: "+str(exc))
else:
print("Credentials #"+str(creds_id)+" added.")
__parser_creds = argparse.ArgumentParser(prog="creds")
__subparser_creds = __parser_creds.add_subparsers(title='Actions', help='Available actions')
__parser_creds_list = __subparser_creds.add_parser("list", help='List saved credentials')
__parser_creds_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_creds_types = __subparser_creds.add_parser("types", help='List available credentials types')
__parser_creds_show = __subparser_creds.add_parser("show", help='Show credentials details')
__parser_creds_show.add_argument('id', help='Creds identifier', choices_method=__get_option_creds)
__parser_creds_edit = __subparser_creds.add_parser("edit", help='Edit credentials details')
__parser_creds_edit.add_argument('id', help='Creds identifier', choices_method=__get_option_creds)
__parser_creds_add = __subparser_creds.add_parser("add", help='Add new credentials')
__subparser_creds_add = __parser_creds_add.add_subparsers(title='Add creds', help='Available creds types')
for __methodName in Extensions.auths:
__method = Extensions.auths[__methodName]
__parser_method = __subparser_creds_add.add_parser(__methodName, help=__method.descr())
__parser_method.set_defaults(type=__methodName)
__method.buildParser(__parser_method)
__parser_creds_del = __subparser_creds.add_parser("delete", help='Delete credentials from workspace')
__parser_creds_del.add_argument('id', help='Creds identifier', choices_method=__get_option_creds)
__parser_creds_list.set_defaults(func=__creds_list)
__parser_creds_types.set_defaults(func=__creds_types)
__parser_creds_show.set_defaults(func=__creds_show)
__parser_creds_edit.set_defaults(func=__creds_edit)
__parser_creds_add.set_defaults(func=__creds_add)
__parser_creds_del.set_defaults(func=__creds_del)
[docs] @cmd2.with_argparser(__parser_creds)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_creds(self, stmt):
'''Create, list, edit and delete credentials.
Credentials are secrets used to authenticate. They can be of different
types (see "creds types" to list supported types) and are used with "set"
and "connect".
The creds object provides a unified interface for the underlying types.
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__creds_list(stmt)
#################################################################
################### PAYLOADS ###################
#################################################################
def __payload_list(self, stmt):
print("Available payloads:")
data = []
for key in Extensions.payloads:
data.append([key, Extensions.payloads[key].descr()])
print(tabulate.tabulate(data, headers=["Key", "Description"]))
__parser_payload = argparse.ArgumentParser(prog="payload")
__subparser_payload = __parser_payload.add_subparsers(title='Actions', help='Available actions')
__parser_payload_list = __subparser_payload.add_parser("list", help='List payloads')
__parser_payload_list.set_defaults(func=__payload_list)
[docs] @cmd2.with_argparser(__parser_payload)
@cmd2.with_category(__CMD_CAT_WSP)
def do_payload(self, stmt):
'''List available payloads'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__payload_list(None)
#################################################################
################### CONNECTIONS ###################
#################################################################
def __connection_list(self, stmt):
print("Available connections:")
show_all = getattr(stmt, 'all', False)
connections = self.workspace.get_objects(connections=True, scope=None if show_all else True)
if not connections:
print("No connections in current workspace")
return True
data = []
for connection in connections:
if not show_all:
if not connection.scope:
continue
data.append([connection.endpoint, connection.user, connection.creds, "o" if connection.conn is not None else ""])
print(tabulate.tabulate(data, headers=["Endpoint", "User", "Creds", "Open"]))
return True
def __connection_close(self, stmt):
connection = getattr(stmt, "connection", None)
return self.workspace.connection_close(connection)
def __connection_del(self, stmt):
connection = getattr(stmt, "connection", None)
return self.workspace.connection_del(connection)
__parser_connection = argparse.ArgumentParser(prog="connection")
__subparser_connection = __parser_connection.add_subparsers(title='Actions', help='Available actions')
__parser_connection_list = __subparser_connection.add_parser("list", help='List connections')
__parser_connection_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_connection_close = __subparser_connection.add_parser("close", help='Close connection')
__parser_connection_close.add_argument('connection', help='Connection string', nargs="?", choices_method=__get_option_connection)
__parser_connection_del = __subparser_connection.add_parser("delete", help='Delete connection')
__parser_connection_del.add_argument('connection', help='Connection string', choices_method=__get_option_connection)
__parser_connection_list.set_defaults(func=__connection_list)
__parser_connection_close.set_defaults(func=__connection_close)
__parser_connection_del.set_defaults(func=__connection_del)
[docs] @cmd2.with_argparser(__parser_connection)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_connection(self, stmt):
'''List and delete working connections.
A connection object is saved whenever a user and a creds object work on
an endpoint, as tested by the "connect" command. Once the object is
created, it can be used with "set" and "run" to run payloads.
'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__connection_list(stmt)
#################################################################
################### OPTIONS ###################
#################################################################
def __options_list(self):
print("Current options:")
for key, val in self.workspace.options.items():
print(" - "+key+": "+str(val))
__parser_option = argparse.ArgumentParser(prog="option")
__subparser_option = __parser_option.add_subparsers(title='Actions', help='Available actions')
__parser_option_list = __subparser_option.add_parser("list", help='List options')
__parser_option_user = __subparser_option.add_parser("user", help='Set target user')
__parser_option_user.add_argument('username', help='User name', nargs="?", choices_method=__get_option_user)
__parser_option_creds = __subparser_option.add_parser("creds", help='Set target creds')
__parser_option_creds.add_argument('id', help='Creds ID', nargs="?", choices_method=__get_option_creds)
__parser_option_endpoint = __subparser_option.add_parser("endpoint", help='Set target endpoint')
__parser_option_endpoint.add_argument('endpoint', nargs="?", help='Endpoint', choices_method=__get_option_endpoint_tag)
__parser_option_payload = __subparser_option.add_parser("payload", help='Set target payload')
__parser_option_payload.add_argument('payload', nargs="?", help='Payload name', choices_method=__get_option_payload)
__parser_option_connection = __subparser_option.add_parser("connection", help='Set target connection')
__parser_option_connection.add_argument('connection', nargs="?", help='Connection string', choices_method=__get_option_connection)
__parser_option_params = __subparser_option.add_parser("params", help='Set payload params')
__parser_option_params.add_argument('params', nargs="*", help='Payload params')
__parser_option_list.set_defaults(option="list")
__parser_option_user.set_defaults(option="user")
__parser_option_creds.set_defaults(option="creds")
__parser_option_endpoint.set_defaults(option="endpoint")
__parser_option_payload.set_defaults(option="payload")
__parser_option_connection.set_defaults(option="connection")
__parser_option_params.set_defaults(option="params")
[docs] @cmd2.with_argparser(__parser_option)
@cmd2.with_category(__CMD_CAT_WSP)
def do_set(self, stmt):
'''Set the workspace active options.
Once set, the options will be used when running "probe", "connect" and
"run" without parameters to define which connections to target and
which payload to run with which options.
'''
if 'option' not in vars(stmt):
self.__options_list()
return
option = vars(stmt)['option']
if option is not None:
if option == "list":
self.__options_list()
return
if option == "user":
value = vars(stmt)['username']
elif option == "creds":
value = vars(stmt)['id']
elif option == "endpoint":
value = vars(stmt)['endpoint']
elif option == "payload":
value = vars(stmt)['payload']
elif option == "connection":
value = vars(stmt)['connection']
elif option == "params":
value = " ".join(vars(stmt)['params'])
try:
self.workspace.set_option(option, value)
except:
print("Invalid value for "+option)
else:
self.__options_list()
#################################################################
################### TAGS ###################
#################################################################
def __tag_list(self, stmt):
print("Current tags in workspace:")
tags = self.workspace.get_objects(tags=True)
if not tags:
print("No tags in current workspace")
return
data = []
for tag in tags:
data.append([tag])
print(tabulate.tabulate(data, headers=["Tag name"]))
def __tag_show(self, stmt):
name = vars(stmt)['tagname']
self.workspace.tag_show(name)
def __tag_del(self, stmt):
name = vars(stmt)['tagname']
self.workspace.tag_del(name)
__parser_tag = argparse.ArgumentParser(prog="tag")
__subparser_tag = __parser_tag.add_subparsers(title='Actions', help='Available actions')
__parser_tag_list = __subparser_tag.add_parser("list", help='List tags')
__parser_tag_show = __subparser_tag.add_parser("show", help='Show endpoints with tag')
__parser_tag_show.add_argument('tagname', help='Tag name', choices_method=__get_tag)
__parser_tag_del = __subparser_tag.add_parser("delete", help='Delete tag')
__parser_tag_del.add_argument('tagname', help='Tag name', choices_method=__get_tag)
__parser_tag_list.set_defaults(func=__tag_list)
__parser_tag_show.set_defaults(func=__tag_show)
__parser_tag_del.set_defaults(func=__tag_del)
[docs] @cmd2.with_argparser(__parser_tag)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_tag(self, stmt):
'''Manage tags'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__tag_list(stmt)
#################################################################
################### PATHS ###################
#################################################################
def __path_list(self, stmt):
print("Current paths in workspace:")
show_all = getattr(stmt, 'all', False)
paths = self.workspace.get_objects(paths=True)
if not paths:
print("No paths in current workspace")
return
data = []
for path in paths:
if not path.scope and not show_all:
continue
src = path.src
if src is None:
src = "Local"
data.append([src, path.dst])
print(tabulate.tabulate(data, headers=["Source", "Destination"]))
def __path_get(self, stmt):
endpoint = vars(stmt)['endpoint']
as_ip = getattr(stmt, "numeric", False)
self.workspace.path_find_existing(endpoint, as_ip)
def __path_add(self, stmt):
src = vars(stmt)['src']
dst = vars(stmt)['dst']
self.workspace.path_add(src, dst)
def __path_del(self, stmt):
src = vars(stmt)['src']
dst = vars(stmt)['dst']
self.workspace.path_del(src, dst)
__parser_path = argparse.ArgumentParser(prog="path")
__subparser_path = __parser_path.add_subparsers(title='Actions', help='Available actions')
__parser_path_list = __subparser_path.add_parser("list", help='List paths')
__parser_path_list.add_argument("-a", "--all", help="Show out of scope objects", action="store_true")
__parser_path_get = __subparser_path.add_parser("get", help='Get path to endpoint')
__parser_path_get.add_argument("-n", "--numeric", help="Show Endpoint instead of Host", action="store_true")
__parser_path_get.add_argument('endpoint', help='Endpoint', choices_method=__get_endpoint_or_host)
__parser_path_add = __subparser_path.add_parser("add", help='Add path to endpoint')
__parser_path_add.add_argument('src', help='Source host', choices_method=__get_host_or_local)
__parser_path_add.add_argument('dst', help='Destination endpoint', choices_method=__get_option_endpoint)
__parser_path_del = __subparser_path.add_parser("delete", help='Delete path to endpoint')
__parser_path_del.add_argument('src', help='Source host', choices_method=__get_host_or_local)
__parser_path_del.add_argument('dst', help='Destination endpoint', choices_method=__get_option_endpoint)
__parser_path_list.set_defaults(func=__path_list)
__parser_path_get.set_defaults(func=__path_get)
__parser_path_add.set_defaults(func=__path_add)
__parser_path_del.set_defaults(func=__path_del)
[docs] @cmd2.with_argparser(__parser_path)
@cmd2.with_category(__CMD_CAT_OBJ)
def do_path(self, stmt):
'''Manage paths'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__path_list(stmt)
#################################################################
################### PROBE ###################
#################################################################
__parser_probe = argparse.ArgumentParser(prog="probe")
__parser_probe.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
__parser_probe.add_argument("-a", "--again", help="include already probed endpoints", action="store_true")
__parser_probe.add_argument("-n", "--new", help="try finding new shorter path", action="store_true")
__parser_probe.add_argument("-g", "--gateway", help="force specific gateway", choices_method=__get_option_gateway)
__parser_probe.add_argument('target', help='Endpoint to probe', nargs="?", choices_method=__get_option_endpoint_tag)
[docs] @cmd2.with_argparser(__parser_probe)
@cmd2.with_category(__CMD_CAT_CON)
def do_probe(self, stmt):
'''Try to reach an endpoint through pivoting, using an existing path or finding a new one'''
target = getattr(stmt, 'target', None)
verbose = getattr(stmt, 'verbose', False)
again = getattr(stmt, 'again', False)
new = getattr(stmt, 'new', False)
gateway = getattr(stmt, 'gateway', "auto")
if gateway is None:
gateway = "auto"
if new and gateway != "auto":
print("Error: You cannot use both --new and --gateway options.")
return
targets = self.workspace.enum_probe(target, again)
nb_targets = len(targets)
if nb_targets > 1:
if not yes_no("This will probe "+str(nb_targets)+" endpoints. Proceed ?", False, list_val=targets):
return
self.workspace.probe(targets, gateway, verbose, find_new=new)
#################################################################
################### CONNECT ###################
#################################################################
__parser_connect = argparse.ArgumentParser(prog="connect")
__parser_connect.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
__parser_connect.add_argument("-f", "--force", help="force connection even if already existing", action="store_true")
__parser_connect.add_argument("-p", "--probe", help="Automatically probe the endpoint if it wasn't yet", action="store_true")
__parser_connect.add_argument('connection', help='Connection string', nargs="?", choices_method=__get_option_connection)
[docs] @cmd2.with_argparser(__parser_connect)
@cmd2.with_category(__CMD_CAT_CON)
def do_connect(self, stmt):
'''Try to authenticate on an Enpoint using a User and Creds'''
connection = getattr(stmt, 'connection', None)
verbose = getattr(stmt, 'verbose', False)
force = getattr(stmt, 'force', False)
probe_auto = getattr(stmt, 'probe', False)
targets = self.workspace.enum_connect(connection, force=force, unprobed=probe_auto)
nb_targets = len(targets)
if nb_targets > 1:
if not yes_no("This will attempt up to "+str(nb_targets)+" connections. Proceed ?", False, list_val=targets):
return
try:
nb_working = self.workspace.connect(targets, verbose, probe_auto)
except NoPathError:
print("Could not find path to the target, run \"probe\" first.")
return
print("\033[1;32m"+str(nb_working)+"/"+str(nb_targets)+"\033[0m working.")
__parser_run = argparse.ArgumentParser(prog="run")
__parser_run.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
__parser_run.add_argument('connection', help='Connection string', nargs="?", choices_method=__get_run_targets)
__subparser_run = __parser_run.add_subparsers(title='Actions', help='Available actions')
for __payloadName in Extensions.payloads:
__payload = Extensions.payloads[__payloadName]
__parser_payload = __subparser_run.add_parser(__payloadName, help=__payload.descr())
__parser_payload.set_defaults(type=__payloadName)
__payload.buildParser(__parser_payload)
[docs] @cmd2.with_argparser(__parser_run)
@cmd2.with_category(__CMD_CAT_CON)
def do_run(self, stmt):
'''Run a payload on a connection'''
connection = getattr(stmt, 'connection', None)
payload = getattr(stmt, 'type', None)
verbose = getattr(stmt, 'verbose', False)
self._reset_completion_defaults()
if payload is not None:
payload = Extensions.payloads[payload]
else:
payload = self.workspace.options["payload"]
params = self.workspace.options["params"]
__parser = argparse.ArgumentParser(description='Params __parser')
payload.buildParser(__parser)
if params is None:
params = ""
stmt, junk = __parser.parse_known_args(params.split())
if payload is None:
print("Error : No payload specified")
return
targets = self.workspace.enum_run(connection)
nb_targets = len(targets)
if nb_targets == 0:
print("No valid targets found.")
return
if nb_targets > 1:
if not yes_no("The payload will be run on "+str(nb_targets)+" connections. Proceed ?", False, list_val=targets):
return
self.workspace.run(targets, payload, stmt, verbose=verbose)
#################################################################
################### TUNNELS ###################
#################################################################
def __tunnel_list(self, stmt):
print("Current tunnels in workspace:")
tunnels = self.workspace.tunnels.values()
if not tunnels:
print("No tunnels in current workspace")
return
data = []
for tunnel in tunnels:
data.append([tunnel.port, tunnel.connection])
print(tabulate.tabulate(data, headers=["Local port", "Destination"]))
def __tunnel_open(self, stmt):
connection_str = getattr(stmt, 'connection', None)
port = getattr(stmt, 'port', None)
self.workspace.tunnel_open(connection_str, port)
def __tunnel_close(self, stmt):
port = getattr(stmt, 'port', None)
self.workspace.tunnel_close(port)
__parser_tunnel = argparse.ArgumentParser(prog="tunnel")
__subparser_tunnel = __parser_tunnel.add_subparsers(title='Actions', help='Available actions')
__parser_tunnel_list = __subparser_tunnel.add_parser("list", help='List tunnels')
__parser_tunnel_open = __subparser_tunnel.add_parser("open", help='Open tunnel')
__parser_tunnel_open.add_argument('connection', help='Connection string', choices_method=__get_option_connection)
__parser_tunnel_open.add_argument('port', help='Tunnel entry port', type=int, nargs='?')
__parser_tunnel_close = __subparser_tunnel.add_parser("close", help='Close tunnel')
__parser_tunnel_close.add_argument('port', help='Tunnel entry port', type=int, choices_method=__get_open_tunnels)
__parser_tunnel_list.set_defaults(func=__tunnel_list)
__parser_tunnel_open.set_defaults(func=__tunnel_open)
__parser_tunnel_close.set_defaults(func=__tunnel_close)
[docs] @cmd2.with_argparser(__parser_tunnel)
@cmd2.with_category(__CMD_CAT_CON)
def do_tunnel(self, stmt):
'''Manage tunnels'''
func = getattr(stmt, 'func', None)
if func is not None:
# Call whatever subcommand function was selected
func(self, stmt)
else:
self.__tunnel_list(None)
#################################################################
################### EXPORTS ###################
#################################################################
__parser_export = argparse.ArgumentParser(prog="export")
__subparser_export = __parser_export.add_subparsers(title='Actions', help='Available exporters')
__parser_method = __subparser_export.add_parser('list', help='List available exporters')
for __key in Extensions.exports:
__export = Extensions.exports[__key]
__parser_method = __subparser_export.add_parser(__key, help=__export.descr())
__parser_method.set_defaults(exporter=__key)
__export.buildParser(__parser_method)
[docs] @cmd2.with_argparser(__parser_export)
@cmd2.with_category(__CMD_CAT_WSP)
def do_export(self, stmt):
'''Export workspace info'''
key = getattr(stmt, 'exporter', 'list')
if key == 'list':
print("Available exporters:")
data = []
for key in Extensions.exports:
data.append([key, Extensions.exports[key].descr()])
print(tabulate.tabulate(data, headers=["Key", "Description"]))
return
try:
exporter = Extensions.exports[key]
except Exception as exc:
print("Error: "+str(exc))
return
exporter.run(stmt, self.workspace)
#################################################################
################### IMPORTS ###################
#################################################################
__parser_import = argparse.ArgumentParser(prog="import")
__subparser_import = __parser_import.add_subparsers(title='Actions', help='Available importers')
__parser_method = __subparser_import.add_parser('list', help='List available importers')
for __key in Extensions.imports:
__importer = Extensions.imports[__key]
__parser_method = __subparser_import.add_parser(__key, help=__importer.descr())
__parser_method.set_defaults(importer=__key)
__importer.buildParser(__parser_method)
[docs] @cmd2.with_argparser(__parser_import)
@cmd2.with_category(__CMD_CAT_WSP)
def do_import(self, stmt):
'''Import workspace info'''
key = getattr(stmt, 'importer', 'list')
if key == 'list':
print("Available importers:")
data = []
for key in Extensions.imports:
data.append([key, Extensions.imports[key].descr()])
print(tabulate.tabulate(data, headers=["Key", "Description"]))
return
try:
importer = Extensions.imports[key]
except Exception as exc:
print("Error: "+str(exc))
return
importer.run(stmt, self.workspace)
#################################################################
################### SCOPE ###################
#################################################################
def __get_all_objects(self):
return self.workspace.get_objects(endpoints=True, creds=True, users=True, hosts=True)
__parser_scope = argparse.ArgumentParser(prog="scope")
__parser_scope.add_argument('target', help='Object to scope', choices_method=__get_all_objects)
[docs] @cmd2.with_argparser(__parser_scope)
@cmd2.with_category(__CMD_CAT_WSP)
def do_scope(self, stmt):
'''Toggle object in/out of scope'''
key = getattr(stmt, 'target', None)
self.workspace.scope(key)
#################################################################
################### CMD ###################
#################################################################
@cmd2.with_category(__CMD_CAT_WSP)
def do_store(self, arg):
for obj_type, objects in self.workspace.store.items():
print(obj_type)
for obj_id, obj in objects.items():
print('\t'+str(obj)+' > '+str(obj_id))
[docs] def do_exit(self, arg):
'Close active workspace & quit Baboossh'
self.workspace.close()
print("Bye !")
return True
def __init_prompt(self):
'Build prompt to output currect workspace & active options'
new_prompt = "\033[1;33m"
new_prompt = new_prompt+"["+self.workspace.name+"]\033[1;34m"
user = self.workspace.options["user"]
creds = self.workspace.options["creds"]
endpoint = self.workspace.options["endpoint"]
payload = self.workspace.options["payload"]
if user or endpoint or creds:
if user:
new_prompt = new_prompt+str(user)
else:
new_prompt = new_prompt+"*"
new_prompt = new_prompt+":"
if creds:
new_prompt = new_prompt+str(creds)
else:
new_prompt = new_prompt+"*"
new_prompt = new_prompt+"@"
if endpoint:
new_prompt = new_prompt+str(endpoint)
else:
new_prompt = new_prompt+"*"
if payload:
new_prompt = new_prompt+"\033[1;31m("+str(payload)+")\033[0m"
self.prompt = new_prompt+"\033[1;33m>\033[0m "
[docs] def emptyline(self):
'Don\'t output empty line after command'
[docs] def postcmd(self, stop, line):
'Refresh promt after each command to reflect parameters changes'
self.__init_prompt()
return stop
[docs] def __init__(self):
'Init BabooSSH shell & cmd2.Cmd, create (if needed) & open default workspace.'
super().__init__()
if not os.path.exists(WORKSPACES_DIR):
print("> First run ? Creating workspaces directory")
os.makedirs(WORKSPACES_DIR)
#Create default workspace if not exists
if not os.path.exists(os.path.join(WORKSPACES_DIR, 'default')):
Workspace.create('default')
self.intro = '''
%%%%%/ %%% %%%%%. .%%/ %% %/ /%%%/ ,%%%/ *%% %%
%% %%* %% %% %% %% %* % % /@* % %% %% *%% %%
%%%%%% %%, %%% %%%%%% % @@@@ % /@@@ / %%%% %%%% *%%%%%%%%
%% %%% %%%%%%%%, %% %%(% %% % %%% %%% *%% %%
%%%%%% ,%% %% %%%%%% %% ,% %# %% %%%%%. %%%%%. *%% %%
Welcome to BabooSSH. To start, use "help -v" to list commands.'''
self.workspace = Workspace("default")
self.__init_prompt()
#Removes cmd2 default commands
self.disable_command("run_pyscript", "disabled")
self.disable_command("run_script", "disabled")
self.disable_command("alias", "disabled")
self.disable_command("edit", "disabled")
self.disable_command("quit", "disabled")
self.disable_command("macro", "disabled")
self.disable_command("shortcuts", "disabled")
self.quit_on_sigint = False
#TODO remove debug
self.debug = True