Fix: A lot of improvements in framework.

Add: 5 new attacks in redis module
This commit is contained in:
cr0hn
2016-02-16 16:05:48 +01:00
parent 96cd034ed6
commit 18880955b2
18 changed files with 497 additions and 92 deletions

View File

@@ -19,4 +19,4 @@ These attacks can be executed in all of brokers/MQ:
#. Looking for sensible information (i.e. user/password)
#. Remote command injection
#. Listing remote process
#. Reject all messages stored in queues to avoid clients to receive them

View File

@@ -29,9 +29,9 @@ def run_console(config):
if not isinstance(config, GlobalExecutionParameters):
raise TypeError("Expected GlobalParameters, got '%s' instead" % type(config))
logging.warning("[*] Starting Enteletaor execution")
logging.warning("Starting Enteletaor execution")
run(config)
logging.warning("[*] Done!")
logging.warning("Done!")
# ----------------------------------------------------------------------
@@ -52,5 +52,4 @@ def run(config):
from .libs.core.structs import AppSettings
# Run modules
for mod_name, mod_obj in six.iteritems(AppSettings.modules):
mod_obj().run(config)
AppSettings.modules[config.action]().run(config)

View File

@@ -24,9 +24,9 @@ def main():
# Start!
run_console(config)
except KeyboardInterrupt:
log.warning("[*] CTRL+C caught. Exiting...")
log.warning("CTRL+C caught. Exiting...")
except Exception as e:
log.critical("[!] Unhandled exception: %s" % str(e))
log.critical("Unhandled exception: %s" % str(e))
log.debug("", exc_info=True)
if __name__ == "__main__" and __package__ is None:

View File

