diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 9a40e1728..a86db9042 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,5 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 +from monkey_island.cc.services.attack.technique_reports import T1145 from monkey_island.cc.services.attack.attack_telem import AttackTelemService from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -16,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1003': T1003.T1003, 'T1059': T1059.T1059, 'T1086': T1086.T1086, - 'T1082': T1082.T1082} + 'T1082': T1082.T1082, + 'T1145': T1145.T1145} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 4d979a682..00d3e9536 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -67,6 +67,16 @@ SCHEMA = { "information, normally in the form of a hash or a clear text password, " "from the operating system and software.", "depends_on": ["T1078"] + }, + "T1145": { + "title": "T1145 Private keys", + "type": "bool", + "value": True, + "necessary": False, + "description": "Adversaries may gather private keys from compromised systems for use in " + "authenticating to Remote Services like SSH or for use in decrypting " + "other collected files such as email.", + "depends_on": ["T1110", "T1210"] } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 040b05be2..7fe5ac90f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -35,11 +35,15 @@ class T1110(AttackTechnique): result['successful_creds'].append(T1110.parse_creds(attempt)) if succeeded: - data = {'message': T1110.used_msg, 'status': ScanStatus.USED.name} + data = T1110.get_message_and_status(T1110, ScanStatus.USED) elif attempts: - data = {'message': T1110.scanned_msg, 'status': ScanStatus.SCANNED.name} + data = T1110.get_message_and_status(T1110, ScanStatus.SCANNED) else: - data = {'message': T1110.unscanned_msg, 'status': ScanStatus.UNSCANNED.name} + 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 @@ -51,21 +55,39 @@ class T1110(AttackTechnique): :return: string with username and used password/hash """ username = attempt['user'] - if attempt['lm_hash']: - return '%s ; LM hash %s ...' % (username, encryptor.dec(attempt['lm_hash'])[0:5]) - if attempt['ntlm_hash']: - return '%s ; NTLM hash %s ...' % (username, encryptor.dec(attempt['ntlm_hash'])[0:20]) - if attempt['ssh_key']: - return '%s ; SSH key %s ...' % (username, encryptor.dec(attempt['ssh_key'])[0:15]) - if attempt['password']: - return '%s : %s' % (username, T1110.obfuscate_password(encryptor.dec(attempt['password']))) + 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 obfuscate_password(password, plain_chars=3): + def censor_password(password, plain_chars=3, secret_chars=5): """ - Obfuscates password by changing characters to * + 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**** """ - return password[0:plain_chars] + '*' * (len(password) - plain_chars) + 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/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py new file mode 100644 index 000000000..29b4e97c0 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -0,0 +1,31 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus + +__author__ = "VakarisZ" + + +class T1145(AttackTechnique): + tech_id = "T1145" + unscanned_msg = "Monkey didn't find any shh keys." + scanned_msg = "" + used_msg = "Monkey found ssh keys on machines in the network." + + # Gets data about ssh keys found + query = [{'$match': {'telem_type': 'system_info_collection', + 'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}}, + {'$project': {'_id': 0, + 'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, + 'ssh_info': '$data.ssh_info'}}] + + @staticmethod + def get_report_data(): + ssh_info = list(mongo.db.telemetry.aggregate(T1145.query)) + + if ssh_info: + data = T1145.get_base_data_by_status(T1145, ScanStatus.USED) + else: + data = T1145.get_base_data_by_status(T1145, ScanStatus.UNSCANNED) + + data.update({'ssh_info': ssh_info}) + return data 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 0346a1857..1e152fd3f 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,15 @@ class AttackTechnique(object): else: return ScanStatus.UNSCANNED + @staticmethod + def get_message_and_status(technique, status): + if status == ScanStatus.UNSCANNED: + return {'message': technique.unscanned_msg, 'status': ScanStatus.UNSCANNED.name} + elif status == ScanStatus.SCANNED: + return {'message': technique.scanned_msg, 'status': ScanStatus.SCANNED.name} + else: + return {'message': technique.used_msg, 'status': ScanStatus.USED.name} + @staticmethod def technique_title(technique): """ @@ -86,3 +95,9 @@ class AttackTechnique(object): else: data.update({'message': technique.used_msg}) return data + + @staticmethod + def get_base_data_by_status(technique, status): + data = technique.get_message_and_status(technique, status) + data.update({'title': technique.technique_title(technique.tech_id)}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 90d4f120f..bc66fa8e7 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -54,7 +54,7 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1145"] }, { "type": "string", 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 b15bba693..9c976461c 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 @@ -4,4 +4,12 @@ export function RenderMachine(val){ return ( {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ) -}; +} + +export function renderMachineFromSystemData(data) { + let machineStr = data['hostname'] + " ( "; + data['ips'].forEach(function(ipInfo){ + machineStr += ipInfo['addr'] + " "; + }); + return machineStr + ")" +} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js index 3b3f1df7c..739c69bc5 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js @@ -1,6 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" class T1082 extends React.Component { @@ -9,14 +10,6 @@ class T1082 extends React.Component { super(props); } - static renderMachineString(data){ - let machineStr = data['hostname'] + " ( "; - data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; - }); - return machineStr + ")" - } - static renderCollections(collections){ let output = []; collections.forEach(function(collection){ @@ -30,7 +23,7 @@ class T1082 extends React.Component { static getSystemInfoColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => T1082.renderMachineString(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, {Header: 'Gathered info', id: 'info', accessor: x => T1082.renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, ] }])}; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js new file mode 100644 index 000000000..c34f436e2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js @@ -0,0 +1,53 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" + + +class T1145 extends React.Component { + + constructor(props) { + super(props); + } + + static renderSSHKeys(keys){ + let output = []; + keys.forEach(function(keyInfo){ + output.push(
+ SSH key pair used by {keyInfo['name']} user found in {keyInfo['home_dir']}
) + }); + return (
{output}
); + } + + static getKeysInfoColumns() { + return ([{ + columns: [ + {Header: 'Machine', + id: 'machine', + accessor: x => renderMachineFromSystemData(x.machine), + style: { 'whiteSpace': 'unset' }}, + {Header: 'Keys found', + id: 'keys', + accessor: x => T1145.renderSSHKeys(x.ssh_info), + style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === 'USED' ? + : ""} +
+ ); + } +} + +export default T1145; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 6543ec389..348510175 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -13,6 +13,7 @@ import T1003 from "../attack/techniques/T1003"; import T1059 from "../attack/techniques/T1059"; import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; +import T1145 from "../attack/techniques/T1145"; const tech_components = { 'T1210': T1210, @@ -22,7 +23,8 @@ const tech_components = { 'T1003': T1003, 'T1059': T1059, 'T1086': T1086, - 'T1082': T1082 + 'T1082': T1082, + 'T1145': T1145 }; const classNames = require('classnames');