From 44b6c3d2433f675c3ec72231f63db3f744f56e9b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 6 Feb 2018 19:54:41 +0200 Subject: [PATCH 01/26] Bugfix in address lookup --- chaos_monkey/network/range.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chaos_monkey/network/range.py b/chaos_monkey/network/range.py index fdd29bc09..b07828f4b 100644 --- a/chaos_monkey/network/range.py +++ b/chaos_monkey/network/range.py @@ -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 From ab18005fd079ab347376ff542ee8c4f8067a9277 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 6 Feb 2018 19:59:04 +0200 Subject: [PATCH 02/26] Fix ping scanner exceptions --- chaos_monkey/network/ping_scanner.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/chaos_monkey/network/ping_scanner.py b/chaos_monkey/network/ping_scanner.py index 842a6aee8..7162c36f3 100644 --- a/chaos_monkey/network/ping_scanner.py +++ b/chaos_monkey/network/ping_scanner.py @@ -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 From fe2af59975a3a1d6efe27b8929c584823d460c5b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 6 Feb 2018 19:55:01 +0200 Subject: [PATCH 03/26] Rename to check_tcp|udp_port and refactor. --- chaos_monkey/exploit/rdpgrinder.py | 6 +++--- chaos_monkey/exploit/smbexec.py | 6 +++--- chaos_monkey/exploit/sshexec.py | 4 ++-- chaos_monkey/exploit/win_ms08_067.py | 4 ++-- chaos_monkey/network/sshfinger.py | 7 ++++--- chaos_monkey/network/tcp_scanner.py | 7 ++++--- chaos_monkey/network/tools.py | 20 ++++++++++---------- chaos_monkey/tunnel.py | 4 ++-- 8 files changed, 30 insertions(+), 28 deletions(-) diff --git a/chaos_monkey/exploit/rdpgrinder.py b/chaos_monkey/exploit/rdpgrinder.py index 207564778..606f44f90 100644 --- a/chaos_monkey/exploit/rdpgrinder.py +++ b/chaos_monkey/exploit/rdpgrinder.py @@ -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 diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py index f5fa2b26b..b76a7bce6 100644 --- a/chaos_monkey/exploit/smbexec.py +++ b/chaos_monkey/exploit/smbexec.py @@ -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 diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index f58e5677b..b93970ca9 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -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 diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index 3ed553931..51393ea69 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -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): diff --git a/chaos_monkey/network/sshfinger.py b/chaos_monkey/network/sshfinger.py index 75a3380ca..89c3092d7 100644 --- a/chaos_monkey/network/sshfinger.py +++ b/chaos_monkey/network/sshfinger.py @@ -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] = {} diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index 8ce715f7f..4a6b8c40e 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -1,8 +1,9 @@ import time from random import shuffle -from network import HostScanner, HostFinger + from model.host import VictimHost -from network.tools import check_port_tcp +from network import HostScanner, HostFinger +from network.tools import check_tcp_port __author__ = 'itamar' @@ -26,7 +27,7 @@ class TcpScanner(HostScanner, HostFinger): for target_port in target_ports: - is_open, banner = check_port_tcp(host.ip_addr, + is_open, banner = check_tcp_port(host.ip_addr, target_port, self._config.tcp_scan_timeout / 1000.0, self._config.tcp_scan_get_banner) diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index 66f4eef57..b89002802 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -1,6 +1,6 @@ -import socket -import select import logging +import select +import socket import struct DEFAULT_TIMEOUT = 10 @@ -32,10 +32,10 @@ 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): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) @@ -43,7 +43,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 @@ -56,23 +56,23 @@ def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): banner = sock.recv(BANNER_READ) except: pass - + sock.close() return True, banner -def check_port_udp(ip, port, timeout=DEFAULT_TIMEOUT): +def check_udp_port(ip, port, timeout=DEFAULT_TIMEOUT): 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() diff --git a/chaos_monkey/tunnel.py b/chaos_monkey/tunnel.py index 7f7edec03..9a50679ff 100644 --- a/chaos_monkey/tunnel.py +++ b/chaos_monkey/tunnel.py @@ -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: From 7c6c153733dc8295b7e081c2ba9c1e9dcdf5d395 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 6 Feb 2018 19:56:25 +0200 Subject: [PATCH 04/26] Implement async TCP port scanning + banner grabbing --- chaos_monkey/network/network_scanner.py | 13 +++-- chaos_monkey/network/tcp_scanner.py | 40 +++++++-------- chaos_monkey/network/tools.py | 68 ++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/chaos_monkey/network/network_scanner.py b/chaos_monkey/network/network_scanner.py index 5a9037184..9c1cf897e 100644 --- a/chaos_monkey/network/network_scanner.py +++ b/chaos_monkey/network/network_scanner.py @@ -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] diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index 4a6b8c40e..c258a6397 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -1,9 +1,8 @@ -import time +from itertools import izip_longest from random import shuffle -from model.host import VictimHost from network import HostScanner, HostFinger -from network.tools import check_tcp_port +from network.tools import check_tcp_ports __author__ = 'itamar' @@ -18,29 +17,24 @@ 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) + 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_tcp_port(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 diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index b89002802..716be15a0 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -2,6 +2,7 @@ import logging import select import socket import struct +import time DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 @@ -36,6 +37,14 @@ def struct_unpack_tracker_string(data, index): 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) @@ -54,7 +63,7 @@ def check_tcp_port(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() @@ -62,6 +71,13 @@ def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): 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) @@ -77,3 +93,53 @@ def check_udp_port(ip, port, timeout=DEFAULT_TIMEOUT): 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)) + 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)) + + if len(good_ports) != 0: + time.sleep(timeout) + read_sockets, write_sockets, errored_sockets = \ + select.select( + [s[1] for s in good_ports], + [s[1] for s in good_ports], + [s[1] for s in good_ports], + 0) # no timeout because we've already slept + connected_ports_sockets = [x for x in good_ports if x[1] in write_sockets] + 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: + # read first X bytes + banners = [sock.recv(BANNER_READ) if sock in read_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 [], [] From 41de1a86e0e6898938928f15098783cd2130169a Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 6 Feb 2018 20:13:27 +0200 Subject: [PATCH 05/26] Fix, forgot to include the configuration of whether we're pulling the banner from victims. --- chaos_monkey/network/tcp_scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index c258a6397..e291e8d3e 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -28,7 +28,8 @@ class TcpScanner(HostScanner, HostFinger): target_ports = self._config.tcp_target_ports[:] shuffle(target_ports) - ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0) + 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] = {} From 0ed2f74824426224c7ef48508e4191c409e90fb8 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 13 Feb 2018 12:05:01 +0200 Subject: [PATCH 06/26] Add encryptor --- monkey_island/cc/encryptor.py | 48 +++++++++++++++++++ .../monkey_island_pip_requirements.txt | 1 + monkey_island/requirements.txt | 3 +- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 monkey_island/cc/encryptor.py diff --git a/monkey_island/cc/encryptor.py b/monkey_island/cc/encryptor.py new file mode 100644 index 000000000..7af0840d0 --- /dev/null +++ b/monkey_island/cc/encryptor.py @@ -0,0 +1,48 @@ +import base64 +import os + +from Crypto import Random +from Crypto.Cipher import AES + +__author__ = "itay.mizeretz" + + +class Encryptor: + _BLOCK_SIZE = 32 + _DB_PASSWORD_FILENAME = "mongo_key.bin" + + def __init__(self): + self._load_key() + + def _init_key(self): + self._cipher_key = Random.new().read(self._BLOCK_SIZE) + with open(self._DB_PASSWORD_FILENAME, 'wb') as f: + f.write(self._cipher_key) + + def _load_existing_key(self): + with open(self._DB_PASSWORD_FILENAME, 'rb') as f: + self._cipher_key = f.read() + + def _load_key(self): + if os.path.exists(self._DB_PASSWORD_FILENAME): + self._load_existing_key() + else: + self._init_key() + + def _pad(self, message): + return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( + self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) + + def _unpad(self, message): + return message[0:-ord(message[len(message) - 1])] + + def enc(self, message): + cipher_iv = Random.new().read(AES.block_size) + cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) + return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message))) + + def dec(self, enc_message): + enc_message = base64.b64decode(enc_message) + cipher_iv = enc_message[0:AES.block_size] + cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) + return self._unpad(cipher.decrypt(enc_message[AES.block_size:])) diff --git a/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey_island/deb-package/monkey_island_pip_requirements.txt index 404aad8b0..582efc5f2 100644 --- a/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -12,4 +12,5 @@ jsonschema netifaces ipaddress enum34 +PyCrypto virtualenv \ No newline at end of file diff --git a/monkey_island/requirements.txt b/monkey_island/requirements.txt index 9d8bfbfb8..18098eec0 100644 --- a/monkey_island/requirements.txt +++ b/monkey_island/requirements.txt @@ -11,4 +11,5 @@ Flask-Restful jsonschema netifaces ipaddress -enum34 \ No newline at end of file +enum34 +PyCrypto \ No newline at end of file From 02d89ce5ddf35d87a3f98b3a34aafb71105f4304 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 13 Feb 2018 16:24:13 +0200 Subject: [PATCH 07/26] Rewrite actual check for sockets to be cross platform and notify when we fail to open a socket for unknown reasons. --- chaos_monkey/network/tools.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index 716be15a0..eac020dc0 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -113,25 +113,30 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): 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) - read_sockets, write_sockets, errored_sockets = \ - select.select( - [s[1] for s in good_ports], - [s[1] for s in good_ports], - [s[1] for s in good_ports], - 0) # no timeout because we've already slept - connected_ports_sockets = [x for x in good_ports if x[1] in write_sockets] + # 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: - # read first X bytes - banners = [sock.recv(BANNER_READ) if sock in read_sockets else "" + 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 From 29e85100d2dce87c58f4c4b1df6092c92c989311 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 13 Feb 2018 16:29:24 +0200 Subject: [PATCH 08/26] Add global encryptor --- monkey_island/cc/encryptor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey_island/cc/encryptor.py b/monkey_island/cc/encryptor.py index 7af0840d0..90009d1b0 100644 --- a/monkey_island/cc/encryptor.py +++ b/monkey_island/cc/encryptor.py @@ -46,3 +46,6 @@ class Encryptor: cipher_iv = enc_message[0:AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) return self._unpad(cipher.decrypt(enc_message[AES.block_size:])) + + +encryptor = Encryptor() From 06a2e4f18dcf372f5339be3c8f87403bca16eecb Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 13 Feb 2018 16:34:37 +0200 Subject: [PATCH 09/26] encrypt credentials in config+telemetry --- .../cc/resources/monkey_configuration.py | 2 +- monkey_island/cc/resources/telemetry.py | 53 +++-- monkey_island/cc/services/config.py | 213 +++++++++++------- 3 files changed, 170 insertions(+), 98 deletions(-) diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 6d622b1cd..c3c9f51cb 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -18,6 +18,6 @@ class MonkeyConfiguration(flask_restful.Resource): if config_json.has_key('reset'): ConfigService.reset_config() else: - ConfigService.update_config(config_json) + ConfigService.update_config(config_json, should_encrypt=True) return self.get() diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 94c4046b5..6f69ecc2f 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -11,6 +11,7 @@ from cc.database import mongo from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService +from cc.encryptor import encryptor __author__ = 'Barak' @@ -121,6 +122,8 @@ class Telemetry(flask_restful.Resource): if new_exploit['result']: EdgeService.set_edge_exploited(edge) + Telemetry.encrypt_exploit_creds(telemetry_json) + for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} @@ -163,25 +166,49 @@ class Telemetry(flask_restful.Resource): def process_system_info_telemetry(telemetry_json): if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] - for user in creds: - ConfigService.creds_add_username(user) - if 'password' in creds[user]: - ConfigService.creds_add_password(creds[user]['password']) - if 'lm_hash' in creds[user]: - ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) - if 'ntlm_hash' in creds[user]: - ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - - for user in creds: - if -1 != user.find('.'): - new_user = user.replace('.', ',') - creds[new_user] = creds.pop(user) + Telemetry.encrypt_system_info_creds(creds) + Telemetry.add_system_info_creds_to_config(creds) + Telemetry.replace_user_dot_with_comma(creds) @staticmethod def process_trace_telemetry(telemetry_json): # Nothing to do return + @staticmethod + def replace_user_dot_with_comma(creds): + for user in creds: + if -1 != user.find('.'): + new_user = user.replace('.', ',') + creds[new_user] = creds.pop(user) + + @staticmethod + def encrypt_system_info_creds(creds): + for user in creds: + for field in ['password', 'lm_hash', 'ntlm_hash']: + if field in creds[user]: + creds[user][field] = encryptor.enc(creds[user][field]) + + @staticmethod + def add_system_info_creds_to_config(creds): + for user in creds: + ConfigService.creds_add_username(user) + if 'password' in creds[user]: + ConfigService.creds_add_password(creds[user]['password']) + if 'lm_hash' in creds[user]: + ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) + if 'ntlm_hash' in creds[user]: + ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + + @staticmethod + def encrypt_exploit_creds(telemetry_json): + attempts = telemetry_json['data']['attempts'] + for i in range(len(attempts)): + for field in ['password', 'lm_hash', 'ntlm_hash']: + credential = attempts[i][field] + if len(credential) > 0: + attempts[i][field] = encryptor.enc(credential) + TELEM_PROCESS_DICT = \ { diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ea755312f..878520217 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,9 @@ -from cc.database import mongo +import copy + from jsonschema import Draft4Validator, validators +from cc.database import mongo +from cc.encryptor import encryptor from cc.island_config import ISLAND_PORT from cc.utils import local_ip_addresses @@ -17,60 +20,60 @@ SCHEMA = { "type": "string", "anyOf": [ { - "type": "string", - "enum": [ - "SmbExploiter" - ], - "title": "SMB Exploiter" + "type": "string", + "enum": [ + "SmbExploiter" + ], + "title": "SMB Exploiter" }, { - "type": "string", - "enum": [ - "WmiExploiter" - ], - "title": "WMI Exploiter" + "type": "string", + "enum": [ + "WmiExploiter" + ], + "title": "WMI Exploiter" }, { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)" + "type": "string", + "enum": [ + "RdpExploiter" + ], + "title": "RDP Exploiter (UNSAFE)" }, { - "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], - "title": "MS08-067 Exploiter (UNSAFE)" + "type": "string", + "enum": [ + "Ms08_067_Exploiter" + ], + "title": "MS08-067 Exploiter (UNSAFE)" }, { - "type": "string", - "enum": [ - "SSHExploiter" - ], - "title": "SSH Exploiter" + "type": "string", + "enum": [ + "SSHExploiter" + ], + "title": "SSH Exploiter" }, { - "type": "string", - "enum": [ - "ShellShockExploiter" - ], - "title": "ShellShock Exploiter" + "type": "string", + "enum": [ + "ShellShockExploiter" + ], + "title": "ShellShock Exploiter" }, { - "type": "string", - "enum": [ - "SambaCryExploiter" - ], - "title": "SambaCry Exploiter" + "type": "string", + "enum": [ + "SambaCryExploiter" + ], + "title": "SambaCry Exploiter" }, { - "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], - "title": "ElasticGroovy Exploiter" + "type": "string", + "enum": [ + "ElasticGroovyExploiter" + ], + "title": "ElasticGroovy Exploiter" }, ] }, @@ -79,46 +82,46 @@ SCHEMA = { "type": "string", "anyOf": [ { - "type": "string", - "enum": [ - "SMBFinger" - ], - "title": "SMBFinger" + "type": "string", + "enum": [ + "SMBFinger" + ], + "title": "SMBFinger" }, { - "type": "string", - "enum": [ - "SSHFinger" - ], - "title": "SSHFinger" + "type": "string", + "enum": [ + "SSHFinger" + ], + "title": "SSHFinger" }, { - "type": "string", - "enum": [ - "PingScanner" - ], - "title": "PingScanner" + "type": "string", + "enum": [ + "PingScanner" + ], + "title": "PingScanner" }, { - "type": "string", - "enum": [ - "HTTPFinger" - ], - "title": "HTTPFinger" + "type": "string", + "enum": [ + "HTTPFinger" + ], + "title": "HTTPFinger" }, { - "type": "string", - "enum": [ - "MySQLFinger" - ], - "title": "MySQLFinger" + "type": "string", + "enum": [ + "MySQLFinger" + ], + "title": "MySQLFinger" }, { - "type": "string", - "enum": [ - "ElasticFinger" - ], - "title": "ElasticFinger" + "type": "string", + "enum": [ + "ElasticFinger" + ], + "title": "ElasticFinger" } ] } @@ -794,29 +797,42 @@ SCHEMA = { } } +ENCRYPTED_CONFIG_ARRAYS = \ + [ + ['basic', 'credentials', 'exploit_password_list'], + ['internal', 'exploits', 'exploit_lm_hash_list'], + ['internal', 'exploits', 'exploit_ntlm_hash_list'] + ] + class ConfigService: + default_config = None + def __init__(self): pass @staticmethod - def get_config(is_initial_config=False): + def get_config(is_initial_config=False, should_decrypt=True): config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} for field in ('name', '_id'): config.pop(field, None) + if should_decrypt and len(config) > 0: + ConfigService.decrypt_config(config) return config @staticmethod - def get_config_value(config_key_as_arr, is_initial_config=False): - config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr) + def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True): + config_key = reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: config = config[config_key_part] + if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS): + config = [encryptor.dec(x) for x in config] return config @staticmethod - def get_flat_config(is_initial_config=False): - config_json = ConfigService.get_config(is_initial_config) + def get_flat_config(is_initial_config=False, should_decrypt=True): + config_json = ConfigService.get_config(is_initial_config, should_decrypt) flat_config_json = {} for i in config_json: for j in config_json[i]: @@ -860,27 +876,38 @@ class ConfigService: ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) @staticmethod - def update_config(config_json): + def update_config(config_json, should_encrypt): + if should_encrypt: + ConfigService.encrypt_config(config_json) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) @staticmethod - def get_default_config(): - defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) - config = {} - defaultValidatingDraft4Validator(SCHEMA).validate(config) + def init_default_config(): + if ConfigService.default_config is None: + defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) + config = {} + defaultValidatingDraft4Validator(SCHEMA).validate(config) + ConfigService.default_config = config + + @staticmethod + def get_default_config(should_decrypt=True): + ConfigService.init_default_config() + config = copy.deepcopy(ConfigService.default_config) + if not should_decrypt: + ConfigService.encrypt_config(config) return config @staticmethod def init_config(): - if ConfigService.get_config() != {}: + if ConfigService.get_config(should_decrypt=False) != {}: return ConfigService.reset_config() @staticmethod def reset_config(): - config = ConfigService.get_default_config() + config = ConfigService.get_default_config(should_decrypt=False) ConfigService.set_server_ips_in_config(config) - ConfigService.update_config(config) + ConfigService.update_config(config, should_encrypt=False) @staticmethod def set_server_ips_in_config(config): @@ -922,3 +949,21 @@ class ConfigService: return validators.extend( validator_class, {"properties": set_defaults}, ) + + @staticmethod + def decrypt_config(config): + ConfigService._encrypt_config(config, True) + + @staticmethod + def encrypt_config(config): + ConfigService._encrypt_config(config, False) + + @staticmethod + def _encrypt_config(config, is_decrypt=False): + for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: + config_arr = config + for config_key_part in config_arr_as_array: + config_arr = config_arr[config_key_part] + + for i in range(len(config_arr)): + config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) From dbe7a6a378b87e25e61b17a01ae76aaff5026a4c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 14 Feb 2018 15:50:53 +0200 Subject: [PATCH 10/26] Add log sending logic to monkey Add log processing logic to monkey island backend --- chaos_monkey/control.py | 16 +++++++++ chaos_monkey/main.py | 9 +++--- chaos_monkey/monkey.py | 13 ++++++++ chaos_monkey/utils.py | 14 ++++++++ monkey_island/cc/app.py | 13 +++++--- monkey_island/cc/resources/log.py | 29 +++++++++++++++++ monkey_island/cc/resources/root.py | 3 +- monkey_island/cc/services/log.py | 52 ++++++++++++++++++++++++++++++ monkey_island/cc/services/node.py | 6 +++- 9 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 chaos_monkey/utils.py create mode 100644 monkey_island/cc/resources/log.py create mode 100644 monkey_island/cc/services/log.py diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index b4f2769cd..fef37de1f 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -1,3 +1,4 @@ +import base64 import json import logging import platform @@ -111,6 +112,21 @@ class ControlClient(object): LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) + @staticmethod + def send_log(log): + if not WormConfiguration.current_server: + return + try: + telemetry = {'monkey_guid': GUID, 'log': base64.b64encode(log)} + reply = requests.post("https://%s/api/log" % (WormConfiguration.current_server,), + data=json.dumps(telemetry), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + @staticmethod def load_control_config(): if not WormConfiguration.current_server: diff --git a/chaos_monkey/main.py b/chaos_monkey/main.py index c53232b2c..ef57492cc 100644 --- a/chaos_monkey/main.py +++ b/chaos_monkey/main.py @@ -12,6 +12,7 @@ from config import WormConfiguration, EXTERNAL_CONFIG_FILE from dropper import MonkeyDrops from model import MONKEY_ARG, DROPPER_ARG from monkey import ChaosMonkey +import utils if __name__ == "__main__": sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -78,12 +79,10 @@ def main(): try: if MONKEY_ARG == monkey_mode: - log_path = os.path.expandvars( - WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux + log_path = utils.get_monkey_log_path() monkey_cls = ChaosMonkey elif DROPPER_ARG == monkey_mode: - log_path = os.path.expandvars( - WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux + log_path = utils.get_dropper_log_path() monkey_cls = MonkeyDrops else: return True @@ -91,6 +90,8 @@ def main(): return True if WormConfiguration.use_file_logging: + if os.path.exists(log_path): + os.remove(log_path) LOG_CONFIG['handlers']['file']['filename'] = log_path LOG_CONFIG['root']['handlers'].append('file') else: diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 79012dc39..79e8bf3ec 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -6,6 +6,7 @@ import sys import time import tunnel +import utils from config import WormConfiguration from control import ControlClient from model import DELAY_DELETE_CMD @@ -226,6 +227,8 @@ class ChaosMonkey(object): firewall.close() + self.send_log() + self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'): @@ -244,3 +247,13 @@ class ChaosMonkey(object): LOG.error("Exception in self delete: %s", exc) LOG.info("Monkey is shutting down") + + def send_log(self): + monkey_log_path = utils.get_monkey_log_path() + if os.path.exists(monkey_log_path): + with open(monkey_log_path, 'r') as f: + log = f.read() + else: + log = '' + + ControlClient.send_log(log) diff --git a/chaos_monkey/utils.py b/chaos_monkey/utils.py new file mode 100644 index 000000000..d95407341 --- /dev/null +++ b/chaos_monkey/utils.py @@ -0,0 +1,14 @@ +import os +import sys + +from config import WormConfiguration + + +def get_monkey_log_path(): + return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.monkey_log_path_linux + + +def get_dropper_log_path(): + return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.dropper_log_path_linux diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 9c85f6230..2d8041eb0 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -1,22 +1,24 @@ from datetime import datetime + import bson -from bson.json_util import dumps -from flask import Flask, send_from_directory, redirect, make_response import flask_restful +from bson.json_util import dumps +from flask import Flask, send_from_directory, make_response from werkzeug.exceptions import NotFound from cc.database import mongo from cc.resources.client_run import ClientRun -from cc.resources.monkey import Monkey +from cc.resources.edge import Edge from cc.resources.local_run import LocalRun -from cc.resources.telemetry import Telemetry +from cc.resources.log import Log +from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap -from cc.resources.edge import Edge from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root +from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.services.config import ConfigService @@ -91,5 +93,6 @@ def init_app(mongo_url): api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') + api.add_resource(Log, '/api/log', '/api/log/') return app diff --git a/monkey_island/cc/resources/log.py b/monkey_island/cc/resources/log.py new file mode 100644 index 000000000..5a308b5dd --- /dev/null +++ b/monkey_island/cc/resources/log.py @@ -0,0 +1,29 @@ +import json + +import flask_restful +from bson import ObjectId +from flask import request + +from cc.database import mongo +from cc.services.log import LogService +from cc.services.node import NodeService + +__author__ = "itay.mizeretz" + + +class Log(flask_restful.Resource): + def get(self): + monkey_id = request.args.get('id') + exists_monkey_id = request.args.get('exists') + if monkey_id: + return LogService.get_log_by_monkey_id(ObjectId(monkey_id)) + else: + return LogService.log_exists(ObjectId(exists_monkey_id)) + + def post(self): + telemetry_json = json.loads(request.data) + + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id'] + log_id = LogService.add_log(monkey_id, telemetry_json['log']) + + return mongo.db.log.find_one_or_404({"_id": log_id}) diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 25d7dfed7..f725163aa 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -33,7 +33,8 @@ class Root(flask_restful.Resource): @staticmethod def reset_db(): - [mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']] + [mongo.db[x].drop() for x in + ['config', 'monkey', 'telemetry', 'node', 'edge', 'report', 'log', 'fs.chunks', 'fs.files']] ConfigService.init_config() return jsonify(status='OK') diff --git a/monkey_island/cc/services/log.py b/monkey_island/cc/services/log.py new file mode 100644 index 000000000..2783a1dfa --- /dev/null +++ b/monkey_island/cc/services/log.py @@ -0,0 +1,52 @@ +from datetime import datetime + +import gridfs + +import cc.services.node +from cc.database import mongo + +__author__ = "itay.mizeretz" + + +class LogService: + def __init__(self): + pass + + @staticmethod + def get_log_by_monkey_id(monkey_id): + log = mongo.db.log.find_one({'monkey_id': monkey_id}) + if log: + fs = gridfs.GridFS(mongo.db) + log_file = fs.get(log['file_id']) + monkey_label = cc.services.node.NodeService.get_monkey_label( + cc.services.node.NodeService.get_monkey_by_id(log['monkey_id'])) + return \ + { + 'monkey_label': monkey_label, + 'log': log_file.read(), + 'timestamp': log['timestamp'] + } + + @staticmethod + def remove_logs_by_monkey_id(monkey_id): + fs = gridfs.GridFS(mongo.db) + for log in mongo.db.log.find({'monkey_id': monkey_id}): + fs.delete(log['file_id']) + mongo.db.log.delete_many({'monkey_id': monkey_id}) + + @staticmethod + def add_log(monkey_id, log_data, timestamp=datetime.now()): + LogService.remove_logs_by_monkey_id(monkey_id) + fs = gridfs.GridFS(mongo.db) + file_id = fs.put(log_data) + return mongo.db.log.insert( + { + 'monkey_id': monkey_id, + 'file_id': file_id, + 'timestamp': timestamp + } + ) + + @staticmethod + def log_exists(monkey_id): + return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None diff --git a/monkey_island/cc/services/node.py b/monkey_island/cc/services/node.py index 47cfba8d9..47cd9cd21 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey_island/cc/services/node.py @@ -1,9 +1,12 @@ from datetime import datetime, timedelta + from bson import ObjectId +import cc.services.log from cc.database import mongo from cc.services.edge import EdgeService from cc.utils import local_ip_addresses + __author__ = "itay.mizeretz" @@ -54,6 +57,7 @@ class NodeService: else: new_node["services"] = [] + new_node['has_log'] = cc.services.log.LogService.log_exists(ObjectId(node_id)) return new_node @staticmethod @@ -241,7 +245,7 @@ class NodeService: @staticmethod def get_monkey_island_pseudo_net_node(): - return\ + return \ { "id": NodeService.get_monkey_island_pseudo_id(), "label": "MonkeyIsland", From 86a0e47d15afde8be46ccdadb5eaebe751b7556d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 14 Feb 2018 15:51:22 +0200 Subject: [PATCH 11/26] Add log downloading from map --- monkey_island/cc/ui/package.json | 1 + .../map/preview-pane/PreviewPane.js | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 5ee2e5389..27b536365 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -63,6 +63,7 @@ "dependencies": { "bootstrap": "^3.3.7", "core-js": "^2.5.1", + "downloadjs": "^1.4.7", "fetch": "^1.1.0", "js-file-download": "^0.4.1", "normalize.css": "^4.0.0", diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 842440149..56c6d0e75 100644 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -2,6 +2,7 @@ import React from 'react'; import {Icon} from 'react-fa'; import Toggle from 'react-toggle'; import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' class PreviewPaneComponent extends React.Component { @@ -88,6 +89,34 @@ class PreviewPaneComponent extends React.Component { ); } + downloadLog(asset) { + + fetch('/api/log?id=' + asset.id) + .then(res => res.json()) + .then(res => { + let timestamp = res['timestamp']; + timestamp = timestamp.substr(0, timestamp.indexOf('.')); + let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; + download(atob(res['log']), filename, 'text/plain'); + }); + + } + + downloadLogRow(asset) { + return ( + + + Download Log + + + this.downloadLog(asset)}>Download + + + ); + } + exploitsTimeline(asset) { if (asset.exploits.length === 0) { return (
); @@ -140,6 +169,7 @@ class PreviewPaneComponent extends React.Component { {this.servicesRow(asset)} {this.accessibleRow(asset)} {this.forceKillRow(asset)} + {this.downloadLogRow(asset)} {this.exploitsTimeline(asset)} From 70766e7358556bec80524b25c010cbc62734e5ff Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 14 Feb 2018 16:58:58 +0200 Subject: [PATCH 12/26] Save some space --- monkey_island/cc/resources/log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/log.py b/monkey_island/cc/resources/log.py index 5a308b5dd..e49c7024d 100644 --- a/monkey_island/cc/resources/log.py +++ b/monkey_island/cc/resources/log.py @@ -24,6 +24,8 @@ class Log(flask_restful.Resource): telemetry_json = json.loads(request.data) monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id'] - log_id = LogService.add_log(monkey_id, telemetry_json['log']) + # This is base64 so no data will be lost. this'll take 2 time less space. + log_data = str(telemetry_json['log']) + log_id = LogService.add_log(monkey_id, log_data) return mongo.db.log.find_one_or_404({"_id": log_id}) From 33345e8460ad3164d3f1a79b405013afea16db51 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 14 Feb 2018 19:03:29 +0200 Subject: [PATCH 13/26] Rename chaos_monkey to infection_monkey --- README.md | 4 ++-- chaos_monkey/main.py | 4 ++-- chaos_monkey/monkey.py | 2 +- chaos_monkey/readme.txt | 16 ++++++++-------- chaos_monkey/test/config__test.py | 2 +- monkey_island/readme.txt | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dfe501470..f1fc47676 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The Infection Monkey is an open source security tool for testing a data center's The Infection Monkey is comprised of two parts: * 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 +* Monkey Island - A C&C server with a dedicated UI to visualize the Infection Monkey's progress inside the data center To read more about the Monkey, visit http://infectionmonkey.com @@ -43,7 +43,7 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t Building the Monkey from source ------------------------------- 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). +and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island). License diff --git a/chaos_monkey/main.py b/chaos_monkey/main.py index c53232b2c..ea8ee769a 100644 --- a/chaos_monkey/main.py +++ b/chaos_monkey/main.py @@ -11,7 +11,7 @@ import traceback from config import WormConfiguration, EXTERNAL_CONFIG_FILE from dropper import MonkeyDrops from model import MONKEY_ARG, DROPPER_ARG -from monkey import ChaosMonkey +from monkey import InfectionMonkey if __name__ == "__main__": sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -80,7 +80,7 @@ def main(): if MONKEY_ARG == monkey_mode: log_path = os.path.expandvars( WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux - monkey_cls = ChaosMonkey + monkey_cls = InfectionMonkey elif DROPPER_ARG == monkey_mode: log_path = os.path.expandvars( WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 79012dc39..22be2cf46 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -19,7 +19,7 @@ __author__ = 'itamar' LOG = logging.getLogger(__name__) -class ChaosMonkey(object): +class InfectionMonkey(object): def __init__(self, args): self._keep_running = False self._exploited_machines = set() diff --git a/chaos_monkey/readme.txt b/chaos_monkey/readme.txt index bdf267f60..67c4033d9 100644 --- a/chaos_monkey/readme.txt +++ b/chaos_monkey/readme.txt @@ -28,13 +28,13 @@ The monkey is composed of three separate parts. 64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523 6. Download the dependent python packages using pip install -r requirements.txt -7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe: +7. Download and extract UPX binary to [source-path]\monkey\infection_monkey\bin\upx.exe: https://github.com/upx/upx/releases/download/v3.94/upx394w.zip 8. Build/Download Sambacry and Mimikatz binaries a. Build/Download according to sections at the end of this readme. - b. Place the binaries under [code location]\chaos_monkey\bin + b. Place the binaries under [code location]\infection_monkey\bin 9. To build the final exe: - cd [code location]/chaos_monkey + cd [code location]/infection_monkey build_windows.bat output is placed under dist\monkey.exe @@ -46,13 +46,13 @@ Tested on Ubuntu 16.04 and 17.04. sudo apt-get update sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 Install the python packages listed in requirements.txt using pip - cd [code location]/chaos_monkey + cd [code location]/infection_monkey pip install -r requirements.txt 2. Build Sambacry binaries a. Build/Download according to sections at the end of this readme. - b. Place the binaries under [code location]\chaos_monkey\bin + b. Place the binaries under [code location]\infection_monkey\bin 3. To build, run in terminal: - cd [code location]/chaos_monkey + cd [code location]/infection_monkey chmod +x build_linux.sh ./build_linux.sh output is placed under dist/monkey @@ -63,11 +63,11 @@ Sambacry requires two standalone binaries to execute remotely. 1. Install gcc-multilib if it's not installed sudo apt-get install gcc-multilib 2. Build the binaries - cd [code location]/chaos_monkey/monkey_utils/sambacry_monkey_runner + cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner ./build.sh -- Mimikatz -- Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from https://github.com/guardicore/mimikatz/releases/tag/1.0.0 -Download both 32 and 64 bit DLLs and place them under [code location]\chaos_monkey\bin \ No newline at end of file +Download both 32 and 64 bit DLLs and place them under [code location]\infection_monkey\bin \ No newline at end of file diff --git a/chaos_monkey/test/config__test.py b/chaos_monkey/test/config__test.py index fccde2f0d..accdd5a49 100644 --- a/chaos_monkey/test/config__test.py +++ b/chaos_monkey/test/config__test.py @@ -1,5 +1,5 @@ # -*- coding: UTF-8 -*- -# NOTE: Launch all tests with `nosetests` command from chaos_monkey dir. +# NOTE: Launch all tests with `nosetests` command from infection_monkey dir. import json import unittest diff --git a/monkey_island/readme.txt b/monkey_island/readme.txt index 2feaaa1ab..42d29b2f8 100644 --- a/monkey_island/readme.txt +++ b/monkey_island/readme.txt @@ -18,7 +18,7 @@ How to set C&C server: 5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572 6. Generate SSL Certificate 6.1. run create_certificate.bat when your current working directory is monkey_island -7. Create the monkey_island\cc\binaries folder and put chaos monkey binaries inside +7. Create the monkey_island\cc\binaries folder and put Infection Monkey binaries inside monkey-linux-64 - monkey binary for linux 64bit monkey-linux-32 - monkey binary for linux 32bit monkey-windows-32.exe - monkey binary for windows 32bit From 3ea46099750934209a26f029e7a18a57ba3922a6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 15 Feb 2018 15:36:19 +0200 Subject: [PATCH 14/26] Remove C&C from all textual and code mentions --- README.md | 12 ++++++------ chaos_monkey/control.py | 2 +- monkey_island/cc/main.py | 2 +- monkey_island/cc/services/config.py | 2 +- monkey_island/cc/ui/src/components/Main.js | 2 +- .../cc/ui/src/components/pages/ReportPage.js | 2 +- .../cc/ui/src/components/pages/RunMonkeyPage.js | 2 +- .../cc/ui/src/components/pages/RunServerPage.js | 4 ++-- monkey_island/cc/ui/src/index.html | 2 +- monkey_island/deb-package/DEBIAN/control | 2 +- monkey_island/readme.txt | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f59de5094..b48030204 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ 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 Command and Control(C&C) 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 Command and Control server. The Infection Monkey is comprised of two parts: * Chaos 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 +* Monkey Island - A dedicated UI to visualize the Chaos Monkey's progress inside the data center To read more about the Monkey, visit http://infectionmonkey.com @@ -22,7 +22,7 @@ The Infection Monkey uses the following techniques and exploits to propagate to * Multiple propagation techniques: * Predefined passwords * Common logical exploits - * Password stealing using mimikatz + * Password stealing using Mimikatz * Multiple exploit methods: * SSH * SMB @@ -39,15 +39,15 @@ Getting Started ### Requirements -The C&C Server has been tested on Ubuntu 14.04,15.04 and 16.04. -The Monkey itself has been tested on Windows XP, 7, 8.1 and 10. The Linux build has been tested on Ubuntu server (multiple versions). +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 C&C 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. +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 diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index b4f2769cd..e7fb4cebb 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -25,7 +25,7 @@ class ControlClient(object): @staticmethod def wakeup(parent=None, default_tunnel=None, has_internet_access=None): - LOG.debug("Trying to wake up with C&C servers list: %r" % WormConfiguration.command_servers) + LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) if parent or default_tunnel: LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel)) hostname = gethostname() diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index bb1ed9eaf..e2f97cde5 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -33,6 +33,6 @@ if __name__ == '__main__': ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) http_server.listen(ISLAND_PORT) - print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT)) + print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT)) IOLoop.instance().start() diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ea755312f..cc3e65e3a 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -528,7 +528,7 @@ SCHEMA = { } }, "cnc": { - "title": "C&C", + "title": "Command and Control", "type": "object", "properties": { "servers": { diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey_island/cc/ui/src/components/Main.js index 881c3a2ec..ffd318527 100644 --- a/monkey_island/cc/ui/src/components/Main.js +++ b/monkey_island/cc/ui/src/components/Main.js @@ -77,7 +77,7 @@ class AppComponent extends React.Component {
  • 1. - Run C&C Server + Run Monkey Island Server { this.state.completedSteps.run_server ? : ''} diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 440000596..92c3b2db6 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -610,7 +610,7 @@ class ReportPageComponent extends React.Component { The network can probably be segmented. A monkey instance on {issue.machine} in the networks {this.generateInfoBadges(issue.networks)} - could directly access the Monkey Island C&C server in the + could directly access the Monkey Island server in the networks {this.generateInfoBadges(issue.server_networks)}.
  • diff --git a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 5574a73ba..8d692eddd 100644 --- a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -146,7 +146,7 @@ class RunMonkeyPageComponent extends React.Component { className="btn btn-default btn-lg center-block" disabled={this.state.runningOnIslandState !== 'not_running'} > - Run on C&C Server + Run on Monkey Island Server { this.renderIconByState(this.state.runningOnIslandState) } { diff --git a/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey_island/cc/ui/src/components/pages/RunServerPage.js index 5143f24fc..fe8e8f611 100644 --- a/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -10,14 +10,14 @@ class RunServerPageComponent extends React.Component { render() { return ( -

    1. Monkey Island C&C Server

    +

    1. Monkey Island Server

    Congrats! You have successfully set up the Monkey Island server. 👏 👏

    The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infections. The Monkey uses various methods to propagate across a data - center and reports to this Command and Control (C&C) server. + center and reports to this Monkey Island Command and Control server.

    To read more about the Monkey, visit infectionmonkey.com diff --git a/monkey_island/cc/ui/src/index.html b/monkey_island/cc/ui/src/index.html index e75b183ca..86a05dd28 100644 --- a/monkey_island/cc/ui/src/index.html +++ b/monkey_island/cc/ui/src/index.html @@ -2,7 +2,7 @@ - Infection Monkey C&C + Infection Monkey Island Server diff --git a/monkey_island/deb-package/DEBIAN/control b/monkey_island/deb-package/DEBIAN/control index ecbd75cc6..2426feecb 100644 --- a/monkey_island/deb-package/DEBIAN/control +++ b/monkey_island/deb-package/DEBIAN/control @@ -4,5 +4,5 @@ Maintainer: Guardicore Homepage: http://www.guardicore.com Priority: optional Version: 1.0 -Description: Guardicore Infection Monkey Island (C&C) installation package +Description: Guardicore Infection Monkey Island installation package Depends: openssl, python-pip diff --git a/monkey_island/readme.txt b/monkey_island/readme.txt index 2feaaa1ab..d426de548 100644 --- a/monkey_island/readme.txt +++ b/monkey_island/readme.txt @@ -1,4 +1,4 @@ -How to set C&C server: +How to set up the Monkey Island server: ---------------- On Windows ----------------: 1. Create folder "bin" under monkey_island From de4e4b9ea170c1b06987650a679f4c52f18ee9de Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 19 Feb 2018 11:35:34 +0200 Subject: [PATCH 15/26] rename chaos_monkey folder to infection_monkey --- {chaos_monkey => infection_monkey}/build_linux.sh | 0 .../build_windows.bat | 0 {chaos_monkey => infection_monkey}/config.py | 0 {chaos_monkey => infection_monkey}/control.py | 0 {chaos_monkey => infection_monkey}/dropper.py | 0 {chaos_monkey => infection_monkey}/example.conf | 0 .../exploit/__init__.py | 0 .../exploit/elasticgroovy.py | 0 .../exploit/rdpgrinder.py | 0 .../exploit/sambacry.py | 0 .../exploit/shellshock.py | 0 .../exploit/shellshock_resources.py | 0 .../exploit/smbexec.py | 0 .../exploit/sshexec.py | 0 {chaos_monkey => infection_monkey}/exploit/tools.py | 0 .../exploit/win_ms08_067.py | 0 .../exploit/wmiexec.py | 0 {chaos_monkey => infection_monkey}/main.py | 0 .../model/__init__.py | 0 {chaos_monkey => infection_monkey}/model/host.py | 0 .../monkey-linux.spec | 0 {chaos_monkey => infection_monkey}/monkey.ico | Bin {chaos_monkey => infection_monkey}/monkey.py | 0 {chaos_monkey => infection_monkey}/monkey.spec | 0 .../monkey_utils/sambacry_monkey_runner/build.sh | 0 .../sambacry_monkey_runner/sc_monkey_runner.c | 0 .../sambacry_monkey_runner/sc_monkey_runner.h | 0 {chaos_monkey => infection_monkey}/monkeyfs.py | 0 .../network/__init__.py | 0 .../network/elasticfinger.py | 0 .../network/firewall.py | 0 .../network/httpfinger.py | 0 {chaos_monkey => infection_monkey}/network/info.py | 0 .../network/mysqlfinger.py | 0 .../network/network_scanner.py | 0 .../network/ping_scanner.py | 0 {chaos_monkey => infection_monkey}/network/range.py | 0 .../network/smbfinger.py | 0 .../network/sshfinger.py | 0 .../network/tcp_scanner.py | 0 {chaos_monkey => infection_monkey}/network/tools.py | 0 {chaos_monkey => infection_monkey}/readme.txt | 0 {chaos_monkey => infection_monkey}/requirements.txt | 0 .../system_info/__init__.py | 0 .../system_info/linux_info_collector.py | 0 .../system_info/mimikatz_collector.py | 0 .../system_info/windows_info_collector.py | 0 .../system_singleton.py | 0 {chaos_monkey => infection_monkey}/test/__init__.py | 0 .../test/config__test.py | 0 .../transport/__init__.py | 0 .../transport/base.py | 0 {chaos_monkey => infection_monkey}/transport/ftp.py | 0 .../transport/http.py | 0 {chaos_monkey => infection_monkey}/transport/tcp.py | 0 {chaos_monkey => infection_monkey}/tunnel.py | 0 56 files changed, 0 insertions(+), 0 deletions(-) rename {chaos_monkey => infection_monkey}/build_linux.sh (100%) rename {chaos_monkey => infection_monkey}/build_windows.bat (100%) rename {chaos_monkey => infection_monkey}/config.py (100%) rename {chaos_monkey => infection_monkey}/control.py (100%) rename {chaos_monkey => infection_monkey}/dropper.py (100%) rename {chaos_monkey => infection_monkey}/example.conf (100%) rename {chaos_monkey => infection_monkey}/exploit/__init__.py (100%) rename {chaos_monkey => infection_monkey}/exploit/elasticgroovy.py (100%) rename {chaos_monkey => infection_monkey}/exploit/rdpgrinder.py (100%) rename {chaos_monkey => infection_monkey}/exploit/sambacry.py (100%) rename {chaos_monkey => infection_monkey}/exploit/shellshock.py (100%) rename {chaos_monkey => infection_monkey}/exploit/shellshock_resources.py (100%) rename {chaos_monkey => infection_monkey}/exploit/smbexec.py (100%) rename {chaos_monkey => infection_monkey}/exploit/sshexec.py (100%) rename {chaos_monkey => infection_monkey}/exploit/tools.py (100%) rename {chaos_monkey => infection_monkey}/exploit/win_ms08_067.py (100%) rename {chaos_monkey => infection_monkey}/exploit/wmiexec.py (100%) rename {chaos_monkey => infection_monkey}/main.py (100%) rename {chaos_monkey => infection_monkey}/model/__init__.py (100%) rename {chaos_monkey => infection_monkey}/model/host.py (100%) rename {chaos_monkey => infection_monkey}/monkey-linux.spec (100%) rename {chaos_monkey => infection_monkey}/monkey.ico (100%) rename {chaos_monkey => infection_monkey}/monkey.py (100%) rename {chaos_monkey => infection_monkey}/monkey.spec (100%) rename {chaos_monkey => infection_monkey}/monkey_utils/sambacry_monkey_runner/build.sh (100%) rename {chaos_monkey => infection_monkey}/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c (100%) rename {chaos_monkey => infection_monkey}/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h (100%) rename {chaos_monkey => infection_monkey}/monkeyfs.py (100%) rename {chaos_monkey => infection_monkey}/network/__init__.py (100%) rename {chaos_monkey => infection_monkey}/network/elasticfinger.py (100%) rename {chaos_monkey => infection_monkey}/network/firewall.py (100%) rename {chaos_monkey => infection_monkey}/network/httpfinger.py (100%) rename {chaos_monkey => infection_monkey}/network/info.py (100%) rename {chaos_monkey => infection_monkey}/network/mysqlfinger.py (100%) rename {chaos_monkey => infection_monkey}/network/network_scanner.py (100%) rename {chaos_monkey => infection_monkey}/network/ping_scanner.py (100%) rename {chaos_monkey => infection_monkey}/network/range.py (100%) rename {chaos_monkey => infection_monkey}/network/smbfinger.py (100%) rename {chaos_monkey => infection_monkey}/network/sshfinger.py (100%) rename {chaos_monkey => infection_monkey}/network/tcp_scanner.py (100%) rename {chaos_monkey => infection_monkey}/network/tools.py (100%) rename {chaos_monkey => infection_monkey}/readme.txt (100%) rename {chaos_monkey => infection_monkey}/requirements.txt (100%) rename {chaos_monkey => infection_monkey}/system_info/__init__.py (100%) rename {chaos_monkey => infection_monkey}/system_info/linux_info_collector.py (100%) rename {chaos_monkey => infection_monkey}/system_info/mimikatz_collector.py (100%) rename {chaos_monkey => infection_monkey}/system_info/windows_info_collector.py (100%) rename {chaos_monkey => infection_monkey}/system_singleton.py (100%) rename {chaos_monkey => infection_monkey}/test/__init__.py (100%) rename {chaos_monkey => infection_monkey}/test/config__test.py (100%) rename {chaos_monkey => infection_monkey}/transport/__init__.py (100%) rename {chaos_monkey => infection_monkey}/transport/base.py (100%) rename {chaos_monkey => infection_monkey}/transport/ftp.py (100%) rename {chaos_monkey => infection_monkey}/transport/http.py (100%) rename {chaos_monkey => infection_monkey}/transport/tcp.py (100%) rename {chaos_monkey => infection_monkey}/tunnel.py (100%) diff --git a/chaos_monkey/build_linux.sh b/infection_monkey/build_linux.sh similarity index 100% rename from chaos_monkey/build_linux.sh rename to infection_monkey/build_linux.sh diff --git a/chaos_monkey/build_windows.bat b/infection_monkey/build_windows.bat similarity index 100% rename from chaos_monkey/build_windows.bat rename to infection_monkey/build_windows.bat diff --git a/chaos_monkey/config.py b/infection_monkey/config.py similarity index 100% rename from chaos_monkey/config.py rename to infection_monkey/config.py diff --git a/chaos_monkey/control.py b/infection_monkey/control.py similarity index 100% rename from chaos_monkey/control.py rename to infection_monkey/control.py diff --git a/chaos_monkey/dropper.py b/infection_monkey/dropper.py similarity index 100% rename from chaos_monkey/dropper.py rename to infection_monkey/dropper.py diff --git a/chaos_monkey/example.conf b/infection_monkey/example.conf similarity index 100% rename from chaos_monkey/example.conf rename to infection_monkey/example.conf diff --git a/chaos_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py similarity index 100% rename from chaos_monkey/exploit/__init__.py rename to infection_monkey/exploit/__init__.py diff --git a/chaos_monkey/exploit/elasticgroovy.py b/infection_monkey/exploit/elasticgroovy.py similarity index 100% rename from chaos_monkey/exploit/elasticgroovy.py rename to infection_monkey/exploit/elasticgroovy.py diff --git a/chaos_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py similarity index 100% rename from chaos_monkey/exploit/rdpgrinder.py rename to infection_monkey/exploit/rdpgrinder.py diff --git a/chaos_monkey/exploit/sambacry.py b/infection_monkey/exploit/sambacry.py similarity index 100% rename from chaos_monkey/exploit/sambacry.py rename to infection_monkey/exploit/sambacry.py diff --git a/chaos_monkey/exploit/shellshock.py b/infection_monkey/exploit/shellshock.py similarity index 100% rename from chaos_monkey/exploit/shellshock.py rename to infection_monkey/exploit/shellshock.py diff --git a/chaos_monkey/exploit/shellshock_resources.py b/infection_monkey/exploit/shellshock_resources.py similarity index 100% rename from chaos_monkey/exploit/shellshock_resources.py rename to infection_monkey/exploit/shellshock_resources.py diff --git a/chaos_monkey/exploit/smbexec.py b/infection_monkey/exploit/smbexec.py similarity index 100% rename from chaos_monkey/exploit/smbexec.py rename to infection_monkey/exploit/smbexec.py diff --git a/chaos_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py similarity index 100% rename from chaos_monkey/exploit/sshexec.py rename to infection_monkey/exploit/sshexec.py diff --git a/chaos_monkey/exploit/tools.py b/infection_monkey/exploit/tools.py similarity index 100% rename from chaos_monkey/exploit/tools.py rename to infection_monkey/exploit/tools.py diff --git a/chaos_monkey/exploit/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py similarity index 100% rename from chaos_monkey/exploit/win_ms08_067.py rename to infection_monkey/exploit/win_ms08_067.py diff --git a/chaos_monkey/exploit/wmiexec.py b/infection_monkey/exploit/wmiexec.py similarity index 100% rename from chaos_monkey/exploit/wmiexec.py rename to infection_monkey/exploit/wmiexec.py diff --git a/chaos_monkey/main.py b/infection_monkey/main.py similarity index 100% rename from chaos_monkey/main.py rename to infection_monkey/main.py diff --git a/chaos_monkey/model/__init__.py b/infection_monkey/model/__init__.py similarity index 100% rename from chaos_monkey/model/__init__.py rename to infection_monkey/model/__init__.py diff --git a/chaos_monkey/model/host.py b/infection_monkey/model/host.py similarity index 100% rename from chaos_monkey/model/host.py rename to infection_monkey/model/host.py diff --git a/chaos_monkey/monkey-linux.spec b/infection_monkey/monkey-linux.spec similarity index 100% rename from chaos_monkey/monkey-linux.spec rename to infection_monkey/monkey-linux.spec diff --git a/chaos_monkey/monkey.ico b/infection_monkey/monkey.ico similarity index 100% rename from chaos_monkey/monkey.ico rename to infection_monkey/monkey.ico diff --git a/chaos_monkey/monkey.py b/infection_monkey/monkey.py similarity index 100% rename from chaos_monkey/monkey.py rename to infection_monkey/monkey.py diff --git a/chaos_monkey/monkey.spec b/infection_monkey/monkey.spec similarity index 100% rename from chaos_monkey/monkey.spec rename to infection_monkey/monkey.spec diff --git a/chaos_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh similarity index 100% rename from chaos_monkey/monkey_utils/sambacry_monkey_runner/build.sh rename to infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh diff --git a/chaos_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c similarity index 100% rename from chaos_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c rename to infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c diff --git a/chaos_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h similarity index 100% rename from chaos_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h rename to infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h diff --git a/chaos_monkey/monkeyfs.py b/infection_monkey/monkeyfs.py similarity index 100% rename from chaos_monkey/monkeyfs.py rename to infection_monkey/monkeyfs.py diff --git a/chaos_monkey/network/__init__.py b/infection_monkey/network/__init__.py similarity index 100% rename from chaos_monkey/network/__init__.py rename to infection_monkey/network/__init__.py diff --git a/chaos_monkey/network/elasticfinger.py b/infection_monkey/network/elasticfinger.py similarity index 100% rename from chaos_monkey/network/elasticfinger.py rename to infection_monkey/network/elasticfinger.py diff --git a/chaos_monkey/network/firewall.py b/infection_monkey/network/firewall.py similarity index 100% rename from chaos_monkey/network/firewall.py rename to infection_monkey/network/firewall.py diff --git a/chaos_monkey/network/httpfinger.py b/infection_monkey/network/httpfinger.py similarity index 100% rename from chaos_monkey/network/httpfinger.py rename to infection_monkey/network/httpfinger.py diff --git a/chaos_monkey/network/info.py b/infection_monkey/network/info.py similarity index 100% rename from chaos_monkey/network/info.py rename to infection_monkey/network/info.py diff --git a/chaos_monkey/network/mysqlfinger.py b/infection_monkey/network/mysqlfinger.py similarity index 100% rename from chaos_monkey/network/mysqlfinger.py rename to infection_monkey/network/mysqlfinger.py diff --git a/chaos_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py similarity index 100% rename from chaos_monkey/network/network_scanner.py rename to infection_monkey/network/network_scanner.py diff --git a/chaos_monkey/network/ping_scanner.py b/infection_monkey/network/ping_scanner.py similarity index 100% rename from chaos_monkey/network/ping_scanner.py rename to infection_monkey/network/ping_scanner.py diff --git a/chaos_monkey/network/range.py b/infection_monkey/network/range.py similarity index 100% rename from chaos_monkey/network/range.py rename to infection_monkey/network/range.py diff --git a/chaos_monkey/network/smbfinger.py b/infection_monkey/network/smbfinger.py similarity index 100% rename from chaos_monkey/network/smbfinger.py rename to infection_monkey/network/smbfinger.py diff --git a/chaos_monkey/network/sshfinger.py b/infection_monkey/network/sshfinger.py similarity index 100% rename from chaos_monkey/network/sshfinger.py rename to infection_monkey/network/sshfinger.py diff --git a/chaos_monkey/network/tcp_scanner.py b/infection_monkey/network/tcp_scanner.py similarity index 100% rename from chaos_monkey/network/tcp_scanner.py rename to infection_monkey/network/tcp_scanner.py diff --git a/chaos_monkey/network/tools.py b/infection_monkey/network/tools.py similarity index 100% rename from chaos_monkey/network/tools.py rename to infection_monkey/network/tools.py diff --git a/chaos_monkey/readme.txt b/infection_monkey/readme.txt similarity index 100% rename from chaos_monkey/readme.txt rename to infection_monkey/readme.txt diff --git a/chaos_monkey/requirements.txt b/infection_monkey/requirements.txt similarity index 100% rename from chaos_monkey/requirements.txt rename to infection_monkey/requirements.txt diff --git a/chaos_monkey/system_info/__init__.py b/infection_monkey/system_info/__init__.py similarity index 100% rename from chaos_monkey/system_info/__init__.py rename to infection_monkey/system_info/__init__.py diff --git a/chaos_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py similarity index 100% rename from chaos_monkey/system_info/linux_info_collector.py rename to infection_monkey/system_info/linux_info_collector.py diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/infection_monkey/system_info/mimikatz_collector.py similarity index 100% rename from chaos_monkey/system_info/mimikatz_collector.py rename to infection_monkey/system_info/mimikatz_collector.py diff --git a/chaos_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py similarity index 100% rename from chaos_monkey/system_info/windows_info_collector.py rename to infection_monkey/system_info/windows_info_collector.py diff --git a/chaos_monkey/system_singleton.py b/infection_monkey/system_singleton.py similarity index 100% rename from chaos_monkey/system_singleton.py rename to infection_monkey/system_singleton.py diff --git a/chaos_monkey/test/__init__.py b/infection_monkey/test/__init__.py similarity index 100% rename from chaos_monkey/test/__init__.py rename to infection_monkey/test/__init__.py diff --git a/chaos_monkey/test/config__test.py b/infection_monkey/test/config__test.py similarity index 100% rename from chaos_monkey/test/config__test.py rename to infection_monkey/test/config__test.py diff --git a/chaos_monkey/transport/__init__.py b/infection_monkey/transport/__init__.py similarity index 100% rename from chaos_monkey/transport/__init__.py rename to infection_monkey/transport/__init__.py diff --git a/chaos_monkey/transport/base.py b/infection_monkey/transport/base.py similarity index 100% rename from chaos_monkey/transport/base.py rename to infection_monkey/transport/base.py diff --git a/chaos_monkey/transport/ftp.py b/infection_monkey/transport/ftp.py similarity index 100% rename from chaos_monkey/transport/ftp.py rename to infection_monkey/transport/ftp.py diff --git a/chaos_monkey/transport/http.py b/infection_monkey/transport/http.py similarity index 100% rename from chaos_monkey/transport/http.py rename to infection_monkey/transport/http.py diff --git a/chaos_monkey/transport/tcp.py b/infection_monkey/transport/tcp.py similarity index 100% rename from chaos_monkey/transport/tcp.py rename to infection_monkey/transport/tcp.py diff --git a/chaos_monkey/tunnel.py b/infection_monkey/tunnel.py similarity index 100% rename from chaos_monkey/tunnel.py rename to infection_monkey/tunnel.py From aa02d8945dc32121e12f7fb530ca79d5e74ca107 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 19 Feb 2018 17:22:48 +0200 Subject: [PATCH 16/26] Replace base64 with string escaping --- chaos_monkey/control.py | 3 +- .../map/preview-pane/PreviewPane.js | 60 +++++++++++-------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index fef37de1f..dd1814133 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -1,4 +1,3 @@ -import base64 import json import logging import platform @@ -117,7 +116,7 @@ class ControlClient(object): if not WormConfiguration.current_server: return try: - telemetry = {'monkey_guid': GUID, 'log': base64.b64encode(log)} + telemetry = {'monkey_guid': GUID, 'log': json.dumps(log)} reply = requests.post("https://%s/api/log" % (WormConfiguration.current_server,), data=json.dumps(telemetry), headers={'content-type': 'application/json'}, diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 56c6d0e75..b7f055103 100644 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -82,22 +82,34 @@ class PreviewPaneComponent extends React.Component { this.forceKill(e, asset)} /> + onChange={(e) => this.forceKill(e, asset)}/> ); } - downloadLog(asset) { + unescapeLog(st) { + return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string. + .replace(/\\n/g, "\n") + .replace(/\\r/g, "\r") + .replace(/\\t/g, "\t") + .replace(/\\b/g, "\b") + .replace(/\\f/g, "\f") + .replace(/\\"/g, '\"') + .replace(/\\'/g, "\'") + .replace(/\\&/g, "\&"); + } + downloadLog(asset) { fetch('/api/log?id=' + asset.id) .then(res => res.json()) .then(res => { let timestamp = res['timestamp']; timestamp = timestamp.substr(0, timestamp.indexOf('.')); let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; - download(atob(res['log']), filename, 'text/plain'); + let logContent = this.unescapeLog(res['log']); + download(logContent, filename, 'text/plain'); }); } @@ -119,7 +131,7 @@ class PreviewPaneComponent extends React.Component { exploitsTimeline(asset) { if (asset.exploits.length === 0) { - return (

    ); + return (
    ); } return ( @@ -129,9 +141,9 @@ class PreviewPaneComponent extends React.Component { {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}