From 9492b14c9579cfef7ae244c18e15cf1e243ffb10 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Jul 2021 13:48:34 +0300 Subject: [PATCH] Add unit tests and for ransomware report, which get skipped because of a bug in mongomock --- .../services/ransomware/ransomware_report.py | 83 ++++++++++++ .../data_for_tests/mongo_documents/edges.py | 125 ++++++++++++++++++ .../data_for_tests/mongo_documents/monkeys.py | 50 +++++++ .../telemetries/file_encryption.py | 59 +++++++++ .../ransomware/test_ransomware_report.py | 70 ++++++++++ 5 files changed, 387 insertions(+) create mode 100644 monkey/tests/data_for_tests/mongo_documents/edges.py create mode 100644 monkey/tests/data_for_tests/mongo_documents/monkeys.py create mode 100644 monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 0e0f1c299..769fc5e53 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,8 +1,91 @@ +from monkey_island.cc.database import mongo from typing import Dict, List from monkey_island.cc.services.reporting.report import ReportService +class RansomwareReportService: + @staticmethod + def get_encrypted_files_table(): + query = [ + {"$match": {"telem_category": "file_encryption"}}, + {"$unwind": "$data.files"}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid", "files_encrypted": "$data.files.success"} + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, + {"$sort": {"files_encrypted": -1}}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid"}, + "monkey_guid": {"$first": "$monkey_guid"}, + "files_encrypted": {"$first": "$files_encrypted"}, + } + }, + { + "$lookup": { + "from": "monkey", + "localField": "_id.monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "files_encrypted": "$files_encrypted", + } + }, + { + "$lookup": { + "from": "edge", + "localField": "monkey._id", + "foreignField": "dst_node_id", + "as": "edge", + } + }, + { + "$project": { + "monkey": "$monkey", + "files_encrypted": "$files_encrypted", + "edge": {"$arrayElemAt": ["$edge", 0]}, + } + }, + { + "$project": { + "hostname": "$monkey.hostname", + "successful_exploits": { + "$filter": { + "input": "$edge.exploits", + "as": "exploit", + "cond": {"$eq": ["$$exploit.result", True]}, + } + }, + "files_encrypted": "$files_encrypted", + } + }, + { + "$addFields": { + "successful_exploit": {"$arrayElemAt": ["$successful_exploits", 0]}, + } + }, + { + "$project": { + "hostname": "$hostname", + "exploiter": "$successful_exploit.info.display_name", + "files_encrypted": "$files_encrypted", + } + }, + ] + + table_data = list(mongo.db.telemetry.aggregate(query)) + for encryption_entry in table_data: + if "exploiter" not in encryption_entry: + encryption_entry["exploiter"] = "Manual run" + return table_data + def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() exploited = ReportService.get_exploited() diff --git a/monkey/tests/data_for_tests/mongo_documents/edges.py b/monkey/tests/data_for_tests/mongo_documents/edges.py new file mode 100644 index 000000000..25c12f29f --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/edges.py @@ -0,0 +1,125 @@ +from mongomock import ObjectId + +EDGE_EXPLOITED = { + "_id": ObjectId("60e541c07a6cdf66484ba504"), + "_cls": "Edge.EdgeService", + "src_node_id": ObjectId("60e541aab6732b49f4c564ea"), + "dst_node_id": ObjectId("60e541c6b6732b49f4c56622"), + "scans": [ + { + "timestamp": "2021-07-07T08:55:12.866Z", + "data": { + "os": {"type": "windows"}, + "services": {"tcp-445": {"display_name": "SMB", "port": 445}}, + "icmp": True, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, + }, + } + ], + "exploits": [ + { + "result": True, + "exploiter": "SmbExploiter", + "info": { + "display_name": "SMB", + "started": "2021-07-07T08:55:12.944Z", + "finished": "2021-07-07T08:55:14.376Z", + "vulnerable_urls": [], + "vulnerable_ports": ["139 or 445", "139 or 445"], + "executed_cmds": [], + }, + "attempts": [ + { + "result": False, + "user": "Administrator", + "password": "LydBuBjDAe/igLGS2FyeKL1zLoTt0r+CkaPH1v5/Vr7HmzcbBPW562Io+MQlrMey", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + { + "result": True, + "user": "user", + "password": "Evzzovf6QLOPUja78/nP6XgiNXH5bB1MrXqPBYmBgeQDOcBhJPUE32+8968zDlHy", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + ], + "timestamp": "2021-07-07T08:55:14.420Z", + }, + { + "result": True, + "exploiter": "SmbExploiter", + "info": { + "display_name": "SMB", + "started": "2021-07-07T12:08:35.168Z", + "finished": "2021-07-07T12:08:36.612Z", + "vulnerable_urls": [], + "vulnerable_ports": ["139 or 445", "139 or 445"], + "executed_cmds": [], + }, + "attempts": [ + { + "result": False, + "user": "Administrator", + "password": "B4o8ujKpBfKyjCvb7c5bHr7a8CzwfOJi+i228WGv4/9OZZaEsKjps/5Zg1aHSEun", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + { + "result": True, + "user": "user", + "password": "Evzzovf6QLOPUja78/nP6XgiNXH5bB1MrXqPBYmBgeQDOcBhJPUE32+8968zDlHy", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + ], + "timestamp": "2021-07-07T12:08:36.650Z", + }, + ], + "tunnel": False, + "exploited": True, + "src_label": "MonkeyIsland - test-pc-2 : 192.168.56.1", + "dst_label": "WinDev2010Eval : 172.25.33.145", + "domain_name": "", + "ip_address": "172.25.33.145", +} + +EDGE_SCANNED = { + "_id": ObjectId("60e6b24dc10b80a409c048a3"), + "_cls": "Edge.EdgeService", + "src_node_id": ObjectId("60e541aab6732b49f4c564ea"), + "dst_node_id": ObjectId("60e6b24dc10b80a409c048a2"), + "scans": [ + { + "timestamp": "2021-07-08T11:07:41.407Z", + "data": { + "os": {"type": "linux", "version": "Ubuntu-4ubuntu0.3"}, + "services": { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "banner": "SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3\r\n", + "name": "ssh", + } + }, + "icmp": True, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, + }, + } + ], + "exploits": [], + "tunnel": False, + "exploited": False, + "src_label": "MonkeyIsland - test-pc-2 : 192.168.56.1", + "dst_label": "Ubuntu-4ubuntu0.3 : 172.24.125.179", + "domain_name": "", + "ip_address": "172.24.125.179", +} diff --git a/monkey/tests/data_for_tests/mongo_documents/monkeys.py b/monkey/tests/data_for_tests/mongo_documents/monkeys.py new file mode 100644 index 000000000..c35c1d124 --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/monkeys.py @@ -0,0 +1,50 @@ +from mongomock import ObjectId + +MONKEY_AT_ISLAND = { + "_id": ObjectId("60e541aab6732b49f4c564ea"), + "guid": "211375648895908", + "config": {}, + "creds": [], + "dead": True, + "description": "Windows test-pc-2 10", + "hostname": "test-pc-2", + "internet_access": True, + "ip_addresses": [ + "192.168.56.1", + "172.17.192.1", + "172.25.32.1", + "192.168.43.1", + "192.168.10.1", + "192.168.0.102", + ], + "keepalive": "2021-07-07T12:08:13.164Z", + "modifytime": "2021-07-07T12:10:13.340Z", + "parent": [ + ["211375648895908", None], + ["211375648895908", None], + ["211375648895908", None], + ["211375648895908", None], + ], + "ttl_ref": ObjectId("60e56f757a6cdf66484ba5cc"), + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, + "pba_results": [], +} + +MONKEY_AT_VICTIM = { + "_id": ObjectId("60e541c6b6732b49f4c56622"), + "guid": "91758264576", + "config": {}, + "creds": [], + "dead": False, + "description": "Windows WinDev2010Eval 10 10.0.19041 AMD64 Intel64 Family 6 Model 165 " + "Stepping 2, GenuineIntel", + "hostname": "WinDev2010Eval", + "internet_access": True, + "ip_addresses": ["172.25.33.145"], + "keepalive": "2021-07-07T12:08:41.200Z", + "modifytime": "2021-07-07T12:08:47.144Z", + "parent": [["211375648895908", "SmbExploiter"], ["211375648895908", None]], + "ttl_ref": ObjectId("60e56f1f7a6cdf66484ba5c5"), + "command_control_channel": {"src": "172.25.33.145", "dst": "172.25.32.1:5000"}, + "pba_results": [], +} diff --git a/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py b/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py new file mode 100644 index 000000000..ff1a6914e --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py @@ -0,0 +1,59 @@ +from mongomock import ObjectId + +ENCRYPTED = { + "_id": ObjectId("60e541c37a6cdf66484ba517"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + {"path": "infection_monkey.py", "success": True, "error": ""}, + {"path": "monkey_island.py", "success": True, "error": ""}, + {"path": "__init__.py", "success": True, "error": ""}, + ] + }, + "timestamp": "2021-07-07T08:55:15.830Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTED_2 = { + "_id": ObjectId("60e54fee7a6cdf66484ba559"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + {"path": "infection_monkey.py", "success": True, "error": ""}, + {"path": "monkey_island.py", "success": True, "error": ""}, + {"path": "__init__.py", "success": True, "error": ""}, + ] + }, + "timestamp": "2021-07-07T09:55:42.311Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTION_ERROR = { + "_id": ObjectId("60e56f167a6cdf66484ba5aa"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + { + "path": "C:\\w\\Dump\\README.txt", + "success": False, + "error": "[WinError 183] Cannot create a file when that " + "file already exists: 'C:\\\\w\\\\Dump\\\\README.txt'" + " -> 'C:\\\\w\\\\Dump\\\\README.txt.m0nk3y'", + } + ] + }, + "timestamp": "2021-07-07T12:08:38.058Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTION_ONE_FILE = { + "_id": ObjectId("60e56f1b7a6cdf66484ba5c0"), + "monkey_guid": "91758264576", + "telem_category": "file_encryption", + "data": {"files": [{"path": "C:\\w\\Dump\\README.txt", "success": True, "error": ""}]}, + "timestamp": "2021-07-07T12:08:43.421Z", + "command_control_channel": {"src": "172.25.33.145", "dst": "172.25.32.1:5000"}, +} diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 4c586aa81..c1213ef95 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,8 +1,78 @@ +import mongomock +from tests.data_for_tests.mongo_documents.edges import EDGE_EXPLOITED, EDGE_SCANNED +from tests.data_for_tests.mongo_documents.monkeys import MONKEY_AT_ISLAND, MONKEY_AT_VICTIM +from tests.data_for_tests.mongo_documents.telemetries.file_encryption import ( + ENCRYPTED, + ENCRYPTED_2, + ENCRYPTION_ERROR, + ENCRYPTION_ONE_FILE, +) import pytest from monkey_island.cc.services.ransomware import ransomware_report from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.ransomware.ransomware_report import RansomwareReportService + + +@pytest.fixture +def fake_mongo(monkeypatch): + mongo = mongomock.MongoClient() + monkeypatch.setattr("monkey_island.cc.services.ransomware.ransomware_report.mongo", mongo) + return mongo + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + fake_mongo.db.telemetry.insert(ENCRYPTED) + fake_mongo.db.telemetry.insert(ENCRYPTED_2) + fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) + fake_mongo.db.telemetry.insert(ENCRYPTION_ONE_FILE) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [ + {"hostname": "test-pc-2", "exploit": "Manual execution", "files_encrypted": True}, + {"hostname": "WinDev2010Eval", "exploit": "SMB", "files_encrypted": True}, + ] + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table__only_errors(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [] + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table__no_telemetries(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [] @pytest.fixture def patch_report_service_for_stats(monkeypatch):