Skip to content

Commit

Permalink
Merge pull request #94 from guardicore/feature/support-subnet-in-config
Browse files Browse the repository at this point in the history
Feature/support subnet in config
  • Loading branch information
itaymmguardicore authored Apr 12, 2018
2 parents 2365f4d + a77044d commit 768d144
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 140 deletions.
1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = 'itay.mizeretz'
1 change: 1 addition & 0 deletions common/network/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__author__ = 'itay.mizeretz'
122 changes: 122 additions & 0 deletions common/network/network_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import random
import socket
import struct
from abc import ABCMeta, abstractmethod

import ipaddress

__author__ = 'itamar'


class NetworkRange(object):
__metaclass__ = ABCMeta

def __init__(self, shuffle=True):
self._shuffle = shuffle

def get_range(self):
"""
:return: Returns a sequence of IPs in an internal format (might be numbers)
"""
return self._get_range()

def __iter__(self):
"""
Iterator of ip addresses (strings) from the current range.
Use get_range if you want it in one go.
:return:
"""
base_range = self.get_range()
if self._shuffle:
random.shuffle(base_range)

for x in base_range:
yield self._number_to_ip(x)

@abstractmethod
def is_in_range(self, ip_address):
raise NotImplementedError()

@abstractmethod
def _get_range(self):
raise NotImplementedError()

@staticmethod
def get_range_obj(address_str):
address_str = address_str.strip()
if not address_str: # Empty string
return None
if -1 != address_str.find('-'):
return IpRange(ip_range=address_str)
if -1 != address_str.find('/'):
return CidrRange(cidr_range=address_str)
return SingleIpRange(ip_address=address_str)

@staticmethod
def _ip_to_number(address):
return struct.unpack(">L", socket.inet_aton(address))[0]

@staticmethod
def _number_to_ip(num):
return socket.inet_ntoa(struct.pack(">L", num))


class CidrRange(NetworkRange):
def __init__(self, cidr_range, shuffle=True):
super(CidrRange, self).__init__(shuffle=shuffle)
self._cidr_range = cidr_range.strip()
self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False)

def __repr__(self):
return "<CidrRange %s>" % (self._cidr_range,)

def is_in_range(self, ip_address):
return ipaddress.ip_address(ip_address) in self._ip_network

def _get_range(self):
return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address]


class IpRange(NetworkRange):
def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True):
super(IpRange, self).__init__(shuffle=shuffle)
if ip_range is not None:
addresses = ip_range.split('-')
if len(addresses) != 2:
raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range)
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
elif (lower_end_ip is not None) and (higher_end_ip is not None):
self._lower_end_ip = lower_end_ip.strip()
self._higher_end_ip = higher_end_ip.strip()
else:
raise ValueError('Illegal IP range: %s' % ip_range)

