Backmerge branch 'develop' into bugfix/rename_Cnc_island
# Conflicts: # README.md
This commit is contained in:
commit
bbfb801603
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
35
README.md
35
README.md
|
@ -6,11 +6,13 @@ 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:
|
||||
* Chaos 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 - A tool which infects other machines and propagates to them
|
||||
* 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
|
||||
|
||||
|
@ -33,34 +35,19 @@ The Infection Monkey uses the following techniques and exploits to propagate to
|
|||
* SambaCry
|
||||
* Elastic Search (CVE-2015-1427)
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### 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.
|
||||
Setup
|
||||
-------------------------------
|
||||
Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki.
|
||||
|
||||
|
||||
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
|
||||
=======
|
||||
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).
|
||||
|
|
|
@ -13,7 +13,7 @@ from exploit import HostExploiter
|
|||
from exploit.tools import HTTPTools, get_monkey_depth
|
||||
from exploit.tools import get_target_monkey
|
||||
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
|
||||
|
||||
__author__ = 'hoffer'
|
||||
|
@ -245,7 +245,7 @@ class RdpExploiter(HostExploiter):
|
|||
return True
|
||||
|
||||
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:
|
||||
self.host.os['type'] = 'windows'
|
||||
return True
|
||||
|
@ -254,7 +254,7 @@ class RdpExploiter(HostExploiter):
|
|||
def exploit_host(self):
|
||||
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:
|
||||
LOG.info("RDP port is closed on %r, skipping", self.host)
|
||||
return False
|
||||
|
|
|
@ -7,7 +7,7 @@ from exploit import HostExploiter
|
|||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
||||
from network import SMBFinger
|
||||
from network.tools import check_port_tcp
|
||||
from network.tools import check_tcp_port
|
||||
from tools import build_monkey_commandline
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
|
@ -31,12 +31,12 @@ class SmbExploiter(HostExploiter):
|
|||
return True
|
||||
|
||||
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:
|
||||
smb_finger = SMBFinger()
|
||||
smb_finger.get_host_fingerprint(self.host)
|
||||
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:
|
||||
self.host.os['type'] = 'windows'
|
||||
return self.host.os.get('type') in self._TARGET_OS_TYPE
|
||||
|
|
|
@ -7,7 +7,7 @@ import monkeyfs
|
|||
from exploit import HostExploiter
|
||||
from exploit.tools import get_target_monkey, get_monkey_depth
|
||||
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
|
||||
|
||||
__author__ = 'hoffer'
|
||||
|
@ -41,7 +41,7 @@ class SSHExploiter(HostExploiter):
|
|||
if servdata.get('name') == 'ssh' and servkey.startswith('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:
|
||||
LOG.info("SSH port is closed on %r, skipping", self.host)
|
||||
return False
|
||||
|
|
|
@ -17,7 +17,7 @@ from impacket.dcerpc.v5 import transport
|
|||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||
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 . import HostExploiter
|
||||
|
||||
|
@ -168,7 +168,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
|
||||
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')):
|
||||
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:
|
||||
smb_finger = SMBFinger()
|
||||
if smb_finger.get_host_fingerprint(self.host):
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import time
|
||||
import logging
|
||||
from . import HostScanner
|
||||
import time
|
||||
|
||||
from config import WormConfiguration
|
||||
from info import local_ips, get_ips_from_interfaces
|
||||
from range import *
|
||||
from . import HostScanner
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -18,6 +19,12 @@ class NetworkScanner(object):
|
|||
self._ranges = None
|
||||
|
||||
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
|
||||
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)
|
||||
# for fixed range, only scan once.
|
||||
if WormConfiguration.range_class is FixedRange:
|
||||
self._ranges = [WormConfiguration.range_class(None)]
|
||||
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]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import logging
|
||||
from . import HostScanner, HostFinger
|
||||
from model.host import VictimHost
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from model.host import VictimHost
|
||||
from . import HostScanner, HostFinger
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -62,7 +63,7 @@ class PingScanner(HostScanner, HostFinger):
|
|||
elif WINDOWS_TTL == ttl:
|
||||
host.os['type'] = 'windows'
|
||||
return True
|
||||
except Exception, exc:
|
||||
except Exception as exc:
|
||||
LOG.debug("Error parsing ping fingerprint: %s", exc)
|
||||
|
||||
return False
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import socket
|
||||
import random
|
||||
import socket
|
||||
import struct
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from model.host import VictimHost
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
@ -77,5 +78,5 @@ class FixedRange(NetworkRange):
|
|||
for address in self._fixed_addresses:
|
||||
if not address: # Empty string
|
||||
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
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import re
|
||||
from network import HostFinger
|
||||
from network.tools import check_port_tcp
|
||||
|
||||
from model.host import VictimHost
|
||||
from network import HostFinger
|
||||
from network.tools import check_tcp_port
|
||||
|
||||
SSH_PORT = 22
|
||||
SSH_SERVICE_DEFAULT = 'tcp-22'
|
||||
|
@ -38,7 +39,7 @@ class SSHFinger(HostFinger):
|
|||
self._banner_match(name, host, banner)
|
||||
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:
|
||||
host.services[SSH_SERVICE_DEFAULT] = {}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import time
|
||||
from itertools import izip_longest
|
||||
from random import shuffle
|
||||
|
||||
from network import HostScanner, HostFinger
|
||||
from model.host import VictimHost
|
||||
from network.tools import check_port_tcp
|
||||
from network.tools import check_tcp_ports
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -17,29 +17,25 @@ class TcpScanner(HostScanner, HostFinger):
|
|||
return self.get_host_fingerprint(host, True)
|
||||
|
||||
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
|
||||
target_ports = self._config.tcp_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,
|
||||
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
|
||||
return len(ports) != 0
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import socket
|
||||
import select
|
||||
import logging
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
||||
DEFAULT_TIMEOUT = 10
|
||||
BANNER_READ = 1024
|
||||
|
@ -32,10 +33,18 @@ def struct_unpack_tracker_string(data, index):
|
|||
"""
|
||||
ascii_len = data[index:].find('\0')
|
||||
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.settimeout(timeout)
|
||||
|
||||
|
@ -43,7 +52,7 @@ def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
|||
sock.connect((ip, port))
|
||||
except socket.timeout:
|
||||
return False, None
|
||||
except socket.error, exc:
|
||||
except socket.error as exc:
|
||||
LOG.debug("Check port: %s:%s, Exception: %s", ip, port, exc)
|
||||
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)
|
||||
if len(read_ready) > 0:
|
||||
banner = sock.recv(BANNER_READ)
|
||||
except:
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
|
||||
sock.close()
|
||||
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.settimeout(timeout)
|
||||
|
||||
|
||||
data = None
|
||||
is_open = False
|
||||
|
||||
|
||||
try:
|
||||
sock.sendto("-", (ip, port))
|
||||
data, _ = sock.recvfrom(BANNER_READ)
|
||||
is_open = True
|
||||
except:
|
||||
except socket.error:
|
||||
pass
|
||||
sock.close()
|
||||
|
||||
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 [], []
|
||||
|
|
|
@ -8,7 +8,7 @@ from threading import Thread
|
|||
from model import VictimHost
|
||||
from network.firewall import app as firewall
|
||||
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
|
||||
|
||||
__author__ = 'hoffer'
|
||||
|
@ -40,7 +40,7 @@ def _check_tunnel(address, port, existing_sock=None):
|
|||
sock = existing_sock
|
||||
|
||||
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:
|
||||
LOG.debug("Could not connect to %s:%s", address, port)
|
||||
if not existing_sock:
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue