Backmerge branch 'develop' into bugfix/rename_Cnc_island

# Conflicts:
#	README.md
This commit is contained in:
Itay Mizeretz 2018-02-22 10:50:08 +02:00
commit bbfb801603
14 changed files with 153 additions and 84 deletions

BIN
.github/map-full.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -6,11 +6,13 @@ Infection Monkey
Welcome to the Infection Monkey! Welcome to the Infection Monkey!
The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island Command and Control server. The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server.
![Infection Monkey map](.github/map-full.png)
The Infection Monkey is comprised of two parts: The Infection Monkey is comprised of two parts:
* Chaos Monkey - A tool which infects other machines and propagates to them * Monkey - A tool which infects other machines and propagates to them
* Monkey Island - A dedicated UI to visualize the Chaos Monkey's progress inside the data center * Monkey Island - A C&C server with a dedicated UI to visualize the Chaos Monkey's progress inside the data center
To read more about the Monkey, visit http://infectionmonkey.com To read more about the Monkey, visit http://infectionmonkey.com
@ -33,34 +35,19 @@ The Infection Monkey uses the following techniques and exploits to propagate to
* SambaCry * SambaCry
* Elastic Search (CVE-2015-1427) * Elastic Search (CVE-2015-1427)
Setup
Getting Started -------------------------------
--------------- Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki.
### Requirements
The Monkey Island server has been tested on Ubuntu 14.04,15.04 and 16.04 and Windows Server 2012.
The Monkey itself has been tested on Windows XP, 7, 8.1 and 10. The Linux build has been tested on Ubuntu server and Debian (multiple versions).
### Installation
For off-the-shelf use, download a Debian package from our website and follow the guide [written in our blog](https://www.guardicore.com/2016/07/infection-monkey-loose-2/).
Warning! The Debian package will uninstall the python library 'bson' because of an issue with pymongo. You can reinstall it later, but monkey island will probably not work.
To manually set up and the Monkey Island server follow the instructions on [Monkey Island readme](monkey_island/readme.txt). If you wish to compile the binaries yourself, follow the instructions under Building the Monkey from Source.
### Start Infecting
After installing the Infection Monkey on a server of your choice, just browse https://your-server-ip:5000 and follow the instructions to start infecting.
Building the Monkey from source Building the Monkey from source
------------------------------- -------------------------------
If you want to build the monkey from source instead of using our provided packages, follow the instructions at the readme files under [chaos_monkey](chaos_monkey) and [monkey_island](monkey_island). If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/setup)
and follow the instructions at the readme files under [chaos_monkey](chaos_monkey) and [monkey_island](monkey_island).
License License
======= =======
Copyright (c) 2017 Guardicore Ltd Copyright (c) 2017 Guardicore Ltd
See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3).

View File

@ -13,7 +13,7 @@ from exploit import HostExploiter
from exploit.tools import HTTPTools, get_monkey_depth from exploit.tools import HTTPTools, get_monkey_depth
from exploit.tools import get_target_monkey from exploit.tools import get_target_monkey
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from network.tools import check_port_tcp from network.tools import check_tcp_port
from tools import build_monkey_commandline from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
@ -245,7 +245,7 @@ class RdpExploiter(HostExploiter):
return True return True
if not self.host.os.get('type'): if not self.host.os.get('type'):
is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT) is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if is_open: if is_open:
self.host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return True return True
@ -254,7 +254,7 @@ class RdpExploiter(HostExploiter):
def exploit_host(self): def exploit_host(self):
global g_reactor global g_reactor
is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT) is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if not is_open: if not is_open:
LOG.info("RDP port is closed on %r, skipping", self.host) LOG.info("RDP port is closed on %r, skipping", self.host)
return False return False

View File

@ -7,7 +7,7 @@ from exploit import HostExploiter
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from network import SMBFinger from network import SMBFinger
from network.tools import check_port_tcp from network.tools import check_tcp_port
from tools import build_monkey_commandline from tools import build_monkey_commandline
LOG = getLogger(__name__) LOG = getLogger(__name__)
@ -31,12 +31,12 @@ class SmbExploiter(HostExploiter):
return True return True
if not self.host.os.get('type'): if not self.host.os.get('type'):
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445) is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
smb_finger.get_host_fingerprint(self.host) smb_finger.get_host_fingerprint(self.host)
else: else:
is_nb_open, _ = check_port_tcp(self.host.ip_addr, 139) is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139)
if is_nb_open: if is_nb_open:
self.host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return self.host.os.get('type') in self._TARGET_OS_TYPE return self.host.os.get('type') in self._TARGET_OS_TYPE

