From e1e5acade0ed664f886fff2271907ec241353e9e Mon Sep 17 00:00:00 2001 From: cr0hn Date: Tue, 23 Feb 2016 14:24:48 +0100 Subject: [PATCH] add: scan module --- enteletaor_lib/modules/scan/__init__.py | 36 ++++ enteletaor_lib/modules/scan/scan_main.py | 234 +++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 enteletaor_lib/modules/scan/__init__.py create mode 100644 enteletaor_lib/modules/scan/scan_main.py diff --git a/enteletaor_lib/modules/scan/__init__.py b/enteletaor_lib/modules/scan/__init__.py new file mode 100644 index 0000000..bd2b25c --- /dev/null +++ b/enteletaor_lib/modules/scan/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +import logging + +from modules import IModule + +from libs.core.structs import CommonData +from libs.core.models import StringField, BoolField, IntegerField + +from .scan_main import action_scan_main + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +class ModuleModel(CommonData): + ports = StringField(default="5672,6379,5555", label="comma separated ports") + target = StringField(required=True) + own_ips = BoolField(label="Try to find all IPs registered for this company") + concurrency = IntegerField(label="maximum parallels scans", default=10) + + +# ---------------------------------------------------------------------- +class ScanProcessModule(IModule): + """ + Try to extract information from remote processes + """ + __model__ = ModuleModel + __submodules__ = { + 'default': dict( + action=action_scan_main + ) + } + + name = "scan" + description = "do a scans trying to find open brokers / MQ" diff --git a/enteletaor_lib/modules/scan/scan_main.py b/enteletaor_lib/modules/scan/scan_main.py new file mode 100644 index 0000000..a98533f --- /dev/null +++ b/enteletaor_lib/modules/scan/scan_main.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- + +import six +import zmq +import redis +import socket +import logging +import eventlet +import ipaddress +import amqp.connection + + +from functools import partial +from threading import Thread, BoundedSemaphore + +from .patch import patch_transport +from enteletaor_lib.libs.contrib.inetnum import get_inet_num + +# Monkey patch for AMQP lib +patch_transport() +# Path thread library +eventlet.monkey_patch(socket=True, select=True, thread=True) + + +# Reconfigure AMQP LOGGER +logging.getLogger('amqp').setLevel(100) + +log = logging.getLogger() + + +# ---------------------------------------------------------------------- +def _do_scan(config, sem, host): + """ + This function try to find brokers services open in remote servers + """ + + handlers = { + 'Redis': handle_redis, + 'RabbitMQ': handle_amqp, + 'ZeroMQ': handle_zmq + } + + log.warning(" > Analyzing host '%s' " % host) + + for port in config.ports.split(","): + + # Check each serve + for server, handle in six.iteritems(handlers): + + try: + log.debug(" >> Trying '%s' port '%s'" % (host, port)) + + # Try to check if port is open + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + + result = s.connect_ex((host, int(port))) + except socket.gaierror as e: + log.warning("%s : %s error: %s" % (server, port, e)) + continue + + # Is port open? + if result == 0: + if handle(host, port, config) is True: + log.error(" Open '%s' server found in port '%s'" % (server, port)) + else: + log.debug(" Port %s is closed" % port) + + sem.release() + + +# ---------------------------------------------------------------------- +def action_scan_main(config): + # -------------------------------------------------------------------------- + # Resolve target + # -------------------------------------------------------------------------- + all_ips = build_targets(config) + + # -------------------------------------------------------------------------- + # Preparing scan + # -------------------------------------------------------------------------- + target_number = len(all_ips) + + log.warning(" - Number of targets to analyze: %s" % target_number) + + # Semaphore + sem = BoundedSemaphore(config.concurrency) + threads = [] + + # Map parameters + _fn = partial(_do_scan, config, sem) + + log.error(" - Starting scan") + + # -------------------------------------------------------------------------- + # Do scan + # -------------------------------------------------------------------------- + for x in all_ips: + sem.acquire() + + t = Thread(target=_fn, args=(x,)) + threads.append(t) + + t.start() + + for t in threads: + t.join() + + +# -------------------------------------------------------------------------- +def build_targets(config): + + results = set() + + # Split targets + for t in config.target.split("-"): + try: + results.update(str(x) for x in ipaddress.ip_network(t, strict=False)) + except ValueError: + # -------------------------------------------------------------------------- + # If reach this, is not a IPs, is a domain + # -------------------------------------------------------------------------- + + # Try to get all assigned IP of domain + if config.own_ips is True: + + # Extract domain + try: + val = get_inet_num(t.split(".")[-2]) + + if val is not None: + + for v in val: + log.debug(" -> Detected registered network '%s'. Added for scan." % v) + + results.update(str(x) for x in ipaddress.ip_network(v, strict=False)) + except KeyError: + # Invalid domain + log.debug(" Error while try to extract domain: '%s'" % t) + + # -------------------------------------------------------------------------- + # Get all IPs for domain + # -------------------------------------------------------------------------- + + # If target is a domain, remove CDIR + _target_cdir = t.split("/") + + _cleaned_target = _target_cdir[0] + + try: + # Resolve + host_ip = socket.gethostbyname(_cleaned_target) + except socket.gaierror: + # Try with the hostname with "www." again + try: + host_ip = socket.gethostbyname("www.%s" % _cleaned_target) + except socket.gaierror: + log.error(" Unable to resolve '%s'" % _cleaned_target) + + continue + + # Add CDIR to result + scan_target = "%s%s" % (host_ip, "/%s" % _target_cdir[1] if len(_target_cdir) > 1 else "") + + results.update(str(x) for x in ipaddress.ip_network(scan_target, strict=False)) + + return results + + +# -------------------------------------------------------------------------- +# These 3 functions determinate if server has listen one of these services: +# - Redis server +# - RabbitMQ server +# - ZeroMQ PUB/SUB pattern +# +# Each function try to connect or do some action and determinate if service +# is on or not. +# -------------------------------------------------------------------------- +def handle_redis(host, port=6379, extra_config=None): + + # log.debug(" * Connection to Redis: %s : %s" % (host, port)) + + try: + redis.StrictRedis(host=host, port=port, socket_connect_timeout=1, socket_timeout=1).config_get() + + return True + + except Exception: + return False + + +# ---------------------------------------------------------------------- +def handle_amqp(host, port=5672, extra_config=None): + + host_and_port = "%s:%s" % (host, port) + + # log.debug(" * Connection to RabbitMQ: %s : %s" % (host, port)) + + try: + amqp.connection.Connection(host=host_and_port, + connect_timeout=1, + read_timeout=1, + socket_timeout=1) + return True + + except Exception: + return False + + +# ---------------------------------------------------------------------- +def handle_zmq(host, port=5555, extra_config=None): + + # log.debug(" * Connection to ZeroMQ: %s : %s" % (host, port)) + + context = zmq.Context() + + # Configure + socket = context.socket(zmq.SUB) + socket.setsockopt(zmq.SUBSCRIBE, b"") # All topics + socket.setsockopt(zmq.LINGER, 0) # All topics + socket.RCVTIMEO = 1000 # timeout: 1 sec + + # Connect + socket.connect("tcp://%s:%s" % (host, port)) + + # Try to receive + try: + socket.recv() + + return True + except Exception: + return False + finally: + socket.close()