diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 97b66690e..f7e4cfae4 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -157,9 +157,8 @@ class Configuration(object):
keep_tunnel_open_time = 60
- # Monkey files directories
- monkey_dir_linux = '/tmp/monkey_dir'
- monkey_dir_windows = r'C:\Windows\Temp\monkey_dir'
+ # Monkey files directory name
+ monkey_dir_name = 'monkey_dir'
###########################
# scanners config
diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf
index 8c8668bef..8dba50352 100644
--- a/monkey/infection_monkey/example.conf
+++ b/monkey/infection_monkey/example.conf
@@ -29,9 +29,7 @@
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey",
- monkey_dir_linux = '/tmp/monkey_dir',
- monkey_dir_windows = r'C:\Windows\Temp\monkey_dir',
-
+ "monkey_dir_name": "monkey_dir",
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not",
diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py
index f2decb590..7353d77bc 100644
--- a/monkey/infection_monkey/exploit/__init__.py
+++ b/monkey/infection_monkey/exploit/__init__.py
@@ -1,6 +1,7 @@
from abc import ABCMeta, abstractmethod, abstractproperty
import infection_monkey.config
from common.utils.exploit_enum import ExploitType
+from datetime import datetime
__author__ = 'itamar'
@@ -20,11 +21,19 @@ class HostExploiter(object):
def __init__(self, host):
self._config = infection_monkey.config.WormConfiguration
self._exploit_info = {'display_name': self._EXPLOITED_SERVICE,
+ 'started': '',
+ 'finished': '',
'vulnerable_urls': [],
'vulnerable_ports': []}
self._exploit_attempts = []
self.host = host
+ def set_start_time(self):
+ self._exploit_info['started'] = datetime.now().isoformat()
+
+ def set_finish_time(self):
+ self._exploit_info['finished'] = datetime.now().isoformat()
+
def is_os_supported(self):
return self.host.os.get('type') in self._TARGET_OS_TYPE
diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py
index 3f8af40fc..ff9cefa60 100644
--- a/monkey/infection_monkey/exploit/mssqlexec.py
+++ b/monkey/infection_monkey/exploit/mssqlexec.py
@@ -1,15 +1,16 @@
-import os
import logging
-from time import sleep
-import pymssql
+import os
import textwrap
+from time import sleep
+
+import pymssql
-from infection_monkey.exploit import HostExploiter, tools
from common.utils.exploit_enum import ExploitType
+from infection_monkey.exploit import HostExploiter, tools
from infection_monkey.exploit.tools import HTTPTools
-from infection_monkey.config import WormConfiguration
-from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.tools import get_monkey_dest_path
+from infection_monkey.model import DROPPER_ARG
+from infection_monkey.utils import get_monkey_dir_path
LOG = logging.getLogger(__name__)
@@ -52,10 +53,10 @@ class MSSQLExploiter(HostExploiter):
LOG.info("Started http server on %s", http_path)
dst_path = get_monkey_dest_path(http_path)
- tmp_file_path = os.path.join(WormConfiguration.monkey_dir_windows, MSSQLExploiter.TMP_FILE_NAME)
+ tmp_file_path = os.path.join(get_monkey_dir_path(), MSSQLExploiter.TMP_FILE_NAME)
# Create monkey dir.
- commands = ["xp_cmdshell \"mkdir %s\"" % WormConfiguration.monkey_dir_windows]
+ commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()]
MSSQLExploiter.execute_command(cursor, commands)
# Form download command in a file
@@ -124,10 +125,12 @@ class MSSQLExploiter(HostExploiter):
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
LOG.info('Successfully connected to host: {0}, '
'using user: {1}, password: {2}'.format(host, user, password))
+ self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password)
cursor = conn.cursor()
return cursor
except pymssql.OperationalError:
+ self.report_login_attempt(False, user, password)
# Combo didn't work, hopping to the next one
pass
diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py
index 41b3820d5..d576af3cd 100644
--- a/monkey/infection_monkey/exploit/win_ms08_067.py
+++ b/monkey/infection_monkey/exploit/win_ms08_067.py
@@ -153,6 +153,7 @@ class SRVSVC_Exploit(object):
class Ms08_067_Exploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
+ _EXPLOITED_SERVICE = 'Microsoft Server Service'
_windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2}
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 06e0f267b..92714c7d6 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -285,7 +285,9 @@ class InfectionMonkey(object):
result = False
try:
+ exploiter.set_start_time()
result = exploiter.exploit_host()
+ exploiter.set_finish_time()
if result:
self.successfully_exploited(machine, exploiter)
return True
diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py
index 650f37b2f..ff7ae3a50 100644
--- a/monkey/infection_monkey/post_breach/actions/add_user.py
+++ b/monkey/infection_monkey/post_breach/actions/add_user.py
@@ -16,4 +16,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
class BackdoorUser(PBA):
def __init__(self):
- super(BackdoorUser, self).__init__("Backdoor user", linux_cmd=LINUX_COMMANDS, windows_cmd=WINDOWS_COMMANDS)
+ super(BackdoorUser, self).__init__("Backdoor user",
+ linux_cmd=' '.join(LINUX_COMMANDS),
+ windows_cmd=WINDOWS_COMMANDS)
diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py
index 0e08203c2..6eb3aefb5 100644
--- a/monkey/infection_monkey/utils.py
+++ b/monkey/infection_monkey/utils.py
@@ -1,8 +1,9 @@
import os
-import sys
import shutil
import struct
-import datetime
+import sys
+import tempfile
+
from infection_monkey.config import WormConfiguration
@@ -17,10 +18,9 @@ def get_dropper_log_path():
def is_64bit_windows_os():
- '''
+ """
Checks for 64 bit Windows OS using environment variables.
- :return:
- '''
+ """
return 'PROGRAMFILES(X86)' in os.environ
@@ -54,7 +54,4 @@ def remove_monkey_dir():
def get_monkey_dir_path():
- if is_windows_os():
- return WormConfiguration.monkey_dir_windows
- else:
- return WormConfiguration.monkey_dir_linux
+ return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name)
diff --git a/monkey/monkey_island/cc/database.py b/monkey/monkey_island/cc/database.py
index 8fb3b120b..082553e5f 100644
--- a/monkey/monkey_island/cc/database.py
+++ b/monkey/monkey_island/cc/database.py
@@ -25,3 +25,14 @@ def is_db_server_up(mongo_url):
return True
except ServerSelectionTimeoutError:
return False
+
+
+def get_db_version(mongo_url):
+ """
+ Return the mongo db version
+ :param mongo_url: Which mongo to check.
+ :return: version as a tuple (e.g. `(u'4', u'0', u'8')`)
+ """
+ client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
+ server_version = tuple(client.server_info()['version'].split('.'))
+ return server_version
diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py
index 412c3c399..5b9bda8cb 100644
--- a/monkey/monkey_island/cc/main.py
+++ b/monkey/monkey_island/cc/main.py
@@ -6,6 +6,8 @@ import sys
import time
import logging
+MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
+
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BASE_PATH not in sys.path:
@@ -22,7 +24,7 @@ from monkey_island.cc.app import init_app
from monkey_island.cc.exporter_init import populate_exporter_list
from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.environment.environment import env
-from monkey_island.cc.database import is_db_server_up
+from monkey_island.cc.database import is_db_server_up, get_db_version
def main():
@@ -31,10 +33,8 @@ def main():
from tornado.ioloop import IOLoop
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
-
- while not is_db_server_up(mongo_url):
- logger.info('Waiting for MongoDB server')
- time.sleep(1)
+ wait_for_mongo_db_server(mongo_url)
+ assert_mongo_db_version(mongo_url)
populate_exporter_list()
app = init_app(mongo_url)
@@ -55,5 +55,27 @@ def main():
IOLoop.instance().start()
+def wait_for_mongo_db_server(mongo_url):
+ while not is_db_server_up(mongo_url):
+ logger.info('Waiting for MongoDB server on {0}'.format(mongo_url))
+ time.sleep(1)
+
+
+def assert_mongo_db_version(mongo_url):
+ """
+ Checks if the mongodb version is new enough for running the app.
+ If the DB is too old, quits.
+ :param mongo_url: URL to the mongo the Island will use
+ """
+ required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split("."))
+ server_version = get_db_version(mongo_url)
+ if server_version < required_version:
+ logger.error(
+ 'Mongo DB version too old. {0} is required, but got {1}'.format(str(required_version), str(server_version)))
+ sys.exit(-1)
+ else:
+ logger.info('Mongo DB version OK. Got {0}'.format(str(server_version)))
+
+
if __name__ == '__main__':
main()
diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py
index bb018caa9..3e1e3d7c5 100644
--- a/monkey/monkey_island/cc/models/monkey.py
+++ b/monkey/monkey_island/cc/models/monkey.py
@@ -55,6 +55,14 @@ class Monkey(Document):
monkey_is_dead = True
return monkey_is_dead
+ def get_os(self):
+ os = "unknown"
+ if self.description.lower().find("linux") != -1:
+ os = "linux"
+ elif self.description.lower().find("windows") != -1:
+ os = "windows"
+ return os
+
class MonkeyNotFoundError(Exception):
pass
diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py
index 008fb0ce6..a744db6b6 100644
--- a/monkey/monkey_island/cc/models/test_monkey.py
+++ b/monkey/monkey_island/cc/models/test_monkey.py
@@ -1,13 +1,13 @@
import uuid
from time import sleep
-from unittest import TestCase
from monkey import Monkey
from monkey_island.cc.models.monkey import MonkeyNotFoundError
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
from monkey_ttl import MonkeyTtl
-class TestMonkey(TestCase):
+class TestMonkey(IslandTestCase):
"""
Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and
won't work.
@@ -15,7 +15,11 @@ class TestMonkey(TestCase):
Also, the working directory needs to be the working directory from which you usually run the island so the
server.json file is found and loaded.
"""
+
def test_is_dead(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
# Arrange
alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30)
alive_monkey_ttl.save()
@@ -43,6 +47,9 @@ class TestMonkey(TestCase):
self.assertFalse(alive_monkey.is_dead())
def test_get_single_monkey_by_id(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
# Arrange
a_monkey = Monkey(guid=str(uuid.uuid4()))
a_monkey.save()
@@ -52,3 +59,21 @@ class TestMonkey(TestCase):
self.assertIsNotNone(Monkey.get_single_monkey_by_id(a_monkey.id))
# Raise on non-existent monkey
self.assertRaises(MonkeyNotFoundError, Monkey.get_single_monkey_by_id, "abcdefabcdefabcdefabcdef")
+
+ def test_get_os(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ linux_monkey = Monkey(guid=str(uuid.uuid4()),
+ description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu SMP Mon May 6 18:46:08 UTC 2019 x86_64 x86_64")
+ windows_monkey = Monkey(guid=str(uuid.uuid4()),
+ description="Windows bla bla bla")
+ unknown_monkey = Monkey(guid=str(uuid.uuid4()),
+ description="bla bla bla")
+ linux_monkey.save()
+ windows_monkey.save()
+ unknown_monkey.save()
+
+ self.assertEquals(1, len(filter(lambda m: m.get_os() == "windows", Monkey.objects())))
+ self.assertEquals(1, len(filter(lambda m: m.get_os() == "linux", Monkey.objects())))
+ self.assertEquals(1, len(filter(lambda m: m.get_os() == "unknown", Monkey.objects())))
diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py
index 87ca49906..69b6bcdd4 100644
--- a/monkey/monkey_island/cc/resources/telemetry.py
+++ b/monkey/monkey_island/cc/resources/telemetry.py
@@ -119,6 +119,8 @@ class Telemetry(flask_restful.Resource):
def process_exploit_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
Telemetry.encrypt_exploit_creds(telemetry_json)
+ telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started'])
+ telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished'])
new_exploit = copy.deepcopy(telemetry_json['data'])
diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py
index 58ba37008..314a2e4df 100644
--- a/monkey/monkey_island/cc/services/attack/attack_report.py
+++ b/monkey/monkey_island/cc/services/attack/attack_report.py
@@ -1,5 +1,5 @@
import logging
-from monkey_island.cc.services.attack.technique_reports import T1210, T1197
+from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.database import mongo
@@ -9,7 +9,8 @@ __author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
TECHNIQUES = {'T1210': T1210.T1210,
- 'T1197': T1197.T1197}
+ 'T1197': T1197.T1197,
+ 'T1110': T1110.T1110}
REPORT_NAME = 'new_report'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
new file mode 100644
index 000000000..7fe5ac90f
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
@@ -0,0 +1,93 @@
+from monkey_island.cc.database import mongo
+from monkey_island.cc.services.attack.technique_reports import AttackTechnique
+from common.utils.attack_utils import ScanStatus
+from monkey_island.cc.encryptor import encryptor
+
+__author__ = "VakarisZ"
+
+
+class T1110(AttackTechnique):
+ tech_id = "T1110"
+ unscanned_msg = "Monkey didn't try to brute force any services."
+ scanned_msg = "Monkey tried to brute force some services, but failed."
+ used_msg = "Monkey successfully used brute force in the network."
+
+ # Gets data about brute force attempts
+ query = [{'$match': {'telem_type': 'exploit',
+ 'data.attempts': {'$not': {'$size': 0}}}},
+ {'$project': {'_id': 0,
+ 'machine': '$data.machine',
+ 'info': '$data.info',
+ 'attempt_cnt': {'$size': '$data.attempts'},
+ 'attempts': {'$filter': {'input': '$data.attempts',
+ 'as': 'attempt',
+ 'cond': {'$eq': ['$$attempt.result', True]}}}}}]
+
+ @staticmethod
+ def get_report_data():
+ attempts = list(mongo.db.telemetry.aggregate(T1110.query))
+ succeeded = False
+
+ for result in attempts:
+ result['successful_creds'] = []
+ for attempt in result['attempts']:
+ succeeded = True
+ result['successful_creds'].append(T1110.parse_creds(attempt))
+
+ if succeeded:
+ data = T1110.get_message_and_status(T1110, ScanStatus.USED)
+ elif attempts:
+ data = T1110.get_message_and_status(T1110, ScanStatus.SCANNED)
+ else:
+ data = T1110.get_message_and_status(T1110, ScanStatus.UNSCANNED)
+
+ # Remove data with no successful brute force attempts
+ attempts = [attempt for attempt in attempts if attempt['attempts']]
+
+ data.update({'services': attempts, 'title': T1110.technique_title(T1110.tech_id)})
+ return data
+
+ @staticmethod
+ def parse_creds(attempt):
+ """
+ Parses used credentials into a string
+ :param attempt: login attempt from database
+ :return: string with username and used password/hash
+ """
+ username = attempt['user']
+ creds = {'lm_hash': {'type': 'LM hash', 'output': T1110.censor_hash(attempt['lm_hash'])},
+ 'ntlm_hash': {'type': 'NTLM hash', 'output': T1110.censor_hash(attempt['ntlm_hash'], 20)},
+ 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
+ 'password': {'type': 'Plaintext password', 'output': T1110.censor_password(attempt['password'])}}
+ for key, cred in creds.items():
+ if attempt[key]:
+ return '%s ; %s : %s' % (username,
+ cred['type'],
+ cred['output'])
+
+ @staticmethod
+ def censor_password(password, plain_chars=3, secret_chars=5):
+ """
+ Decrypts and obfuscates password by changing characters to *
+ :param password: Password or string to obfuscate
+ :param plain_chars: How many plain-text characters should be kept at the start of the string
+ :param secret_chars: How many * symbols should be used to hide the remainder of the password
+ :return: Obfuscated string e.g. Pass****
+ """
+ if not password:
+ return ""
+ password = encryptor.dec(password)
+ return password[0:plain_chars] + '*' * secret_chars
+
+ @staticmethod
+ def censor_hash(hash_, plain_chars=5):
+ """
+ Decrypts and obfuscates hash by only showing a part of it
+ :param hash_: Hash to obfuscate
+ :param plain_chars: How many chars of hash should be shown
+ :return: Obfuscated string
+ """
+ if not hash_:
+ return ""
+ hash_ = encryptor.dec(hash_)
+ return hash_[0: plain_chars] + ' ...'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
index 624656e1b..677495c10 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py
@@ -4,13 +4,6 @@ from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
-TECHNIQUE = "T1210"
-MESSAGES = {
- 'unscanned': "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?",
- 'scanned': "Monkey scanned for remote services on the network, but couldn't exploit any of them.",
- 'used': "Monkey scanned for remote services and exploited some on the network."
-}
-
class T1210(AttackTechnique):
@@ -21,7 +14,7 @@ class T1210(AttackTechnique):
@staticmethod
def get_report_data():
- data = {'title': T1210.technique_title(TECHNIQUE)}
+ data = {'title': T1210.technique_title(T1210.tech_id)}
scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services()
if exploited_services:
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
index 5e9a98a55..1990b61e7 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
@@ -60,6 +60,19 @@ class AttackTechnique(object):
else:
return ScanStatus.UNSCANNED
+ @staticmethod
+ def get_message_and_status(technique, status):
+ return {'message': technique.get_message_by_status(technique, status), 'status': status.name}
+
+ @staticmethod
+ def get_message_by_status(technique, status):
+ if status == ScanStatus.UNSCANNED:
+ return technique.unscanned_msg
+ elif status == ScanStatus.SCANNED:
+ return technique.scanned_msg
+ else:
+ return technique.used_msg
+
@staticmethod
def technique_title(technique):
"""
@@ -78,11 +91,7 @@ class AttackTechnique(object):
data = {}
status = AttackTechnique.technique_status(technique.tech_id)
title = AttackTechnique.technique_title(technique.tech_id)
- data.update({'status': status.name, 'title': title})
- if status == ScanStatus.UNSCANNED:
- data.update({'message': technique.unscanned_msg})
- elif status == ScanStatus.SCANNED:
- data.update({'message': technique.scanned_msg})
- else:
- data.update({'message': technique.used_msg})
+ data.update({'status': status.name,
+ 'title': title,
+ 'message': technique.get_message_by_status(technique, status)})
return data
diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py
index 8a96a0d78..46129266c 100644
--- a/monkey/monkey_island/cc/services/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema.py
@@ -499,17 +499,11 @@ SCHEMA = {
"default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
},
- "monkey_dir_windows": {
- "title": "Monkey's windows directory",
+ "monkey_dir_name": {
+ "title": "Monkey's directory name",
"type": "string",
- "default": r"C:\Windows\temp\monkey_dir",
- "description": "Directory containing all monkey files on windows"
- },
- "monkey_dir_linux": {
- "title": "Monkey's linux directory",
- "type": "string",
- "default": "/tmp/monkey_dir",
- "description": "Directory containing all monkey files on linux"
+ "default": r"monkey_dir",
+ "description": "Directory name for the directory which will contain all of the monkey files"
},
}
},
diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py
index 93fd51989..9f3b9769f 100644
--- a/monkey/monkey_island/cc/services/pth_report.py
+++ b/monkey/monkey_island/cc/services/pth_report.py
@@ -1,6 +1,7 @@
from itertools import product
from monkey_island.cc.database import mongo
+from monkey_island.cc.models import Monkey
from bson import ObjectId
from monkey_island.cc.services.groups_and_users_consts import USERTYPE
@@ -216,15 +217,15 @@ class PTHReportService(object):
@staticmethod
def generate_map_nodes():
- monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1})
+ monkeys = filter(lambda m: m.get_os() == "windows", Monkey.objects())
return [
{
- 'id': monkey['_id'],
- 'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
- 'group': 'critical' if monkey.get('critical_services', []) else 'normal',
- 'services': monkey.get('critical_services', []),
- 'hostname': monkey['hostname']
+ 'id': monkey.guid,
+ 'label': '{0} : {1}'.format(monkey.hostname, monkey.ip_addresses[0]),
+ 'group': 'critical' if monkey.critical_services is not None else 'normal',
+ 'services': monkey.critical_services,
+ 'hostname': monkey.hostname
} for monkey in monkeys
]
diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py
index 32d8e25f7..2cd0e82fa 100644
--- a/monkey/monkey_island/cc/services/report.py
+++ b/monkey/monkey_island/cc/services/report.py
@@ -57,8 +57,8 @@ class ReportService:
STRUTS2 = 8
WEBLOGIC = 9
HADOOP = 10
- PTH_CRIT_SERVICES_ACCESS = 11,
- MSSQL = 12,
+ PTH_CRIT_SERVICES_ACCESS = 11
+ MSSQL = 12
VSFTPD = 13
class WARNINGS_DICT(Enum):
@@ -296,7 +296,7 @@ class ReportService:
def process_vsftpd_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'vsftp'
- return processed_exploit
+ return processed_exploit
@staticmethod
def process_sambacry_exploit(exploit):
diff --git a/monkey/monkey_island/cc/services/test_PTHReportService.py b/monkey/monkey_island/cc/services/test_PTHReportService.py
new file mode 100644
index 000000000..24e560ff0
--- /dev/null
+++ b/monkey/monkey_island/cc/services/test_PTHReportService.py
@@ -0,0 +1,69 @@
+import uuid
+
+from monkey_island.cc.models import Monkey
+from monkey_island.cc.services.pth_report import PTHReportService
+from monkey_island.cc.testing.IslandTestCase import IslandTestCase
+
+
+class TestPTHReportServiceGenerateMapNodes(IslandTestCase):
+ def test_generate_map_nodes(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ self.assertEqual(PTHReportService.generate_map_nodes(), [])
+
+ windows_monkey_with_services = Monkey(
+ guid=str(uuid.uuid4()),
+ hostname="A_Windows_PC_1",
+ critical_services=["aCriticalService", "Domain Controller"],
+ ip_addresses=["1.1.1.1", "2.2.2.2"],
+ description="windows 10"
+ )
+ windows_monkey_with_services.save()
+
+ windows_monkey_with_no_services = Monkey(
+ guid=str(uuid.uuid4()),
+ hostname="A_Windows_PC_2",
+ critical_services=[],
+ ip_addresses=["3.3.3.3"],
+ description="windows 10"
+ )
+ windows_monkey_with_no_services.save()
+
+ linux_monkey = Monkey(
+ guid=str(uuid.uuid4()),
+ hostname="A_Linux_PC",
+ ip_addresses=["4.4.4.4"],
+ description="linux ubuntu"
+ )
+ linux_monkey.save()
+
+ map_nodes = PTHReportService.generate_map_nodes()
+
+ self.assertEquals(2, len(map_nodes))
+
+ def test_generate_map_nodes_parsing(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ monkey_id = str(uuid.uuid4())
+ hostname = "A_Windows_PC_1"
+ windows_monkey_with_services = Monkey(
+ guid=monkey_id,
+ hostname=hostname,
+ critical_services=["aCriticalService", "Domain Controller"],
+ ip_addresses=["1.1.1.1"],
+ description="windows 10"
+ )
+ windows_monkey_with_services.save()
+
+ map_nodes = PTHReportService.generate_map_nodes()
+
+ self.assertEquals(map_nodes[0]["id"], monkey_id)
+ self.assertEquals(map_nodes[0]["label"], "A_Windows_PC_1 : 1.1.1.1")
+ self.assertEquals(map_nodes[0]["group"], "critical")
+ self.assertEquals(len(map_nodes[0]["services"]), 2)
+ self.assertEquals(map_nodes[0]["hostname"], hostname)
+
+
+
diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py
new file mode 100644
index 000000000..e894f13df
--- /dev/null
+++ b/monkey/monkey_island/cc/testing/IslandTestCase.py
@@ -0,0 +1,12 @@
+import unittest
+from monkey_island.cc.environment.environment import env
+from monkey_island.cc.models import Monkey
+
+
+class IslandTestCase(unittest.TestCase):
+ def fail_if_not_testing_env(self):
+ self.failIf(not env.testing, "Change server_config.json to testing environment.")
+
+ @staticmethod
+ def clean_monkey_db():
+ Monkey.objects().delete()
diff --git a/monkey/monkey_island/cc/testing/__init__.py b/monkey/monkey_island/cc/testing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json
index 6f0b58560..ca13c259d 100644
--- a/monkey/monkey_island/cc/ui/package.json
+++ b/monkey/monkey_island/cc/ui/package.json
@@ -6,6 +6,7 @@
"clean": "rimraf dist/*",
"copy": "copyfiles -f ./src/index.html ./src/favicon.ico ./dist",
"dist": "webpack --mode production",
+ "dev": "webpack --mode development",
"lint": "eslint ./src",
"posttest": "npm run lint",
"release:major": "npm version major && npm publish && git push --follow-tags",
@@ -100,6 +101,7 @@
"sass-loader": "^7.1.0",
"sha3": "^2.0.0",
"react-spinners": "^0.5.4",
- "@emotion/core": "^10.0.10"
+ "@emotion/core": "^10.0.10",
+ "react-desktop-notification": "^1.0.9"
}
}
diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js
index 8229133e6..619d5e922 100644
--- a/monkey/monkey_island/cc/ui/src/components/Main.js
+++ b/monkey/monkey_island/cc/ui/src/components/Main.js
@@ -14,6 +14,8 @@ import ReportPage from 'components/pages/ReportPage';
import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage';
+import Notifier from "react-desktop-notification"
+
import 'normalize.css/normalize.css';
import 'react-data-components/css/table-twbs.css';
@@ -25,6 +27,7 @@ import VersionComponent from "./side-menu/VersionComponent";
let logoImage = require('../images/monkey-icon.svg');
let infectionMonkeyImage = require('../images/infection-monkey.svg');
let guardicoreLogoImage = require('../images/guardicore-logo.png');
+let notificationIcon = require('../images/notification-logo-512x512.png');
class AppComponent extends AuthComponent {
updateStatus = () => {
@@ -50,6 +53,7 @@ class AppComponent extends AuthComponent {
}
if (isChanged) {
this.setState({completedSteps: res['completed_steps']});
+ this.showInfectionDoneNotification();
}
});
}
@@ -194,6 +198,20 @@ class AppComponent extends AuthComponent {
);
}
+
+ showInfectionDoneNotification() {
+ if (this.state.completedSteps.infection_done) {
+ let hostname = window.location.hostname;
+ let url = `https://${hostname}:5000/report`;
+ console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon);
+
+ Notifier.start(
+ "Monkey Island",
+ "Infection is done! Click here to go to the report page.",
+ url,
+ notificationIcon);
+ }
+ }
}
AppComponent.defaultProps = {};
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js
new file mode 100644
index 000000000..64619ad55
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import '../../../styles/Collapse.scss'
+import ReactTable from "react-table";
+
+
+class T1110 extends React.Component {
+
+ constructor(props) {
+ super(props);
+ }
+
+ static getServiceColumns() {
+ return ([{
+ columns: [
+ {Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine),
+ style: { 'whiteSpace': 'unset' }, width: 160},
+ {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100},
+ {Header: 'Started', id: 'started', accessor: x => x.info.started, style: { 'whiteSpace': 'unset' }},
+ {Header: 'Finished', id: 'finished', accessor: x => x.info.finished, style: { 'whiteSpace': 'unset' }},
+ {Header: 'Attempts', id: 'attempts', accessor: x => x.attempt_cnt, style: { 'whiteSpace': 'unset' }, width: 160},
+ {Header: 'Successful credentials', id: 'credentials', accessor: x => this.renderCreds(x.successful_creds), style: { 'whiteSpace': 'unset' }},
+ ]
+ }])};
+
+ static renderCreds(creds) {
+ return {creds.map(cred =>