View File

@ -7,7 +7,7 @@ import monkeyfs
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import get_target_monkey, get_monkey_depth from exploit.tools import get_target_monkey, get_monkey_depth
from model import MONKEY_ARG from model import MONKEY_ARG
from network.tools import check_port_tcp from network.tools import check_tcp_port
from tools import build_monkey_commandline from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
@ -41,7 +41,7 @@ class SSHExploiter(HostExploiter):
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
port = int(servkey.replace('tcp-', '')) port = int(servkey.replace('tcp-', ''))
is_open, _ = check_port_tcp(self.host.ip_addr, port) is_open, _ = check_tcp_port(self.host.ip_addr, port)
if not is_open: if not is_open:
LOG.info("SSH port is closed on %r, skipping", self.host) LOG.info("SSH port is closed on %r, skipping", self.host)
return False return False

View File

@ -17,7 +17,7 @@ from impacket.dcerpc.v5 import transport
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from network import SMBFinger from network import SMBFinger
from network.tools import check_port_tcp from network.tools import check_tcp_port
from tools import build_monkey_commandline from tools import build_monkey_commandline
from . import HostExploiter from . import HostExploiter
@ -168,7 +168,7 @@ class Ms08_067_Exploiter(HostExploiter):
if not self.host.os.get('type') or ( if not self.host.os.get('type') or (
self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')): self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')):
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445) is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
if smb_finger.get_host_fingerprint(self.host): if smb_finger.get_host_fingerprint(self.host):

View File