@@ -88,16 +88,37 @@ def build_arg_parser(config=None, modules=None, parser=None):
# --------------------------------------------------------------------------
# Add sub parsers for each module
# --------------------------------------------------------------------------
subparsers = parser.add_subparsers(help='available commands:')
main_subparser = parser.add_subparsers(help='available commands:', dest='module_name')
main_subparser.required = True
for mod_name, mod_instance in six.iteritems(modules):
for mod_name, mod_class in six.iteritems(modules):
# Add subparser to module
mod_parser = subparsers.add_parser(mod_instance.name,
help=mod_instance.description)
mod_parser = main_subparser.add_parser(mod_class.name,
help=mod_class.description)
# Has module parameters?
if hasattr(mod_instance, "__model__"):
_extract_parameters(mod_instance.__model__(), mod_parser, mod_name)
# If module has raw argsubparser, add it
if hasattr(mod_class, "__submodules__"):
sub_module_actions = mod_parser.add_subparsers(help="%s commands:" % mod_name, dest="module_%s" % mod_name)
sub_module_actions.required = True
for x, y in six.iteritems(mod_class.__submodules__):
sub_help = y['help']
sub_action = y.get('cmd_args', None)
sub_sub_parser = sub_module_actions.add_parser(x, help=sub_help)
# Add module parameters
if hasattr(mod_class, "__model__"):
_extract_parameters(mod_class.__model__(), sub_sub_parser, mod_name)
if sub_action is not None:
# Add sub parser
sub_action(sub_sub_parser)
else:
# Add module parameters
if hasattr(mod_class, "__model__"):
_extract_parameters(mod_class.__model__(), mod_parser, mod_name)
return parser
@@ -120,15 +141,17 @@ def _extract_parameters(config, parser, prefix=None):
used_opts = set()
for x, v in six.iteritems(config.vars):
# cmd options
params = ["--%s%s" % ("%s-" % prefix if prefix is not None else "", # Add sub-module prefix?
x)
]
params = ["--%s%s" % (
# Add sub-module prefix?
"%s-" % prefix if prefix is not None and AppSettings.parallel_running is True else "",
x.replace("_", "-"))
]
if x[0] not in used_opts:
used_opts.add(x[0])
# If parameter is form sub-module don't add it
if prefix is None:
if AppSettings.parallel_running is False or prefix is None:
params.append("-%s" % x[0])
# Type configs
@@ -139,12 +162,17 @@ def _extract_parameters(config, parser, prefix=None):
dest=x,
help=str(v.label),
default=v.default,
action=action
action=action,
required=v.required
)
if type_ is not None:
named_params["type"] = type_
# Field has choices?
if hasattr(v, "choices"):
named_params["choices"] = dict(v.choices).keys()
parser.add_argument(*params, **named_params)
@@ -178,8 +206,9 @@ def _resolve_action(field):
:rtype: (str, type)
"""
type_maps = {
'FloatField': ("store", str),
'StringField': ("store", int),
'FloatField': ("store", float),
'StringField': ("store", str),
'SelectField': ("store", str),
'IntegerField': ("store", int),
'BoolField': ("store_true", bool),
'IncrementalIntegerField': ("count", None)

View File

@@ -17,18 +17,17 @@ def setup_logging():
logger = logging.getLogger('')
# Set log level
logger.setLevel(abs(5 - DEBUG_LEVEL) % 5)
logger.setLevel(abs(DEBUG_LEVEL * 10) % 50)
# Set file log format
file_format = logging.Formatter('[%(levelname)s] %(asctime)s - %(message)s', "%Y-%m-%d %H:%M:%S")
# Handler: file
log_file = logging.FileHandler(filename="%s.log" % __name__)
log_file.setFormatter(file_format)
# Handler: console
formatter = ColoredFormatter(
"[ %(log_color)s%(levelname)-8s%(reset)s] %(blue)s%(message)s",
"[ %(log_color)s*%(reset)s ] %(blue)s%(message)s",
# "[ %(log_color)s%(levelname)-8s%(reset)s] %(blue)s%(message)s",
datefmt=None,
reset=True,
log_colors={

View File

@@ -2,29 +2,73 @@
import six
from wtforms import (Form as Model,
DateTimeField,
StringField as _StringField,
IntegerField as _IntegerField,
FloatField as _FloatField,
BooleanField as _BooleanField,
SelectField as _SelectField,
DecimalField, validators)
from wtforms.fields.core import Field as _Field, Label as _Label
from wtforms.utils import unset_value
from wtforms.fields.core import Field as _Field, Label as _Label, UnboundField as _UnboundField
# --------------------------------------------------------------------------
# Monkey patch fo Field to add:
# - New parameter: required
# --------------------------------------------------------------------------
def new_field__init__(self, label=None, validators=None, filters=tuple(),
description='', id=None, default=None, widget=None,
render_kw=None, _form=None, _name=None, _prefix='',
_translations=None, _meta=None, required=False):
self.required = required
self.__old___init__(label=label, validators=validators, filters=filters,
description=description, id=id, default=default, widget=widget,
render_kw=render_kw, _form=_form, _name=_name, _prefix=_prefix,
_translations=_translations, _meta=_meta)
if not hasattr(_Field, "__old___init__"):
_Field.__old___init__ = _Field.__init__
_Field.__init__ = new_field__init__
BaseField = _Field
# --------------------------------------------------------------------------
# Monkey patch fo wftorm to add:
# - Enforce type checking
# - Change default str(..) actcion
# ----------------------------------------------------------------------
# --------------------------------------------------------------------------
# Validate
def new_module_validate(self):
"""
This function add the feature that checks data type in fields
"""
for name, func in six.iteritems(self._fields):
if hasattr(func, "validator"):
if func.validator() is False:
self._errors = {name: "Data type incorrect"}
return False
self._errors = {}
if type(self._fields[name]) != type(self._fields[name].__type__):
self._errors[name] = ("Data type incorrect or not default value "
"provided. Got %s. Expected: %s" % (
type(self._fields[name].data),
self._fields[name].__type__))
return False
# Checks required if object is an instance
if type(self) is type:
if self._fields[name].required is True:
if self._fields[name].data is None and self._fields[name].default is None:
self._errors = {name: "Field '%s' is required" % name}
return False
return self.old_validate()
@@ -37,25 +81,27 @@ if not hasattr(Model, "old_validate"):
# Field Monkey path
# --------------------------------------------------------------------------
def new_field_str(self):
if self.__type__ is str:
return str(self.data)
else:
return self.data
def new_file_repr(self):
return str(self.data)
_Field.__str__ = new_field_str
_Field.__repr__ = new_field_str
_Field.__repr__ = new_file_repr
# --------------------------------------------------------------------------
# Label Monkey path
# --------------------------------------------------------------------------
def new_label_str(self):
return self.text
return str(self.text)
_Label.__str__ = new_label_str
# --------------------------------------------------------------------------
# Base rename
# --------------------------------------------------------------------------
BaseField = _Field
# --------------------------------------------------------------------------
# New types:
@@ -66,8 +112,15 @@ BaseField = _Field
def _validator(self):
to_check = self.data
if to_check is None:
to_check = self.default
return isinstance(to_check, self.__type__)
if self.data is None:
return True
else:
to_check = self.default
else:
if not isinstance(to_check, self.__type__):
return False
else:
return True
# --------------------------------------------------------------------------
@@ -99,7 +152,17 @@ FloatField.validator = _validator
# ----------------------------------------------------------------------
class BoolField(_FloatField):
class BoolField(_BooleanField):
"""Improved bool data that checks types"""
__type__ = bool
BoolField.validator = _validator
# --------------------------------------------------------------------------
# Especial fields
# --------------------------------------------------------------------------
# ----------------------------------------------------------------------
class SelectField(_SelectField):
"""Improved bool data that checks types"""
__type__ = str
SelectField.validator = _validator

View File

@@ -38,9 +38,38 @@ class CommonData(Model):
if self.validate() is False:
raise TypeError("\n".join("'%s' <- %s" % (x, y) for x, y in six.iteritems(self.errors)))
# Add extra vars from modules
try:
module_name = kwargs['module_name']
fake_kwargs = dict(kwargs)
# Add metavars: action / subactions
self.action = fake_kwargs.pop('module_name')
self.sub_action = None
# Is a sub-action selected
for x, y in six.iteritems(fake_kwargs):
if x.startswith("module_"):
self.sub_action = fake_kwargs.pop(x)
break
module_model = getattr(AppSettings.modules[module_name], "__model__", None)
if module_model is not None:
# Load module model to check parameters
module_model(**fake_kwargs)
# If not errors, set vars and values
for x, v in six.iteritems(fake_kwargs):
if x not in self.vars:
setattr(self, x, v)
except KeyError:
# No module name available -> not an error. Class must be loading from another locations
pass
# ----------------------------------------------------------------------
@classmethod
def from_argparser(cls, argparse_data, **kwargs):
def from_argparser(cls, argparse_data):
"""
Load parameters from argparser
"""
@@ -48,7 +77,7 @@ class CommonData(Model):
if not isinstance(argparse_data, Namespace):
raise TypeError("Expected Namespace, got '%s' instead" % type(argparse_data))
return cls(**kwargs)
return cls(**argparse_data.__dict__)
# ----------------------------------------------------------------------
def __repr__(self):
@@ -77,7 +106,6 @@ class CommonResultsExecutionData(CommonData):
# --------------------------------------------------------------------------
# class AppSettings(Singleton, Model):
class _AppSettings(CommonData):
"""Global app settings"""
@@ -89,5 +117,6 @@ class _AppSettings(CommonData):
# Loaded dinamically
hooks = None
modules = None
parallel_running = False
AppSettings = _AppSettings()

View File

@@ -25,4 +25,4 @@ def set_log_level(parsed_args):
"""
if hasattr(parsed_args, "verbosity"):
log.setLevel(abs(5 - parsed_args.verbosity) % 5)
log.setLevel(abs(parsed_args.verbosity * 10) % 50)