self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip)
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
if self._higher_end_ip_num < self._lower_end_ip_num:
raise ValueError(
'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip))

def __repr__(self):
return "<IpRange %s-%s>" % (self._lower_end_ip, self._higher_end_ip)

def is_in_range(self, ip_address):
return self._lower_end_ip_num <= self._ip_to_number(ip_address) <= self._higher_end_ip_num

def _get_range(self):
return range(self._lower_end_ip_num, self._higher_end_ip_num + 1)


class SingleIpRange(NetworkRange):
def __init__(self, ip_address, shuffle=True):
super(SingleIpRange, self).__init__(shuffle=shuffle)
self._ip_address = ip_address

def __repr__(self):
return "<SingleIpRange %s>" % (self._ip_address,)

def is_in_range(self, ip_address):
return self._ip_address == ip_address

def _get_range(self):
return [SingleIpRange._ip_to_number(self._ip_address)]
4 changes: 1 addition & 3 deletions infection_monkey/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
SambaCryExploiter, ElasticGroovyExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
from network.range import FixedRange

__author__ = 'itamar'

Expand Down Expand Up @@ -183,8 +182,7 @@ def as_dict(self):
# Auto detect and scan local subnets
local_network_scan = True

range_class = FixedRange
range_fixed = ['', ]
subnet_scan_list = ['', ]

blocked_ips = ['', ]

Expand Down
3 changes: 1 addition & 2 deletions infection_monkey/example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"www.google.com"
],
"keep_tunnel_open_time": 60,
"range_class": "RelativeRange",
"range_fixed": [
"subnet_scan_list": [
""
],
"blocked_ips": [""],
Expand Down
2 changes: 1 addition & 1 deletion infection_monkey/monkey-linux.spec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ block_cipher = None


a = Analysis(['main.py'],
pathex=['.'],
pathex=['.', '..'],
binaries=None,
datas=None,
hiddenimports=['_cffi_backend'],
Expand Down
2 changes: 1 addition & 1 deletion infection_monkey/monkey.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import platform
a = Analysis(['main.py'],
pathex=['.'],
pathex=['.', '..'],
hiddenimports=['_cffi_backend', 'queue'],
hookspath=None,
runtime_hooks=None)
Expand Down
13 changes: 5 additions & 8 deletions infection_monkey/network/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import netifaces
from subprocess import check_output
from random import randint
from common.network.network_range import CidrRange


def get_host_subnets():
Expand Down Expand Up @@ -129,7 +130,7 @@ def check_internet_access(services):
return False


def get_ips_from_interfaces():
def get_interfaces_ranges():
"""
Returns a list of IPs accessible in the host in each network interface, in the subnet.
Limits to a single class C if the network is larger
Expand All @@ -138,15 +139,11 @@ def get_ips_from_interfaces():
res = []
ifs = get_host_subnets()
for net_interface in ifs:
address_str = unicode(net_interface['addr'])
netmask_str = unicode(net_interface['netmask'])
host_address = ipaddress.ip_address(address_str)
address_str = net_interface['addr']
netmask_str = net_interface['netmask']
ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str))
# limit subnet scans to class C only
if ip_interface.network.num_addresses > 255:
ip_interface = ipaddress.ip_interface(u"%s/24" % address_str)
addrs = [str(addr) for addr in ip_interface.network.hosts() if addr != host_address]
res.extend(addrs)
res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str)))
return res


Expand Down
21 changes: 9 additions & 12 deletions infection_monkey/network/network_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import time

from config import WormConfiguration
from info import local_ips, get_ips_from_interfaces
from range import *
from info import local_ips, get_interfaces_ranges
from common.network.network_range import *
from model import VictimHost
from . import HostScanner

__author__ = 'itamar'
Expand All @@ -20,9 +21,8 @@ def __init__(self):

def initialize(self):
"""
Set up scanning based on configuration
FixedRange -> Reads from range_fixed field in configuration
otherwise, takes a range from every IP address the current host has.
Set up scanning.
based on configuration: scans local network and/or scans fixed list of IPs/subnets.
:return:
"""
# get local ip addresses
Expand All @@ -33,13 +33,9 @@ def initialize(self):

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(fixed_addresses=WormConfiguration.range_fixed)]
else:
self._ranges = [WormConfiguration.range_class(ip_address)
for ip_address in self._ip_addresses]
self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list]
if WormConfiguration.local_network_scan:
self._ranges += [FixedRange([ip_address for ip_address in get_ips_from_interfaces()])]
self._ranges += get_interfaces_ranges()
LOG.info("Base local networks to scan are: %r", self._ranges)

def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
Expand All @@ -50,7 +46,8 @@ def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):

for net_range in self._ranges:
LOG.debug("Scanning for potential victims in the network %r", net_range)
for victim in net_range:
for ip_addr in net_range:
victim = VictimHost(ip_addr)
if stop_callback and stop_callback():
LOG.debug("Got stop signal")
break
Expand Down
82 changes: 0 additions & 82 deletions infection_monkey/network/range.py

This file was deleted.

31 changes: 4 additions & 27 deletions monkey_island/cc/services/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,32 +202,9 @@
"Amount of hops allowed for the monkey to spread from the island. "
+ WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating too far"
}
}
},
"network_range": {
"title": "Network range",
"type": "object",
"properties": {
"range_class": {
"title": "Range class",
"type": "string",
"default": "FixedRange",
"enum": [
"FixedRange",
"ClassCRange"
],
"enumNames": [
"Fixed Range",
"Class C Range"
],
"description":
"Determines which class to use to determine scan range."
" Fixed Range will scan only specific IPs listed under Fixed range IP list."
" Class C Range will scan machines in the Class C network the monkey's on."
},
"range_fixed": {
"title": "Fixed range IP list",
"subnet_scan_list": {
"title": "Scan IP/subnet list",
"type": "array",
"uniqueItems": True,
"items": {
Expand All @@ -236,8 +213,8 @@
"default": [
],
"description":
"List of IPs to include when using FixedRange"
" (Only relevant for Fixed Range)"
"List of IPs/subnets the monkey should scan."
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
}
}
}
Expand Down
5 changes: 1 addition & 4 deletions monkey_island/cc/services/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,7 @@ def get_config_exploits():

@staticmethod
def get_config_ips():
if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True,
True) != 'FixedRange':
return []
return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True, True)
return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True, True)

@staticmethod
def get_config_scan():
Expand Down

0 comments on commit 768d144

Please sign in to comment.