@ -1,9 +1,10 @@
import time
import logging import logging
from . import HostScanner import time
from config import WormConfiguration from config import WormConfiguration
from info import local_ips, get_ips_from_interfaces from info import local_ips, get_ips_from_interfaces
from range import * from range import *
from . import HostScanner
__author__ = 'itamar' __author__ = 'itamar'
@ -18,6 +19,12 @@ class NetworkScanner(object):
self._ranges = None self._ranges = None
def initialize(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.
:return:
"""
# get local ip addresses # get local ip addresses
self._ip_addresses = local_ips() self._ip_addresses = local_ips()
@ -27,7 +34,7 @@ class NetworkScanner(object):
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses) LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
# for fixed range, only scan once. # for fixed range, only scan once.
if WormConfiguration.range_class is FixedRange: if WormConfiguration.range_class is FixedRange:
self._ranges = [WormConfiguration.range_class(None)] self._ranges = [WormConfiguration.range_class(fixed_addresses=WormConfiguration.range_fixed)]
else: else:
self._ranges = [WormConfiguration.range_class(ip_address) self._ranges = [WormConfiguration.range_class(ip_address)
for ip_address in self._ip_addresses] for ip_address in self._ip_addresses]

View File

@ -1,10 +1,11 @@
import os
import sys
import subprocess
import logging import logging
from . import HostScanner, HostFinger import os
from model.host import VictimHost
import re import re
import subprocess
import sys
from model.host import VictimHost
from . import HostScanner, HostFinger
__author__ = 'itamar' __author__ = 'itamar'
@ -62,7 +63,7 @@ class PingScanner(HostScanner, HostFinger):
elif WINDOWS_TTL == ttl: elif WINDOWS_TTL == ttl:
host.os['type'] = 'windows' host.os['type'] = 'windows'
return True return True
except Exception, exc: except Exception as exc:
LOG.debug("Error parsing ping fingerprint: %s", exc) LOG.debug("Error parsing ping fingerprint: %s", exc)
return False return False

View File

@ -1,7 +1,8 @@
import socket
import random import random
import socket
import struct import struct
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from model.host import VictimHost from model.host import VictimHost
__author__ = 'itamar' __author__ = 'itamar'
@ -77,5 +78,5 @@ class FixedRange(NetworkRange):
for address in self._fixed_addresses: for address in self._fixed_addresses:
if not address: # Empty string if not address: # Empty string
continue continue
address_range.append(struct.unpack(">L", socket.inet_aton(address))[0]) address_range.append(struct.unpack(">L", socket.inet_aton(address.strip()))[0])
return address_range return address_range

View File

@ -1,7 +1,8 @@
import re import re
from network import HostFinger
from network.tools import check_port_tcp
from model.host import VictimHost from model.host import VictimHost
from network import HostFinger
from network.tools import check_tcp_port
SSH_PORT = 22 SSH_PORT = 22
SSH_SERVICE_DEFAULT = 'tcp-22' SSH_SERVICE_DEFAULT = 'tcp-22'
@ -38,7 +39,7 @@ class SSHFinger(HostFinger):
self._banner_match(name, host, banner) self._banner_match(name, host, banner)
return return
is_open, banner = check_port_tcp(host.ip_addr, SSH_PORT, TIMEOUT, True) is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True)
if is_open: if is_open:
host.services[SSH_SERVICE_DEFAULT] = {} host.services[SSH_SERVICE_DEFAULT] = {}

View File

@ -1,8 +1,8 @@
import time from itertools import izip_longest
from random import shuffle from random import shuffle
from network import HostScanner, HostFinger from network import HostScanner, HostFinger
from model.host import VictimHost from network.tools import check_tcp_ports
from network.tools import check_port_tcp
__author__ = 'itamar' __author__ = 'itamar'
@ -17,29 +17,25 @@ class TcpScanner(HostScanner, HostFinger):
return self.get_host_fingerprint(host, True) return self.get_host_fingerprint(host, True)
def get_host_fingerprint(self, host, only_one_port=False): def get_host_fingerprint(self, host, only_one_port=False):
assert isinstance(host, VictimHost) """
Scans a target host to see if it's alive using the tcp_target_ports specified in the configuration.
:param host: VictimHost structure
:param only_one_port: Currently unused.
:return: T/F if there is at least one open port. In addition, the host object is updated to mark those services as alive.
"""
count = 0
# maybe hide under really bad detection systems # maybe hide under really bad detection systems
target_ports = self._config.tcp_target_ports[:] target_ports = self._config.tcp_target_ports[:]
shuffle(target_ports) shuffle(target_ports)
for target_port in target_ports: ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0,
self._config.tcp_scan_get_banner)
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
service = 'tcp-' + str(target_port)
host.services[service] = {}
if banner:
host.services[service]['banner'] = banner
if only_one_port:
break
is_open, banner = check_port_tcp(host.ip_addr, return len(ports) != 0
target_port,
self._config.tcp_scan_timeout / 1000.0,
self._config.tcp_scan_get_banner)
if is_open:
count += 1
service = 'tcp-' + str(target_port)
host.services[service] = {}
if banner:
host.services[service]['banner'] = banner
if only_one_port:
break
else:
time.sleep(self._config.tcp_scan_interval / 1000.0)
return count != 0

View File

@ -1,7 +1,8 @@
import socket
import select
import logging import logging
import select
import socket
import struct import struct
import time
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
BANNER_READ = 1024 BANNER_READ = 1024
@ -32,10 +33,18 @@ def struct_unpack_tracker_string(data, index):
""" """
ascii_len = data[index:].find('\0') ascii_len = data[index:].find('\0')
fmt = "%ds" % ascii_len fmt = "%ds" % ascii_len
return struct_unpack_tracker(data,index,fmt) return struct_unpack_tracker(data, index, fmt)
def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
"""
Checks if a given TCP port is open
:param ip: Target IP
:param port: Target Port
:param timeout: Timeout for socket connection
:param get_banner: if true, pulls first BANNER_READ bytes from the socket.
:return: Tuple, T/F + banner if requested.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout) sock.settimeout(timeout)
@ -43,7 +52,7 @@ def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
sock.connect((ip, port)) sock.connect((ip, port))
except socket.timeout: except socket.timeout:
return False, None return False, None
except socket.error, exc: except socket.error as exc:
LOG.debug("Check port: %s:%s, Exception: %s", ip, port, exc) LOG.debug("Check port: %s:%s, Exception: %s", ip, port, exc)
return False, None return False, None
@ -54,26 +63,88 @@ def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
read_ready, _, _ = select.select([sock], [], [], timeout) read_ready, _, _ = select.select([sock], [], [], timeout)
if len(read_ready) > 0: if len(read_ready) > 0:
banner = sock.recv(BANNER_READ) banner = sock.recv(BANNER_READ)
except: except socket.error:
pass pass
sock.close() sock.close()
return True, banner return True, banner
def check_port_udp(ip, port, timeout=DEFAULT_TIMEOUT): def check_udp_port(ip, port, timeout=DEFAULT_TIMEOUT):
"""
Checks if a given UDP port is open by checking if it replies to an empty message
:param ip: Target IP
:param port: Target port
:param timeout: Timeout to wait
:return: Tuple, T/F + banner
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout) sock.settimeout(timeout)
data = None data = None
is_open = False is_open = False
try: try:
sock.sendto("-", (ip, port)) sock.sendto("-", (ip, port))
data, _ = sock.recvfrom(BANNER_READ) data, _ = sock.recvfrom(BANNER_READ)
is_open = True is_open = True
except: except socket.error:
pass pass
sock.close() sock.close()
return is_open, data return is_open, data
def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
"""
Checks whether any of the given ports are open on a target IP.
:param ip: IP of host to attack
:param ports: List of ports to attack. Must not be empty.
:param timeout: Amount of time to wait for connection
:param get_banner: T/F if to get first packets from server
:return: list of open ports. If get_banner=True, then a matching list of banners.
"""
sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports))]
[s.setblocking(0) for s in sockets]
good_ports = []
try:
LOG.debug("Connecting to the following ports %s" % ",".join((str(x) for x in ports)))
for sock, port in zip(sockets, ports):
err = sock.connect_ex((ip, port))
if err == 0:
good_ports.append((port, sock))
continue
if err == 10035: # WSAEWOULDBLOCK is valid, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
good_ports.append((port, sock))
continue
if err == 115: # EINPROGRESS 115 /* Operation now in progress */
good_ports.append((port, sock))
continue
LOG.warning("Failed to connect to port %s, error code is %d", port, err)
if len(good_ports) != 0:
time.sleep(timeout)
# this is possibly connected. meaning after timeout wait, we expect to see a connection up
# Possible valid errors codes if we chose to check for actually closed are
# ECONNREFUSED (111) or WSAECONNREFUSED (10061) or WSAETIMEDOUT(10060)
connected_ports_sockets = [s for s in good_ports if
s[1].getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0]
LOG.debug(
"On host %s discovered the following ports %s" %
(str(ip), ",".join([str(x[0]) for x in connected_ports_sockets])))
banners = []
if get_banner:
readable_sockets, _, _ = select.select([s[1] for s in connected_ports_sockets], [], [], 0)
# read first BANNER_READ bytes
banners = [sock.recv(BANNER_READ) if sock in readable_sockets else ""
for port, sock in connected_ports_sockets]
pass
# try to cleanup
[s[1].close() for s in good_ports]
return [port for port, sock in connected_ports_sockets], banners
else:
return [], []
except socket.error as exc:
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
return [], []

View File

@ -8,7 +8,7 @@ from threading import Thread
from model import VictimHost from model import VictimHost
from network.firewall import app as firewall from network.firewall import app as firewall
from network.info import local_ips, get_free_tcp_port from network.info import local_ips, get_free_tcp_port
from network.tools import check_port_tcp from network.tools import check_tcp_port
from transport.base import get_last_serve_time from transport.base import get_last_serve_time
__author__ = 'hoffer' __author__ = 'hoffer'
@ -40,7 +40,7 @@ def _check_tunnel(address, port, existing_sock=None):
sock = existing_sock sock = existing_sock
LOG.debug("Checking tunnel %s:%s", address, port) LOG.debug("Checking tunnel %s:%s", address, port)
is_open, _ = check_port_tcp(address, int(port)) is_open, _ = check_tcp_port(address, int(port))
if not is_open: if not is_open:
LOG.debug("Could not connect to %s:%s", address, port) LOG.debug("Could not connect to %s:%s", address, port)
if not existing_sock: if not existing_sock:

View File

@ -0,0 +1,5 @@
#!/bin/bash
cd /var/monkey_island/cc
/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db &
/var/monkey_island/bin/python/bin/python main.py