View File

@@ -14,9 +14,11 @@ class IModule:
name = None
description = None
@abc.abstractmethod
def run(self, module_config):
pass
if hasattr(self, "__submodules__"):
self.__submodules__[module_config.sub_action]['action'](module_config)
else:
raise NotImplemented("Run method must be override")
# ----------------------------------------------------------------------

View File

@@ -0,0 +1,102 @@
# -*- 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))

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from libs.core.structs import CommonData
from libs.core.models import FloatField
from .. import IModule
class ModuleModel(CommonData):
start_execution = FloatField(default=1.0)
end_execution = FloatField(default=1.2)
# ----------------------------------------------------------------------
class HelpModule(IModule):
"""
Long description of module
"""
__model__ = ModuleModel
name = "help"
description = "long description"
# ----------------------------------------------------------------------
def run(self, config):
print("hoooola")

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
import logging
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_info import action_redis_server_info
from .redis_clients import action_redis_server_connected
from .redis_disconnect import action_redis_server_disconnect
log = logging.getLogger()
# ----------------------------------------------------------------------
class ModuleModel(CommonData):
target = StringField(required=True)
port = IntegerField(default=6379)
db = IntegerField(default=0)
export_results = StringField()
# ----------------------------------------------------------------------
class RedisModule(IModule):
"""
Try to extract information from remote processes
"""
__model__ = ModuleModel
__submodules__ = {
'dump': dict(
help="dumps all keys in Redis database",
cmd_args=parser_redis_dump,
action=action_redis_dump
),
'info': dict(
help="open a remote shell through Redis server",
action=action_redis_server_info
),
'connected': dict(
help="get connected users to Redis server",
action=action_redis_server_connected
),
'disconnect': dict(
help="disconnect one or all users from Redis server",
cmd_args=parser_redis_server_disconnect,
action=action_redis_server_disconnect
),
}
name = "redis"
description = "some attacks over Redis service"

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
This file contains command line actions for argparser
"""
# ----------------------------------------------------------------------
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")
# ----------------------------------------------------------------------
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")

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import six
import redis
import logging
log = logging.getLogger()
# ----------------------------------------------------------------------
def action_redis_server_connected(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("Connected users to '%s':" % config.target)
for c in con.client_list():
# Skip local host connections
client = c['addr']
db = c['db']
log.error(" - %s (DB: %s)" % (client, db))

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import six
import redis
import logging
log = logging.getLogger()
# ----------------------------------------------------------------------
def action_redis_server_disconnect(config):
"""
Disconnect one or more users from server
"""
log.warning("Trying to connect with redis server...")
# Connection with redis
con = redis.StrictRedis(host=config.target, port=config.port, db=config.db)
clients = {x['addr']: x['addr'] for x in con.client_list()}
# Disconnect all clients?
if config.disconnect_all:
for c in clients:
con.client_kill(c)
log.error(" - Disconnected client '%s'" % c)
# Disconnect only one user
else:
# Check client format
if config.client is None or ":" not in config.client:
log.error("Invalid client format. Client must be format: IP:PORT, i.e: 10.211.55.2:61864")
return
try:
_c = clients[config.client]
con.client_kill(_c)
log.error(" - Disconnected client '%s'" % _c)
except KeyError:
log.warning("Client '%s' doesn't appear to be connected to server" % config.client)

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import redis
import logging
import pprint
log = logging.getLogger()
def dump_keys(con):
for key in con.keys('*'):
key_type = con.type(key).lower()
if key_type == b"kv":
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:
continue
yield val
# ----------------------------------------------------------------------
def action_redis_dump(config):
"""
Dump all redis information
"""
log.error("Trying to connect with redis server...")
# Connection with redis
con = redis.StrictRedis(host=config.target, port=config.port, db=config.db)
# Export results?
export_file = None
if config.export_results:
export_file = open(config.export_results, "w")
for val in dump_keys(con):
# Display results?
if config.no_raw is False:
log.warning(val)
# Dump to file?
if export_file is not None:
export_file.write(str(val))
# Close file descriptor
if export_file is not None:
export_file.close()

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
import six
import redis
import logging
log = logging.getLogger()
# ----------------------------------------------------------------------
def action_redis_server_info(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("Config for server '%s':" % config.target)
for x, y in six.iteritems(con.config_get()):
log.error(" - %s: %s" % (x, y))

View File

@@ -1,20 +0,0 @@
# # -*- coding: utf-8 -*-
#
# from .. import IModule
#
#
# # ----------------------------------------------------------------------
# class SetupModule(IModule):
# """
# Long description of module
# """
#
# name = "setup"
# description = "long description"
#
# def params(self):
# pass
#
# def run(self, config):
# pass
#