261 lines
6.7 KiB
Python
261 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import six
|
|
import zmq
|
|
import json
|
|
import redis
|
|
import socket
|
|
import logging
|
|
import eventlet
|
|
import ipaddress
|
|
import amqp.connection
|
|
|
|
|
|
from functools import partial
|
|
from collections import defaultdict
|
|
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()
|
|
|
|
OPEN_SERVICES = defaultdict(dict)
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
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_type, handle in six.iteritems(handlers):
|
|
|
|
log.info(" >> Trying to find %s service in '%s' port '%s'." % (server_type, host, port))
|
|
|
|
try:
|
|
|
|
# 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.debug("%s : %s error: %s" % (server_type, port, e))
|
|
continue
|
|
finally:
|
|
s.close()
|
|
|
|
# Is port open?
|
|
if result == 0:
|
|
log.error(" ) Port '%s' is open in '%s'" % (port, host))
|
|
|
|
if handle(host, port, config) is True:
|
|
log.error(" <!!> Open '%s' server found in port '%s'" % (server_type, port))
|
|
|
|
OPEN_SERVICES[host][server_type] = dict(
|
|
state="open",
|
|
port=port
|
|
)
|
|
else:
|
|
log.debug(" <i> 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()
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Export results
|
|
# --------------------------------------------------------------------------
|
|
if config.output is not None:
|
|
_output_path = "%s.json" % config.output if ".json" not in config.output else config.output
|
|
|
|
with open(_output_path, "w") as f:
|
|
json.dump(OPEN_SERVICES, f)
|
|
|
|
log.error(" - Output results saved into: %s" % _output_path)
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
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(" <ii> 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(" <ii> 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()
|