From 3da0c1a57c5cbeb57463e307314071966b6e9bb3 Mon Sep 17 00:00:00 2001 From: itsikkes Date: Fri, 15 Jul 2016 16:54:46 +0300 Subject: [PATCH] Added auto-scan subnets option Monkey is now able to auto scan the local host subnets, removing the need to preconfigure it to scan the network subnets (option is on by default) --- chaos_monkey/config.py | 5 +- chaos_monkey/control.py | 3 +- chaos_monkey/network/info.py | 102 +++++++++++++++++------- chaos_monkey/network/network_scanner.py | 6 +- chaos_monkey/network/range.py | 10 ++- chaos_monkey/readme.txt | 3 +- 6 files changed, 93 insertions(+), 36 deletions(-) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index c3767a362..dd6da0a4d 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -141,12 +141,15 @@ class Configuration(object): retry_failed_explotation = True - internet_services = ["www.guardicore.com", "www.google.com"] + internet_services = ["monkey.guardicore.com", "www.google.com"] ########################### # scanners config ########################### + # Auto detect and scan local subnets + local_network_scan = True + range_class = FixedRange range_size = 1 range_fixed = ["", ] diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index dc4bf198d..d262a78ee 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -53,7 +53,8 @@ class ControlClient(object): data=json.dumps(monkey), headers={'content-type': 'application/json'}, verify=False, - proxies=ControlClient.proxies) + proxies=ControlClient.proxies, + timeout=20) break except Exception, exc: diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py index 09c484605..491274970 100644 --- a/chaos_monkey/network/info.py +++ b/chaos_monkey/network/info.py @@ -4,46 +4,74 @@ import socket import struct import array import psutil +import ipaddress from random import randint -__author__ = 'hoffer' - if sys.platform == "win32": + import netifaces + def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] + def get_host_subnets(only_ips=False): + network_adapters = [] + valid_ips = local_ips() + if only_ips: + return valid_ips + interfaces = [netifaces.ifaddresses(x) for x in netifaces.interfaces()] + for inte in interfaces: + if netifaces.AF_INET in inte: + for add in inte[netifaces.AF_INET]: + if "netmask" in add and add["addr"] in valid_ips: + network_adapters.append((add["addr"], add["netmask"])) + return network_adapters + else: import fcntl - def local_ips(): - result = [] - try: - is_64bits = sys.maxsize > 2**32 - struct_size = 40 if is_64bits else 32 - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - max_possible = 8 # initial value - while True: - bytes = max_possible * struct_size - names = array.array('B', '\0' * bytes) - outbytes = struct.unpack('iL', fcntl.ioctl( - s.fileno(), - 0x8912, # SIOCGIFCONF - struct.pack('iL', bytes, names.buffer_info()[0]) - ))[0] - if outbytes == bytes: - max_possible *= 2 - else: - break - namestr = names.tostring() + def get_host_subnets(only_ips=False): + """Get the list of Linux network adapters.""" + import fcntl + max_bytes = 8096 + is_64bits = sys.maxsize > 2 ** 32 + if is_64bits: + offset1 = 16 + offset2 = 40 + else: + offset1 = 32 + offset2 = 32 + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', '\0' * max_bytes) + outbytes = struct.unpack('iL', fcntl.ioctl( + sock.fileno(), + 0x8912, + struct.pack('iL', max_bytes, names.buffer_info()[0])))[0] + adapter_names = [names.tostring()[n_cnt:n_cnt + offset1].split('\0', 1)[0] + for n_cnt in xrange(0, outbytes, offset2)] + network_adapters = [] + for adapter_name in adapter_names: + ip_address = socket.inet_ntoa(fcntl.ioctl( + sock.fileno(), + 0x8915, + struct.pack('256s', adapter_name))[20:24]) + if ip_address.startswith('127'): + continue + subnet_mask = socket.inet_ntoa(fcntl.ioctl( + sock.fileno(), + 0x891b, + struct.pack('256s', adapter_name))[20:24]) - for i in range(0, outbytes, struct_size): - addr = socket.inet_ntoa(namestr[i+20:i+24]) - if not addr.startswith('127'): - result.append(addr) - # name of interface is (namestr[i:i+16].split('\0', 1)[0] - finally: - return result + if only_ips: + network_adapters.append(ip_address) + else: + network_adapters.append((ip_address, subnet_mask)) + + return network_adapters + + + def local_ips(): + return get_host_subnets(only_ips=True) def get_free_tcp_port(min_range=1000, max_range=65535): @@ -60,9 +88,25 @@ def get_free_tcp_port(min_range=1000, max_range=65535): return None + def check_internet_access(services): ping_str = "-n 1" if sys.platform.startswith("win") else "-c 1" for host in services: if os.system("ping " + ping_str + " " + host) == 0: return True return False + + +def get_ips_from_interfaces(): + res = [] + ifs = get_host_subnets() + for interface in ifs: + ipint = ipaddress.ip_interface(u"%s/%s" % interface) + # limit subnet scans to class C only + if ipint.network.num_addresses > 255: + ipint = ipaddress.ip_interface(u"%s/24" % interface[0]) + for addr in ipint.network.hosts(): + if str(addr) == interface[0]: + continue + res.append(str(addr)) + return res diff --git a/chaos_monkey/network/network_scanner.py b/chaos_monkey/network/network_scanner.py index 430c4f68a..9c03ca4b8 100644 --- a/chaos_monkey/network/network_scanner.py +++ b/chaos_monkey/network/network_scanner.py @@ -2,7 +2,7 @@ import time import logging from . import HostScanner from config import WormConfiguration -from info import local_ips +from info import local_ips, get_ips_from_interfaces from range import * __author__ = 'itamar' @@ -27,10 +27,12 @@ class NetworkScanner(object): LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses) # for fixed range, only scan once. if WormConfiguration.range_class is FixedRange: - self._ranges = [WormConfiguration.range_class('0.0.0.0')] + self._ranges = [WormConfiguration.range_class(None)] else: self._ranges = [WormConfiguration.range_class(ip_address) for ip_address in self._ip_addresses] + if WormConfiguration.local_network_scan: + self._ranges += [FixedRange([ip_address for ip_address in get_ips_from_interfaces()])] LOG.info("Base local networks to scan are: %r", self._ranges) def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): diff --git a/chaos_monkey/network/range.py b/chaos_monkey/network/range.py index 0dfcf69ad..f62e895d1 100644 --- a/chaos_monkey/network/range.py +++ b/chaos_monkey/network/range.py @@ -58,10 +58,16 @@ class RelativeRange(NetworkRange): class FixedRange(NetworkRange): - def __init__(self, base_address, shuffle=True): + def __init__(self, fixed_addresses=None, shuffle=True): base_address = 0 super(FixedRange, self).__init__(base_address, shuffle=shuffle) - self._fixed_addresses = self._config.range_fixed + if not fixed_addresses: + self._fixed_addresses = self._config.range_fixed + else: + if type(fixed_addresses) is str: + self._fixed_addresses = [fixed_addresses] + else: + self._fixed_addresses = list(fixed_addresses) def __repr__(self): return "" % (",".join(self._fixed_addresses)) diff --git a/chaos_monkey/readme.txt b/chaos_monkey/readme.txt index 23a1c2aa0..82bb61f9b 100644 --- a/chaos_monkey/readme.txt +++ b/chaos_monkey/readme.txt @@ -21,7 +21,8 @@ Windows: python -m pip install requests python -m pip install odict python -m pip install paramiko - python -m pip install psutil + python -m pip install psutil + python -m pip install netifaces python -m pip install PyInstaller type > C:\Python27\Lib\site-packages\zope\__init__.py 7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe: