From da3a2c1a02f7e6174f520911b548a55c1bebcec8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 23 Jul 2021 11:13:54 -0400 Subject: [PATCH 1/6] UI: Display encrypted file paths in ransomware report table --- .../cc/ui/src/components/pages/ReportPage.js | 15 ++- .../report-components/RansomwareReport.js | 2 +- .../ransomware/FileEncryptionTable.tsx | 106 ++++++++++++------ 3 files changed, 89 insertions(+), 34 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index c45dbe7e7..b2d1446b3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -22,6 +22,7 @@ class ReportPageComponent extends AuthComponent { attackReport: {}, zeroTrustReport: {}, ransomwareReport: {}, + ransomwareTelemetry: {}, allMonkeysAreDead: false, runStarted: true, selectedSection: ReportPageComponent.selectReport(this.sections), @@ -67,6 +68,13 @@ class ReportPageComponent extends AuthComponent { ransomwareReport: res }); }); + this.authFetch('/api/telemetry?telem_category=file_encryption') + .then(res => res.json()) + .then(res => { + this.setState({ + ransomwareTelemetry: res + }); + }); } } @@ -156,7 +164,12 @@ class ReportPageComponent extends AuthComponent { case 'zeroTrust': return (); case 'ransomware': - return (); + return ( + + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 3e7a96311..94a4f331e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -19,7 +19,7 @@ class RansomwareReport extends React.Component {
- +
) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index 340238891..891fdfd87 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -1,25 +1,36 @@ import React from 'react'; import ReactTable from 'react-table'; -import {renderArray} from '../common/RenderArrays'; type Props = { - tableData: [TableRow] + telemetry: object, } type TableRow = { - exploits: [string], - total_attempts: number, - successful_encryptions: number, - hostname: string + hostname: string, + file_path: number, } -const pageSize = 10; - +const PAGE_SIZE = 10; +const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/ +const columns = [ + { + Header: 'Encrypted Files', + columns: [ + {Header: 'Host', id: 'host', accessor: x => x.hostname}, + {Header: 'File Path', id: 'file_path', accessor: x => x.file_path}, + {Header: 'Encryption Algorithm', + id: 'encryption_algorithm', + accessor: () => {return 'Bit Flip'}} + ] + } +]; const FileEncryptionTable = (props: Props) => { - let defaultPageSize = props.tableData.length > pageSize ? pageSize : props.tableData.length; - let showPagination = props.tableData.length > pageSize; + let tableData = processTelemetry(props.telemetry); + let defaultPageSize = tableData.length > PAGE_SIZE ? PAGE_SIZE : tableData.length; + let showPagination = tableData.length > PAGE_SIZE; + return ( <>

@@ -28,7 +39,7 @@ const FileEncryptionTable = (props: Props) => {
@@ -37,30 +48,61 @@ const FileEncryptionTable = (props: Props) => { ); } -const columns = [ - { - Header: 'Ransomware info', - columns: [ - {Header: 'Machine', id: 'machine', accessor: x => x.hostname}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, - {Header: 'Files encrypted', - id: 'files_encrypted', - accessor: x => renderFileEncryptionStats(x.successful_encryptions, x.total_attempts)} - ] - } -]; +function processTelemetry(telemetry): Array { + // Sort ascending so that newer telemetry records overwrite older ones. + sortTelemetry(telemetry); -function renderFileEncryptionStats(successful: number, total: number) { - let textClassName = '' + let latestTelemetry = getLatestTelemetry(telemetry); + let tableData = getDataForTable(latestTelemetry); - if(successful > 0) { - textClassName = 'text-danger' - } else { - textClassName = 'text-dark' - } - - return (

{successful} out of {total}

); + return tableData; } +function sortTelemetry(telemetry): void { + telemetry.objects.sort((a, b) => { + if (a.timestamp > b.timestamp) { + return 1; + } else if (a.timestamp > b.timestamp) { + return -1; + } + + return 0; + }); +} + +function getLatestTelemetry(telemetry) { + let latestTelemetry = {}; + for (let i = 0; i < telemetry.objects.length; i++) { + let monkey = telemetry.objects[i].monkey + + if (! (monkey in latestTelemetry)) { + latestTelemetry[monkey] = {}; + } + + telemetry.objects[i].data.files.forEach((file_encryption_telemetry) => { + latestTelemetry[monkey][file_encryption_telemetry.path] = file_encryption_telemetry.success + }); + } + + return latestTelemetry +} + +function getDataForTable(telemetry): Array { + let tableData = []; + + for (const monkey in telemetry) { + for (const path in telemetry[monkey]) { + if (telemetry[monkey][path]) { + tableData.push({'hostname': parseHostname(monkey), 'file_path': path}); + } + } + } + + return tableData; +} + +function parseHostname(monkey) { + return monkey.match(HOSTNAME_REGEX)[2] +} export default FileEncryptionTable; From 0f84cc19c1fb7973676c85c449f925153b2dc72a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 23 Jul 2021 11:14:55 -0400 Subject: [PATCH 2/6] Island: Remove "encrypted_files_table" from ransomware report endpoint --- .../cc/resources/ransomware_report.py | 2 - .../services/ransomware/ransomware_report.py | 77 ----------- .../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 | 96 -------------- 6 files changed, 409 deletions(-) delete mode 100644 monkey/tests/data_for_tests/mongo_documents/edges.py delete mode 100644 monkey/tests/data_for_tests/mongo_documents/monkeys.py delete mode 100644 monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index df193e693..af86e75a1 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -8,10 +8,8 @@ from monkey_island.cc.services.ransomware import ransomware_report class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - encrypted_files_table = ransomware_report.get_encrypted_files_table() return jsonify( { - "encrypted_files_table": encrypted_files_table, "propagation_stats": ransomware_report.get_propagation_stats(), } ) diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index fb3e69167..0e0f1c299 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,85 +1,8 @@ from typing import Dict, List -from monkey_island.cc.database import mongo from monkey_island.cc.services.reporting.report import ReportService -def get_encrypted_files_table(): - query = [ - {"$match": {"telem_category": "file_encryption"}}, - {"$addFields": {"total_attempts": {"$size": "$data.files"}}}, - { - "$addFields": { - "successful_encryptions": { - "$filter": { - "input": "$data.files", - "as": "files", - "cond": {"$eq": ["$$files.success", True]}, - } - } - } - }, - {"$addFields": {"successful_encryptions": {"$size": "$successful_encryptions"}}}, - { - "$group": { - "_id": { - "monkey_guid": "$monkey_guid", - "successful_encryptions": "$successful_encryptions", - "total_attempts": "$total_attempts", - } - } - }, - {"$replaceRoot": {"newRoot": "$_id"}}, - {"$sort": {"successful_encryptions": -1}}, - { - "$group": { - "_id": {"monkey_guid": "$monkey_guid"}, - "monkey_guid": {"$first": "$monkey_guid"}, - "total_attempts": {"$first": "$total_attempts"}, - "successful_encryptions": {"$first": "$successful_encryptions"}, - } - }, - { - "$lookup": { - "from": "monkey", - "localField": "_id.monkey_guid", - "foreignField": "guid", - "as": "monkey", - } - }, - { - "$project": { - "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "total_attempts": "$total_attempts", - "successful_encryptions": "$successful_encryptions", - } - }, - ] - - monkeys = list(mongo.db.telemetry.aggregate(query)) - exploited_nodes = ReportService.get_exploited() - for monkey in monkeys: - monkey["exploits"] = _get_monkey_origin_exploits( - monkey["monkey"]["hostname"], exploited_nodes - ) - monkey["hostname"] = monkey["monkey"]["hostname"] - del monkey["monkey"] - del monkey["_id"] - return monkeys - - -def _get_monkey_origin_exploits(monkey_hostname, exploited_nodes): - origin_exploits = [ - exploited_node["exploits"] - for exploited_node in exploited_nodes - if exploited_node["label"] == monkey_hostname - ] - if origin_exploits: - return origin_exploits[0] - else: - return ["Manual execution"] - - 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 deleted file mode 100644 index 25c12f29f..000000000 --- a/monkey/tests/data_for_tests/mongo_documents/edges.py +++ /dev/null @@ -1,125 +0,0 @@ -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 deleted file mode 100644 index c35c1d124..000000000 --- a/monkey/tests/data_for_tests/mongo_documents/monkeys.py +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index ff1a6914e..000000000 --- a/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py +++ /dev/null @@ -1,59 +0,0 @@ -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 3f7ce4c57..4c586aa81 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,105 +1,9 @@ -import mongomock import pytest -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, -) from monkey_island.cc.services.ransomware import ransomware_report -from monkey_island.cc.services.ransomware.ransomware_report import get_encrypted_files_table from monkey_island.cc.services.reporting.report import ReportService -@pytest.fixture -def fake_mongo(monkeypatch): - mongo = mongomock.MongoClient() - monkeypatch.setattr("monkey_island.cc.services.ransomware.ransomware_report.mongo", mongo) - return mongo - - -@pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) - fake_mongo.db.edge.insert_one(EDGE_SCANNED) - fake_mongo.db.telemetry.insert_one(ENCRYPTED) - fake_mongo.db.telemetry.insert_one(ENCRYPTED_2) - fake_mongo.db.telemetry.insert_one(ENCRYPTION_ERROR) - fake_mongo.db.telemetry.insert_one(ENCRYPTION_ONE_FILE) - - monkeypatch.setattr( - ReportService, - "get_exploited", - lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], - ) - - results = get_encrypted_files_table() - - assert results == [ - { - "hostname": "test-pc-2", - "exploits": ["Manual execution"], - "successful_encryptions": 3, - "total_attempts": 3, - }, - { - "hostname": "WinDev2010Eval", - "exploits": ["SMB Exploiter"], - "successful_encryptions": 1, - "total_attempts": 1, - }, - ] - - -@pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) - fake_mongo.db.edge.insert_one(EDGE_SCANNED) - fake_mongo.db.telemetry.insert_one(ENCRYPTION_ERROR) - - monkeypatch.setattr( - ReportService, - "get_exploited", - lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], - ) - - results = get_encrypted_files_table() - - assert results == [ - { - "hostname": "test-pc-2", - "exploits": ["Manual execution"], - "successful_encryptions": 0, - "total_attempts": 1, - } - ] - - -@pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table__no_telemetries(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) - fake_mongo.db.edge.insert_one(EDGE_SCANNED) - - monkeypatch.setattr( - ReportService, - "get_exploited", - lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], - ) - - results = get_encrypted_files_table() - - assert results == [] - - @pytest.fixture def patch_report_service_for_stats(monkeypatch): TEST_SCANNED_RESULTS = [{}, {}, {}, {}] From 9bcce8d4b50171de58f9c7fda3ee1631d4fa845c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 23 Jul 2021 11:41:07 -0400 Subject: [PATCH 3/6] UI: Add attack section to RansomwareReport --- .../report-components/RansomwareReport.js | 4 +- .../report-components/ransomware/Attack.tsx | 87 +++++++++++++++++++ .../ransomware/FileEncryptionTable.tsx | 67 +------------- 3 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 94a4f331e..25bc3876d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -2,7 +2,7 @@ import React from 'react'; import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; -import FileEncryptionTable from './ransomware/FileEncryptionTable'; +import Attack from './ransomware/Attack'; import LateralMovement from './ransomware/LateralMovement'; import '../../styles/pages/report/RansomwareReport.scss'; @@ -19,7 +19,7 @@ class RansomwareReport extends React.Component {
- +
) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx new file mode 100644 index 000000000..d2e2d7e38 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx @@ -0,0 +1,87 @@ +import React, {ReactElement} from 'react'; +import {FileEncryptionTable, TableRow} from './FileEncryptionTable'; +import NumberedReportSection from './NumberedReportSection'; + +const ATTACK_DESCRIPTION = 'After the attacker or malware has propagated through your network, \ + your data is at risk on any machine the attacker can access. It can be \ + encrypted and held for ransomware, exfiltrated, or manipulated in \ + whatever way the attacker chooses.' +const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/; + +function Attack({telemetry}: {telemetry: object}): ReactElement { + let tableData = processTelemetry(telemetry); + let body = ( + <> +

Infection Monkey has encrypted {tableData.length} files on your network:

+ + + ); + + return ( + + ); +} + +function processTelemetry(telemetry): Array { + // Sort ascending so that newer telemetry records overwrite older ones. + sortTelemetry(telemetry); + + let latestTelemetry = getLatestTelemetry(telemetry); + let tableData = getDataForTable(latestTelemetry); + + return tableData; +} + +function sortTelemetry(telemetry): void { + telemetry.objects.sort((a, b) => { + if (a.timestamp > b.timestamp) { + return 1; + } else if (a.timestamp < b.timestamp) { + return -1; + } + + return 0; + }); +} + +function getLatestTelemetry(telemetry) { + let latestTelemetry = {}; + for (let i = 0; i < telemetry.objects.length; i++) { + let monkey = telemetry.objects[i].monkey + + if (! (monkey in latestTelemetry)) { + latestTelemetry[monkey] = {}; + } + + telemetry.objects[i].data.files.forEach((file_encryption_telemetry) => { + latestTelemetry[monkey][file_encryption_telemetry.path] = file_encryption_telemetry.success + }); + } + + return latestTelemetry; +} + +function getDataForTable(telemetry): Array { + let tableData = []; + + for (const monkey in telemetry) { + for (const path in telemetry[monkey]) { + if (telemetry[monkey][path]) { + tableData.push({'hostname': parseHostname(monkey), 'file_path': path}); + } + } + } + + return tableData; +} + +function parseHostname(monkey: string): string { + return monkey.match(HOSTNAME_REGEX)[2]; +} + +export default Attack; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index 891fdfd87..0b4406ab6 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -2,17 +2,12 @@ import React from 'react'; import ReactTable from 'react-table'; -type Props = { - telemetry: object, -} - type TableRow = { hostname: string, file_path: number, } const PAGE_SIZE = 10; -const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/ const columns = [ { Header: 'Encrypted Files', @@ -26,8 +21,7 @@ const columns = [ } ]; -const FileEncryptionTable = (props: Props) => { - let tableData = processTelemetry(props.telemetry); +const FileEncryptionTable = ({tableData}: {tableData: Array}) => { let defaultPageSize = tableData.length > PAGE_SIZE ? PAGE_SIZE : tableData.length; let showPagination = tableData.length > PAGE_SIZE; @@ -48,61 +42,4 @@ const FileEncryptionTable = (props: Props) => { ); } -function processTelemetry(telemetry): Array { - // Sort ascending so that newer telemetry records overwrite older ones. - sortTelemetry(telemetry); - - let latestTelemetry = getLatestTelemetry(telemetry); - let tableData = getDataForTable(latestTelemetry); - - return tableData; -} - -function sortTelemetry(telemetry): void { - telemetry.objects.sort((a, b) => { - if (a.timestamp > b.timestamp) { - return 1; - } else if (a.timestamp > b.timestamp) { - return -1; - } - - return 0; - }); -} - -function getLatestTelemetry(telemetry) { - let latestTelemetry = {}; - for (let i = 0; i < telemetry.objects.length; i++) { - let monkey = telemetry.objects[i].monkey - - if (! (monkey in latestTelemetry)) { - latestTelemetry[monkey] = {}; - } - - telemetry.objects[i].data.files.forEach((file_encryption_telemetry) => { - latestTelemetry[monkey][file_encryption_telemetry.path] = file_encryption_telemetry.success - }); - } - - return latestTelemetry -} - -function getDataForTable(telemetry): Array { - let tableData = []; - - for (const monkey in telemetry) { - for (const path in telemetry[monkey]) { - if (telemetry[monkey][path]) { - tableData.push({'hostname': parseHostname(monkey), 'file_path': path}); - } - } - } - - return tableData; -} - -function parseHostname(monkey) { - return monkey.match(HOSTNAME_REGEX)[2] -} - -export default FileEncryptionTable; +export {FileEncryptionTable, TableRow}; From af9caee85fe0e806cd788c3d183ad3629e3fe743 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Jul 2021 08:32:56 -0400 Subject: [PATCH 4/6] UI: Rename Attack -> AttackSection --- .../ui/src/components/report-components/RansomwareReport.js | 4 ++-- .../ransomware/{Attack.tsx => AttackSection.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/ransomware/{Attack.tsx => AttackSection.tsx} (95%) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 25bc3876d..2e437fab2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -2,7 +2,7 @@ import React from 'react'; import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; -import Attack from './ransomware/Attack'; +import AttackSection from './ransomware/AttackSection'; import LateralMovement from './ransomware/LateralMovement'; import '../../styles/pages/report/RansomwareReport.scss'; @@ -19,7 +19,7 @@ class RansomwareReport extends React.Component {
- +
) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx similarity index 95% rename from monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx rename to monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx index d2e2d7e38..bc20207c2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/Attack.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx @@ -8,7 +8,7 @@ const ATTACK_DESCRIPTION = 'After the attacker or malware has propagated through whatever way the attacker chooses.' const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/; -function Attack({telemetry}: {telemetry: object}): ReactElement { +function AttackSection({telemetry}: {telemetry: object}): ReactElement { let tableData = processTelemetry(telemetry); let body = ( <> @@ -84,4 +84,4 @@ function parseHostname(monkey: string): string { return monkey.match(HOSTNAME_REGEX)[2]; } -export default Attack; +export default AttackSection; From 09d7630a472b39f94a0c0f986cd0b75d52927e64 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Jul 2021 09:28:55 -0400 Subject: [PATCH 5/6] UI: Show loading icon while fetching file encryption telemetry --- .../cc/ui/src/components/pages/ReportPage.js | 9 ----- .../report-components/RansomwareReport.js | 2 +- .../ransomware/AttackSection.tsx | 35 +++++++++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b2d1446b3..65707574e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -22,7 +22,6 @@ class ReportPageComponent extends AuthComponent { attackReport: {}, zeroTrustReport: {}, ransomwareReport: {}, - ransomwareTelemetry: {}, allMonkeysAreDead: false, runStarted: true, selectedSection: ReportPageComponent.selectReport(this.sections), @@ -68,13 +67,6 @@ class ReportPageComponent extends AuthComponent { ransomwareReport: res }); }); - this.authFetch('/api/telemetry?telem_category=file_encryption') - .then(res => res.json()) - .then(res => { - this.setState({ - ransomwareTelemetry: res - }); - }); } } @@ -167,7 +159,6 @@ class ReportPageComponent extends AuthComponent { return ( ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 2e437fab2..f4cced8c3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -19,7 +19,7 @@ class RansomwareReport extends React.Component {
- +
) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx index bc20207c2..53f2e6a15 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx @@ -1,6 +1,8 @@ -import React, {ReactElement} from 'react'; +import React, {ReactElement, ReactFragment, useEffect, useState} from 'react'; +import IslandHttpClient from '../../IslandHttpClient'; import {FileEncryptionTable, TableRow} from './FileEncryptionTable'; import NumberedReportSection from './NumberedReportSection'; +import LoadingIcon from '../../ui-components/LoadingIcon'; const ATTACK_DESCRIPTION = 'After the attacker or malware has propagated through your network, \ your data is at risk on any machine the attacker can access. It can be \ @@ -8,25 +10,38 @@ const ATTACK_DESCRIPTION = 'After the attacker or malware has propagated through whatever way the attacker chooses.' const HOSTNAME_REGEX = /^(.* - )?(\S+) :.*$/; -function AttackSection({telemetry}: {telemetry: object}): ReactElement { - let tableData = processTelemetry(telemetry); - let body = ( - <> -

Infection Monkey has encrypted {tableData.length} files on your network:

- - - ); +function AttackSection(): ReactElement { + const [tableData, setTableData] = useState(null); + + useEffect(() => { + IslandHttpClient.get('/api/telemetry?telem_category=file_encryption') + .then(resp => setTableData(processTelemetry(resp.body))); + }, []); + + + if (tableData == null) { + return + } return ( ); } +function getBody(tableData): ReactFragment { + return ( + <> +

Infection Monkey has encrypted {tableData.length} files on your network:

+ + + ); +} + function processTelemetry(telemetry): Array { // Sort ascending so that newer telemetry records overwrite older ones. sortTelemetry(telemetry); From 5531c30d30ee81b6b785ddfdc6e4f92ecaa67e4a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Jul 2021 10:04:43 -0400 Subject: [PATCH 6/6] UI: Don't render ransomware encryption table if no files were encrypted --- .../components/report-components/ransomware/AttackSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx index 53f2e6a15..69dc76c08 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/AttackSection.tsx @@ -36,8 +36,8 @@ function AttackSection(): ReactElement { function getBody(tableData): ReactFragment { return ( <> -

Infection Monkey has encrypted {tableData.length} files on your network:

- +

Infection Monkey has encrypted {tableData.length} files on your network.

+ {(tableData.length > 0) && } ); }