From 0bc666824288e4cdfcaa859f54df479268e3fb75 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 11 Nov 2017 20:32:12 +0200 Subject: [PATCH 1/6] 0001-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 b9809f1e1f4b484c5d75d345c404eec1baa2e031 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 11 Nov 2017 20:36:56 +0200 Subject: [PATCH 2/6] Move tcp scanner to use new check_tcp_pors --- chaos_monkey/network/tcp_scanner.py | 28 +++---- chaos_monkey/network/tools.py | 118 +++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 18 deletions(-) diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index 4a6b8c40e..e149da8e7 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -1,9 +1,7 @@ -import time 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 +16,25 @@ class TcpScanner(HostScanner, HostFinger): return self.get_host_fingerprint(host, True) def get_host_fingerprint(self, host, only_one_port=False): - assert isinstance(host, VictimHost) + """ + Scans a target host to see if it's alive using the tcp_target_ports specified in the configuration. + :param host: VictimHost structure + :param only_one_port: Currently unused. + :return: T/F if there is at least one open port. In addition, the host object is updated to mark those services as alive. + """ - count = 0 # maybe hide under really bad detection systems target_ports = self._config.tcp_target_ports[:] shuffle(target_ports) - for target_port in target_ports: - - 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 + ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0) + if len(ports) != 0: + for target_port, banner in zip(ports, banners): 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..6aea850b5 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,103 @@ 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: + for sock, port in zip(sockets, ports): + LOG.debug("Connecting to port %d" % port) + 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) 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 [], [] + + +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: + for sock, port in zip(sockets, ports): + LOG.debug("Connecting to port %d" % port) + 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) 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 2b17eca6149449a0e8357bdf89d9bc9589818185 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 11 Nov 2017 20:36:56 +0200 Subject: [PATCH 3/6] Move tcp scanner to use new check_tcp_pors --- chaos_monkey/network/tcp_scanner.py | 28 +++++------- chaos_monkey/network/tools.py | 68 ++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index 4a6b8c40e..e149da8e7 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -1,9 +1,7 @@ -import time 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 +16,25 @@ class TcpScanner(HostScanner, HostFinger): return self.get_host_fingerprint(host, True) def get_host_fingerprint(self, host, only_one_port=False): - assert isinstance(host, VictimHost) + """ + Scans a target host to see if it's alive using the tcp_target_ports specified in the configuration. + :param host: VictimHost structure + :param only_one_port: Currently unused. + :return: T/F if there is at least one open port. In addition, the host object is updated to mark those services as alive. + """ - count = 0 # maybe hide under really bad detection systems target_ports = self._config.tcp_target_ports[:] shuffle(target_ports) - for target_port in target_ports: - - 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 + ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0) + if len(ports) != 0: + for target_port, banner in zip(ports, banners): 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..ed0c1d4ca 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: + for sock, port in zip(sockets, ports): + LOG.debug("Connecting to port %d" % port) + 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, _ = \ + 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) 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 7a523bdd75b942b0449028c767681b045029f343 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 12 Nov 2017 18:06:13 +0200 Subject: [PATCH 4/6] Oppertunistic wait --- chaos_monkey/network/tools.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index ed0c1d4ca..706dcc48d 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -117,13 +117,21 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): good_ports.append((port, sock)) if len(good_ports) != 0: - time.sleep(timeout) read_sockets, write_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 + timeout) # wait max timeout + if len(write_sockets) != len(good_ports): + time.sleep(timeout) + read_sockets, write_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" % From fddda34dcd7800eaf2b18d317408d5d49c2fecc0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 12 Nov 2017 19:04:54 +0200 Subject: [PATCH 5/6] Worst case is now timeout rather than every case --- chaos_monkey/network/tools.py | 48 ++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index 706dcc48d..4ce8fed2e 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -5,6 +5,7 @@ import struct import time DEFAULT_TIMEOUT = 10 +SLEEP_BETWEEN_POLL = 0.5 BANNER_READ = 1024 LOG = logging.getLogger(__name__) @@ -100,39 +101,44 @@ 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 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 = [] + port_attempts = [] try: for sock, port in zip(sockets, ports): LOG.debug("Connecting to port %d" % port) err = sock.connect_ex((ip, port)) if err == 0: - good_ports.append((port, sock)) + port_attempts.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: - read_sockets, write_sockets, _ = \ - select.select( - [s[1] for s in good_ports], - [s[1] for s in good_ports], - [s[1] for s in good_ports], - timeout) # wait max timeout - if len(write_sockets) != len(good_ports): - time.sleep(timeout) - read_sockets, write_sockets, _ = \ + port_attempts.append((port, sock)) + if len(port_attempts) != 0: + num_replies = 0 + timeout = int(round(timeout)) # clamp to integer, to avoid checking input + time_left = timeout + write_sockets = [] + read_sockets = [] + while num_replies != len(port_attempts): + # bad ports show up as err_sockets + read_sockets, write_sockets, err_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 + [s[1] for s in port_attempts], + [s[1] for s in port_attempts], + [s[1] for s in port_attempts], + time_left) + # any read_socket is automatically a writesocket + num_replies = len(write_sockets) + len(err_sockets) + if num_replies == len(port_attempts) or time_left == 0: + break + else: + time_left -= SLEEP_BETWEEN_POLL + time.sleep(SLEEP_BETWEEN_POLL) - connected_ports_sockets = [x for x in good_ports if x[1] in write_sockets] + connected_ports_sockets = [x for x in port_attempts if x[1] in write_sockets] LOG.debug( "On host %s discovered the following ports %s" % (str(ip), ",".join([str(x) for x in connected_ports_sockets]))) @@ -143,7 +149,7 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): for port, sock in connected_ports_sockets] pass # try to cleanup - [s[1].close() for s in good_ports] + [s[1].close() for s in port_attempts] return [port for port, sock in connected_ports_sockets], banners else: return [], [] From 30d81e05c98933bc688856d7c1e6a802a37eb52e Mon Sep 17 00:00:00 2001 From: Ace Pace Date: Tue, 9 Jan 2018 18:37:59 +0200 Subject: [PATCH 6/6] CR fixes --- chaos_monkey/network/tcp_scanner.py | 15 +++++++-------- chaos_monkey/network/tools.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/chaos_monkey/network/tcp_scanner.py b/chaos_monkey/network/tcp_scanner.py index e149da8e7..7b37c3278 100644 --- a/chaos_monkey/network/tcp_scanner.py +++ b/chaos_monkey/network/tcp_scanner.py @@ -28,13 +28,12 @@ class TcpScanner(HostScanner, HostFinger): shuffle(target_ports) ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0) - if len(ports) != 0: - for target_port, banner in zip(ports, banners): - service = 'tcp-' + str(target_port) - host.services[service] = {} - if banner: - host.services[service]['banner'] = banner - if only_one_port: - break + for target_port, banner in zip(ports, banners): + service = 'tcp-' + str(target_port) + host.services[service] = {} + if banner: + host.services[service]['banner'] = banner + if only_one_port: + break return len(ports) != 0 diff --git a/chaos_monkey/network/tools.py b/chaos_monkey/network/tools.py index 4ce8fed2e..b26fe5d20 100644 --- a/chaos_monkey/network/tools.py +++ b/chaos_monkey/network/tools.py @@ -132,7 +132,7 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): time_left) # any read_socket is automatically a writesocket num_replies = len(write_sockets) + len(err_sockets) - if num_replies == len(port_attempts) or time_left == 0: + if num_replies == len(port_attempts) or time_left <= 0: break else: time_left -= SLEEP_BETWEEN_POLL