diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py index bff8a94cd..82be595c7 100644 --- a/monkey/monkey_island/cc/resources/bootloader.py +++ b/monkey/monkey_island/cc/resources/bootloader.py @@ -1,35 +1,23 @@ import json +from typing import Dict import flask_restful from flask import request, make_response -from monkey_island.cc.database import mongo -from monkey_island.cc.services.node import NodeService - -WINDOWS_VERSIONS = { - "5.0": "Windows 2000", - "5.1": "Windows XP", - "5.2": "Windows XP/server 2003", - "6.0": "Windows Vista/server 2008", - "6.1": "Windows 7/server 2008R2", - "6.2": "Windows 8/server 2012", - "6.3": "Windows 8.1/server 2012R2", - "10.0": "Windows 10/server 2016-2019" -} +from monkey_island.cc.services.bootloader import BootloaderService class Bootloader(flask_restful.Resource): # Used by monkey. can't secure. def post(self, **kw): - data = json.loads(request.data.decode().replace("\n", "").replace("NAME=\"", "").replace("\"\"", "\"")) - - # Remove local ips - local_addr = [i for i in data["ips"] if i.startswith("127")] - if local_addr: - data["ips"].remove(local_addr[0]) - - mongo.db.bootloader_telems.insert(data) - node_id = NodeService.get_or_create_node_from_bootloader_telem(data) - + data = Bootloader.parse_bootloader_request(request.data) + resp = BootloaderService.parse_bootloader_data(data) return make_response({"status": "OK"}, 200) + + @staticmethod + def parse_bootloader_request(request_data: bytes) -> Dict[str, str]: + parsed_data = json.loads(request_data.decode().replace("\n", "") + .replace("NAME=\"", "") + .replace("\"\"", "\"")) + return parsed_data diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py new file mode 100644 index 000000000..d1a9eead2 --- /dev/null +++ b/monkey/monkey_island/cc/services/bootloader.py @@ -0,0 +1,42 @@ +from typing import Dict, List + +from monkey_island.cc.database import mongo +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.utils.node_groups import NodeGroups + +WINDOWS_VERSIONS = { + "5.0": "Windows 2000", + "5.1": "Windows XP", + "5.2": "Windows XP/server 2003", + "6.0": "Windows Vista/server 2008", + "6.1": "Windows 7/server 2008R2", + "6.2": "Windows 8/server 2012", + "6.3": "Windows 8.1/server 2012R2", + "10.0": "Windows 10/server 2016-2019" +} + +MIN_GLIBC_VERSION = 2.14 + + +class BootloaderService: + + @staticmethod + def parse_bootloader_data(data: Dict) -> str: + data['ips'] = BootloaderService.remove_local_ips(data['ips']) + mongo.db.bootloader_telems.insert(data) + will_monkey_run = BootloaderService.is_glibc_supported(data['glibc_version']) + node = NodeService.get_or_create_node_from_bootloader_data(data, will_monkey_run) + group_keywords = [data['system'], 'monkey'] + group_keywords.append('starting') if will_monkey_run else group_keywords.append('old') + NodeService.set_node_group(node['_id'], NodeGroups.get_group_by_keywords(group_keywords)) + return "abc" + + @staticmethod + def is_glibc_supported(glibc_version_string) -> bool: + glibc_version_string = glibc_version_string.lower() + glibc_version = glibc_version_string.split(' ')[-1] + return glibc_version >= str(MIN_GLIBC_VERSION) and 'eglibc' not in glibc_version_string + + @staticmethod + def remove_local_ips(ip_list) -> List[str]: + return [i for i in ip_list if not i.startswith("127")] diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/bootloader_test.py new file mode 100644 index 000000000..03df2be97 --- /dev/null +++ b/monkey/monkey_island/cc/services/bootloader_test.py @@ -0,0 +1,35 @@ +from unittest import TestCase + +from monkey_island.cc.services.bootloader import BootloaderService + +WINDOWS_VERSIONS = { + "5.0": "Windows 2000", + "5.1": "Windows XP", + "5.2": "Windows XP/server 2003", + "6.0": "Windows Vista/server 2008", + "6.1": "Windows 7/server 2008R2", + "6.2": "Windows 8/server 2012", + "6.3": "Windows 8.1/server 2012R2", + "10.0": "Windows 10/server 2016-2019" +} + +MIN_GLIBC_VERSION = 2.14 + + +class TestBootloaderService(TestCase): + + def test_is_glibc_supported(self): + str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15" + str2 = "ldd (GNU libc) 2.12" + str3 = "ldd (GNU libc) 2.28" + str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23" + self.assertTrue(not BootloaderService.is_glibc_supported(str1) and + not BootloaderService.is_glibc_supported(str2) and + BootloaderService.is_glibc_supported(str3) and + BootloaderService.is_glibc_supported(str4)) + + def test_remove_local_ips(self): + ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"] + ips = BootloaderService.remove_local_ips(ips) + self.assertEqual(["192.168.56.1"], ips) + diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index feb42cc24..8a63d576b 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -1,4 +1,6 @@ from datetime import datetime, timedelta +from typing import Dict +import socket from bson import ObjectId @@ -7,8 +9,8 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.utils import local_ip_addresses -import socket from monkey_island.cc import models +from monkey_island.cc.services.utils.node_groups import NodeGroups __author__ = "itay.mizeretz" @@ -122,20 +124,25 @@ class NodeService: @staticmethod def get_monkey_group(monkey): + keywords = [] if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0: - monkey_type = "island_monkey" + keywords.extend(["island", "monkey"]) else: monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey" + keywords.append(monkey_type) - monkey_os = NodeService.get_monkey_os(monkey) - monkey_running = "" if Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead() else "_running" - return "%s_%s%s" % (monkey_type, monkey_os, monkey_running) + keywords.append(NodeService.get_monkey_os(monkey)) + if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead(): + keywords.append("running") + return NodeGroups.get_group_by_keywords(keywords).value @staticmethod - def get_node_group(node): + def get_node_group(node) -> str: + if node['group']: + return node['group'] node_type = "exploited" if node.get("exploited") else "clean" node_os = NodeService.get_node_os(node) - return "%s_%s" % (node_type, node_os) + return NodeGroups.get_group_by_keywords([node_type, node_os]).value @staticmethod def monkey_to_net_node(monkey, for_report=False): @@ -166,6 +173,12 @@ class NodeService: "os": NodeService.get_node_os(node) } + @staticmethod + def set_node_group(node_id: str, node_group: NodeGroups): + mongo.db.node.update({"_id": node_id}, + {'$set': {'group': node_group.value}}, + upsert=False) + @staticmethod def unset_all_monkey_tunnels(monkey_id): mongo.db.monkey.update( @@ -208,37 +221,38 @@ class NodeService: return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod - def create_node_from_bootloader_telem(bootloader_telem): + def create_node_from_bootloader_data(bootloader_data: Dict, will_monkey_run: bool): new_node_insert_result = mongo.db.node.insert_one( { - "ip_addresses": bootloader_telem['ips'], - "domain_name": bootloader_telem['hostname'], + "ip_addresses": bootloader_data['ips'], + "domain_name": bootloader_data['hostname'], + "will_monkey_run": will_monkey_run, "exploited": False, "creds": [], "os": { - "type": bootloader_telem['system'], - "version": bootloader_telem['os_version'] + "type": bootloader_data['system'], + "version": bootloader_data['os_version'] } }) return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod - def get_or_create_node_from_bootloader_telem(bootloader_telem): - new_node = mongo.db.node.find_one({"domain_name": bootloader_telem['hostname'], - "ip_addresses": bootloader_telem['ips']}) + def get_or_create_node_from_bootloader_data(bootloader_data: Dict, will_monkey_run: bool) -> Dict: + new_node = mongo.db.node.find_one({"domain_name": bootloader_data['hostname'], + "ip_addresses": bootloader_data['ips']}) if new_node is None: - new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem) - if bootloader_telem['tunnel']: - dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel']) + new_node = NodeService.create_node_from_bootloader_data(bootloader_data, will_monkey_run) + if bootloader_data['tunnel']: + dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_data['tunnel']) else: dst_node = NodeService.get_monkey_island_node() edge = EdgeService.get_or_create_edge(new_node['_id'], dst_node['id']) mongo.db.edge.update({"_id": edge["_id"]}, - {'$set': {'tunnel': bool(bootloader_telem['tunnel']), - 'exploited': (not bool(bootloader_telem['tunnel'])), - 'ip_address': bootloader_telem['ips'][0], - 'group': 'island'}}, + {'$set': {'tunnel': bool(bootloader_data['tunnel']), + 'exploited': (not bool(bootloader_data['tunnel'])), + 'ip_address': bootloader_data['ips'][0], + 'group': NodeGroups.get_group_by_keywords(['island']).value}}, upsert=False) return new_node diff --git a/monkey/monkey_island/cc/services/utils/node_groups.py b/monkey/monkey_island/cc/services/utils/node_groups.py new file mode 100644 index 000000000..751798b18 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/node_groups.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from enum import Enum +from typing import List +import collections + + +class NodeGroups(Enum): + CLEAN_UNKNOWN = 'clean_unknown' + CLEAN_LINUX = 'clean_linux' + CLEAN_WINDOWS = 'clean_windows' + EXPLOITED_LINUX = 'exploited_linux' + EXPLOITED_WINDOWS = 'exploited_windows' + ISLAND = 'island' + ISLAND_MONKEY_LINUX = 'island_monkey_linux' + ISLAND_MONKEY_LINUX_RUNNING = 'island_monkey_linux_running' + ISLAND_MONKEY_WINDOWS = 'island_monkey_windows' + ISLAND_MONKEY_WINDOWS_RUNNING = 'island_monkey_windows_running' + MANUAL_LINUX = 'manual_linux' + MANUAL_LINUX_RUNNING = 'manual_linux_running' + MANUAL_WINDOWS = 'manual_windows' + MANUAL_WINDOWS_RUNNING = 'manual_windows_running' + MONKEY_LINUX = 'monkey_linux' + MONKEY_LINUX_RUNNING = 'monkey_linux_running' + MONKEY_WINDOWS = 'monkey_windows' + MONKEY_WINDOWS_RUNNING = 'monkey_windows_running' + MONKEY_WINDOWS_STARTING = 'monkey_windows_starting' + MONKEY_LINUX_STARTING = 'monkey_linux_starting' + MONKEY_WINDOWS_OLD = 'monkey_windows_old' + MONKEY_LINUX_OLD = 'monkey_linux_old' + + @staticmethod + def get_group_by_keywords(keywords: List) -> NodeGroups: + potential_groups = [i for i in NodeGroups if NodeGroups._is_group_from_keywords(i, keywords)] + if len(potential_groups) > 1: + raise MultipleGroupsFoundException("Multiple groups contain provided keywords. " + "Manually build group string to ensure keyword order.") + elif len(potential_groups) == 0: + raise NoGroupsFoundException("No groups found with provided keywords. " + "Check for typos and make sure group codes want to find exists.") + return potential_groups[0] + + @staticmethod + def _is_group_from_keywords(group, keywords) -> bool: + group_keywords = group.value.split("_") + return collections.Counter(group_keywords) == collections.Counter(keywords) + + +class MultipleGroupsFoundException(Exception): + pass + + +class NoGroupsFoundException(Exception): + pass diff --git a/monkey/monkey_island/cc/services/utils/node_groups_test.py b/monkey/monkey_island/cc/services/utils/node_groups_test.py new file mode 100644 index 000000000..ef7a5b555 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/node_groups_test.py @@ -0,0 +1,18 @@ +from unittest import TestCase + +from monkey_island.cc.services.utils.node_groups import NodeGroups, NoGroupsFoundException + + +class TestNodeGroups(TestCase): + + def test_get_group_by_keywords(self): + tst1 = NodeGroups.get_group_by_keywords(['island']) == NodeGroups.ISLAND + tst2 = NodeGroups.get_group_by_keywords(['running', 'linux', 'monkey']) == NodeGroups.MONKEY_LINUX_RUNNING + tst3 = NodeGroups.get_group_by_keywords(['monkey', 'linux', 'running']) == NodeGroups.MONKEY_LINUX_RUNNING + tst4 = False + try: + NodeGroups.get_group_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail']) + except NoGroupsFoundException: + tst4 = True + self.assertTrue(tst1 and tst2 and tst3 and tst4) + diff --git a/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js index 7e4805797..08e8afd0d 100644 --- a/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js @@ -1,7 +1,8 @@ const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', 'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running', 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', - 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; + 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running', 'monkey_windows_starting', + 'monkey_linux_starting', 'monkey_windows_old', 'monkey_linux_old' ]; let getGroupsOptions = () => { let groupOptions = {}; diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png new file mode 100644 index 000000000..d582c8ecf Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png new file mode 100644 index 000000000..a7aa12793 Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png new file mode 100644 index 000000000..58de7ff52 Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png new file mode 100644 index 000000000..1545280b4 Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png differ