diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md
index 92a2fd76e..10027edce 100644
--- a/deployment_scripts/README.md
+++ b/deployment_scripts/README.md
@@ -13,9 +13,10 @@ Don't forget to add python to PATH or do so while installing it via this script.
## Linux
-You must have root permissions, but there is no need to run the script as root.
+You must have root permissions, but don't run the script as root.
Launch deploy_linux.sh from scripts directory.
-First argument is an empty directory (script can create one) and second is branch you want to clone.
+First argument should be an empty directory (script can create one, default is ./infection_monkey) and second is the branch you want to clone (develop by default).
+Choose a directory where you have all the relevant permissions, for e.g. /home/your_username
Example usages:
./deploy_linux.sh (deploys under ./infection_monkey)
./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py
index 620657fb6..87ea58477 100644
--- a/monkey/infection_monkey/exploit/weblogic.py
+++ b/monkey/infection_monkey/exploit/weblogic.py
@@ -36,7 +36,7 @@ class WebLogicExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', 'windows']
_EXPLOITED_SERVICE = 'Weblogic'
- def exploit_host(self):
+ def _exploit_host(self):
exploiters = [WebLogic20192725, WebLogic201710271]
for exploiter in exploiters:
if exploiter(self.host).exploit_host():
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index 7769ceab8..d6a28c924 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -179,8 +179,11 @@ class InfectionMonkey(object):
if monkey_tunnel:
monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server:
- machine.set_default_server(get_interface_to_target(machine.ip_addr) +
- (':'+self._default_server_port if self._default_server_port else ''))
+ if self._network.on_island(self._default_server):
+ machine.set_default_server(get_interface_to_target(machine.ip_addr) +
+ (':'+self._default_server_port if self._default_server_port else ''))
+ else:
+ machine.set_default_server(self._default_server)
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
# Order exploits according to their type
@@ -252,6 +255,7 @@ class InfectionMonkey(object):
if WormConfiguration.self_delete_in_cleanup \
and -1 == sys.executable.find('python'):
try:
+ status = None
if "win32" == sys.platform:
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
startupinfo = subprocess.STARTUPINFO()
@@ -262,10 +266,12 @@ class InfectionMonkey(object):
close_fds=True, startupinfo=startupinfo)
else:
os.remove(sys.executable)
- T1107Telem(ScanStatus.USED, sys.executable).send()
+ status = ScanStatus.USED
except Exception as exc:
LOG.error("Exception in self delete: %s", exc)
- T1107Telem(ScanStatus.SCANNED, sys.executable).send()
+ status = ScanStatus.SCANNED
+ if status:
+ T1107Telem(status, sys.executable).send()
def send_log(self):
monkey_log_path = utils.get_monkey_log_path()
diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py
index 5f65e4228..837add48a 100644
--- a/monkey/infection_monkey/network/network_scanner.py
+++ b/monkey/infection_monkey/network/network_scanner.py
@@ -118,3 +118,6 @@ class NetworkScanner(object):
if NetworkRange.get_range_obj(subnet_str).is_in_range(ip_address):
return True
return False
+
+ def on_island(self, server):
+ return bool([x for x in self._ip_addresses if x in server])
diff --git a/monkey/monkey_island/cc/consts.py b/monkey/monkey_island/cc/consts.py
index deb1db449..c302f6fb7 100644
--- a/monkey/monkey_island/cc/consts.py
+++ b/monkey/monkey_island/cc/consts.py
@@ -3,3 +3,4 @@ import os
__author__ = 'itay.mizeretz'
MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island')
+DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5
diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py
index 520b967a0..f1bd12905 100644
--- a/monkey/monkey_island/cc/models/monkey.py
+++ b/monkey/monkey_island/cc/models/monkey.py
@@ -1,11 +1,11 @@
"""
Define a Document Schema for the Monkey document.
"""
-import mongoengine
from mongoengine import Document, StringField, ListField, BooleanField, EmbeddedDocumentField, ReferenceField, \
- DateTimeField
+ DateTimeField, DynamicField, DoesNotExist
-from monkey_island.cc.models.monkey_ttl import MonkeyTtl
+from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
+from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
class Monkey(Document):
@@ -26,24 +26,37 @@ class Monkey(Document):
ip_addresses = ListField(StringField())
keepalive = DateTimeField()
modifytime = DateTimeField()
- # TODO change this to an embedded document as well - RN it's an unnamed tuple which is confusing.
- parent = ListField(ListField(StringField()))
+ # TODO make "parent" an embedded document, so this can be removed and the schema explained (and validated) verbosly.
+ # This is a temporary fix, since mongoengine doesn't allow for lists of strings to be null
+ # (even with required=False of null=True).
+ # See relevant issue: https://github.com/MongoEngine/mongoengine/issues/1904
+ parent = ListField(ListField(DynamicField()))
config_error = BooleanField()
critical_services = ListField(StringField())
pba_results = ListField()
ttl_ref = ReferenceField(MonkeyTtl)
+ tunnel = ReferenceField("self")
# LOGIC
@staticmethod
def get_single_monkey_by_id(db_id):
try:
- return Monkey.objects(id=db_id)[0]
- except IndexError:
- raise MonkeyNotFoundError("id: {0}".format(str(db_id)))
+ return Monkey.objects.get(id=db_id)
+ except DoesNotExist as ex:
+ raise MonkeyNotFoundError("info: {0} | id: {1}".format(ex.message, str(db_id)))
+
+ @staticmethod
+ def get_single_monkey_by_guid(monkey_guid):
+ try:
+ return Monkey.objects.get(guid=monkey_guid)
+ except DoesNotExist as ex:
+ raise MonkeyNotFoundError("info: {0} | guid: {1}".format(ex.message, str(monkey_guid)))
@staticmethod
def get_latest_modifytime():
- return Monkey.objects.order_by('-modifytime').first().modifytime
+ if Monkey.objects.count() > 0:
+ return Monkey.objects.order_by('-modifytime').first().modifytime
+ return None
def is_dead(self):
monkey_is_dead = False
@@ -54,7 +67,7 @@ class Monkey(Document):
if MonkeyTtl.objects(id=self.ttl_ref.id).count() == 0:
# No TTLs - monkey has timed out. The monkey is MIA.
monkey_is_dead = True
- except (mongoengine.DoesNotExist, AttributeError):
+ except (DoesNotExist, AttributeError):
# Trying to dereference unknown document - the monkey is MIA.
monkey_is_dead = True
return monkey_is_dead
@@ -67,6 +80,10 @@ class Monkey(Document):
os = "windows"
return os
+ def renew_ttl(self, duration=DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS):
+ self.ttl_ref = create_monkey_ttl_document(duration)
+ self.save()
+
class MonkeyNotFoundError(Exception):
pass
diff --git a/monkey/monkey_island/cc/models/monkey_ttl.py b/monkey/monkey_island/cc/models/monkey_ttl.py
index 9ccf77974..b3e59d5ed 100644
--- a/monkey/monkey_island/cc/models/monkey_ttl.py
+++ b/monkey/monkey_island/cc/models/monkey_ttl.py
@@ -38,3 +38,16 @@ class MonkeyTtl(Document):
}
expire_at = DateTimeField()
+
+
+def create_monkey_ttl_document(expiry_duration_in_seconds):
+ """
+ Create a new Monkey TTL document and save it as a document.
+ :param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND - depends on mongodb
+ performance.
+ :return: The TTL document. To get its ID use `.id`.
+ """
+ # The TTL data uses the new `models` module which depends on mongoengine.
+ current_ttl = MonkeyTtl.create_ttl_expire_in(expiry_duration_in_seconds)
+ current_ttl.save()
+ return current_ttl
diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py
index a744db6b6..717fb309a 100644
--- a/monkey/monkey_island/cc/models/test_monkey.py
+++ b/monkey/monkey_island/cc/models/test_monkey.py
@@ -46,6 +46,19 @@ class TestMonkey(IslandTestCase):
self.assertTrue(mia_monkey.is_dead())
self.assertFalse(alive_monkey.is_dead())
+ def test_ttl_renewal(self):
+ self.fail_if_not_testing_env()
+ self.clean_monkey_db()
+
+ # Arrange
+ monkey = Monkey(guid=str(uuid.uuid4()))
+ monkey.save()
+ self.assertIsNone(monkey.ttl_ref)
+
+ # act + assert
+ monkey.renew_ttl()
+ self.assertIsNotNone(monkey.ttl_ref)
+
def test_get_single_monkey_by_id(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py
index 36720e465..8e523a8a7 100644
--- a/monkey/monkey_island/cc/resources/monkey.py
+++ b/monkey/monkey_island/cc/resources/monkey.py
@@ -5,26 +5,17 @@ import dateutil.parser
import flask_restful
from flask import request
+from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.database import mongo
-from monkey_island.cc.models.monkey_ttl import MonkeyTtl
+from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService
-MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5
-
__author__ = 'Barak'
# TODO: separate logic from interface
-def create_monkey_ttl():
- # The TTL data uses the new `models` module which depends on mongoengine.
- current_ttl = MonkeyTtl.create_ttl_expire_in(MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
- current_ttl.save()
- ttlid = current_ttl.id
- return ttlid
-
-
class Monkey(flask_restful.Resource):
# Used by monkey. can't secure.
@@ -58,8 +49,8 @@ class Monkey(flask_restful.Resource):
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip)
- ttlid = create_monkey_ttl()
- update['$set']['ttl_ref'] = ttlid
+ ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
+ update['$set']['ttl_ref'] = ttl.id
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
@@ -120,7 +111,8 @@ class Monkey(flask_restful.Resource):
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
monkey_json.pop('tunnel')
- monkey_json['ttl_ref'] = create_monkey_ttl()
+ ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS)
+ monkey_json['ttl_ref'] = ttl.id
mongo.db.monkey.update({"guid": monkey_json["guid"]},
{"$set": monkey_json},
diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py
index d4aaa72df..279547dc1 100644
--- a/monkey/monkey_island/cc/resources/telemetry.py
+++ b/monkey/monkey_island/cc/resources/telemetry.py
@@ -15,10 +15,10 @@ from monkey_island.cc.services.edge import EdgeService
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.services.wmi_handler import WMIHandler
+from monkey_island.cc.models.monkey import Monkey
__author__ = 'Barak'
-
logger = logging.getLogger(__name__)
@@ -49,6 +49,9 @@ class Telemetry(flask_restful.Resource):
telemetry_json = json.loads(request.data)
telemetry_json['timestamp'] = datetime.now()
+ # Monkey communicated, so it's alive. Update the TTL.
+ Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl()
+
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
try:
@@ -59,7 +62,7 @@ class Telemetry(flask_restful.Resource):
else:
logger.info('Got unknown type of telemetry: %s' % telem_category)
except Exception as ex:
- logger.error("Exception caught while processing telemetry", exc_info=True)
+ logger.error("Exception caught while processing telemetry. Info: {}".format(ex.message), exc_info=True)
telem_id = mongo.db.telemetry.insert(telemetry_json)
return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
@@ -188,7 +191,7 @@ class Telemetry(flask_restful.Resource):
Telemetry.add_system_info_creds_to_config(creds)
Telemetry.replace_user_dot_with_comma(creds)
if 'mimikatz' in telemetry_json['data']:
- users_secrets = mimikatz_utils.MimikatzSecrets.\
+ users_secrets = mimikatz_utils.MimikatzSecrets. \
extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
if 'wmi' in telemetry_json['data']:
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py
index a4f090758..e271c45c5 100644
--- a/monkey/monkey_island/cc/resources/telemetry_feed.py
+++ b/monkey/monkey_island/cc/resources/telemetry_feed.py
@@ -1,3 +1,4 @@
+import logging
from datetime import datetime
import dateutil
@@ -9,6 +10,8 @@ from monkey_island.cc.auth import jwt_required
from monkey_island.cc.database import mongo
from monkey_island.cc.services.node import NodeService
+logger = logging.getLogger(__name__)
+
__author__ = 'itay.mizeretz'
@@ -23,11 +26,15 @@ class TelemetryFeed(flask_restful.Resource):
telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)])
- return \
- {
- 'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries],
- 'timestamp': datetime.now().isoformat()
- }
+ try:
+ return \
+ {
+ 'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries],
+ 'timestamp': datetime.now().isoformat()
+ }
+ except KeyError as err:
+ logger.error("Failed parsing telemetries. Error: {0}.".format(err.message))
+ return {'telemetries': [], 'timestamp': datetime.now().isoformat()}
@staticmethod
def get_displayed_telemetry(telem):
diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py
index 3caa7dfa0..fcc182da1 100644
--- a/monkey/monkey_island/cc/services/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema.py
@@ -389,7 +389,7 @@ SCHEMA = {
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
- "default": False,
+ "default": True,
"description": "Should the monkey delete its executable when going down"
},
"use_file_logging": {
diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py
index 9ceb9662a..593bbfdaf 100644
--- a/monkey/monkey_island/cc/services/report.py
+++ b/monkey/monkey_island/cc/services/report.py
@@ -373,8 +373,13 @@ class ReportService:
@staticmethod
def get_exploits():
+ query = [{'$match': {'telem_category': 'exploit', 'data.result': True}},
+ {'$group': {'_id': {'ip_address': '$data.machine.ip_addr'},
+ 'data': {'$first': '$$ROOT'},
+ }},
+ {"$replaceRoot": {"newRoot": "$data"}}]
exploits = []
- for exploit in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.result': True}):
+ for exploit in mongo.db.telemetry.aggregate(query):
new_exploit = ReportService.process_exploit(exploit)
if new_exploit not in exploits:
exploits.append(new_exploit)
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
index b620e82d7..0950b2a63 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js
@@ -20,7 +20,7 @@ export function renderMachineFromSystemData(data) {
return machineStr + ")"
}
-export const scanStatus = {
+export const ScanStatus = {
UNSCANNED: 0,
SCANNED: 1,
USED: 2
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
index 208840cf3..07fd4a400 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
@@ -2,7 +2,7 @@ import React from 'react';
import '../../../styles/Collapse.scss'
import '../../report-components/StolenPasswords'
import StolenPasswordsComponent from "../../report-components/StolenPasswords";
-import {scanStatus} from "./Helpers"
+import {ScanStatus} from "./Helpers"
class T1003 extends React.Component {
@@ -16,7 +16,7 @@ class T1003 extends React.Component {
- Not sure what this is? Not seeing your AWS EC2 instances? Read the documentation! + Not sure what this is? Not seeing your AWS EC2 instances? Read the documentation!