Merge pull request #1718 from guardicore/1697-process-list-collector-pba

Make process list collection a PBA
This commit is contained in:
Mike Salvatore 2022-02-17 07:04:21 -05:00 committed by GitHub
commit 5a4b508f54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 132 additions and 174 deletions

View File

@ -17,6 +17,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- The setup procedure for custom server_config.json files to be simpler. #1576
- The order and content of Monkey Island's initialization logging to give
clearer instructions to the user and avoid confusion. #1684
- The process list collection system info collector to now be a post-breach action. #1697
### Removed
- VSFTPD exploiter. #1533

View File

@ -14,7 +14,7 @@ An exploit is a sequence of commands that takes advantage of a security vulnerab
### Do I need a new Exploit?
If all you want to do is execute a shell command, configure the required commands in the Monkey Island's post-breach action (PBA) configuration section or [add a new PBA](../adding-post-breach-actions/). If you would like the Infection Monkey agent to collect specific information, [add a new System Info Collector](../adding-system-info-collectors/).
If all you want to do is execute a shell command, configure the required commands in the Monkey Island's post-breach action (PBA) configuration section or [add a new PBA](../adding-post-breach-actions/).
However, if you have your eye on an interesting CVE that you would like the Infection Monkey to support, you must add a new exploit. Keep reading to learn how to add a new exploit.

View File

@ -1,101 +0,0 @@
---
title: "Adding System Info Collectors"
date: 2020-06-09T11:03:42+03:00
draft: false
tags: ["contribute"]
weight: 80
---
## What does this guide cover?
This guide will show you how to create a new _System Info Collector_ for the Infection Monkey. System Info Collectors are modules that each of the Infection Monkey agents runs that collect specific information and send it back to the Monkey Island as part of the System Info Telemetry.
### Do I need a new System Info Collector?
If all you want to do is execute a shell command, then there's no need to add a new System Info Collector - just configure the required commands in the Monkey Island's post-breach action (PBA) section! Also, if there is a relevant System Info Collector and you only need to add more information to it, simply expand the existing one. Otherwise, you must add a new System Info Collector.
## How to add a new System Info Collector
### Modify the Infection Monkey Agent
#### Framework
1. Create your new System Info Collector in the following directory: `monkey/infection_monkey/system_info/collectors` by first creating a new file with the name of your System Info Collector.
2. In that file, create a class that inherits from the `SystemInfoCollector` class:
```py
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
class MyNewCollector(SystemInfoCollector):
```
3. Set the System Info Collector name in the constructor, like so:
```py
class MyNewCollector(SystemInfoCollector):
def __init__(self):
super(MyNewCollector, self).__init__(name="MyNewCollector")
```
#### Implementation
Override the `collect` method with your own implementation. See the `process_list_collector.py` System Info Collector for reference. You can log during collection as well.
### Modify the Monkey Island
#### Configuration
##### Definitions
You'll need to add your Sytem Info Collector to the `monkey_island/cc/services/config_schema.py` file, under `definitions/system_info_collectors_classes/anyOf`, like so:
```json
"system_info_collectors_classes": {
"title": "System Information Collectors",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"HostnameCollector"
],
"title": "Which Environment this machine is on (on prem/cloud)",
"attack_techniques": []
},
{ <=================================
"type": "string", <=================================
"enum": [ <=================================
"MyNewCollector" <=================================
], <=================================
"title": "My new title", <=================================
"attack_techniques": [] <=================================
},
],
},
```
##### properties
Also, you can add the System Info Collector to be used by default by adding it to the `default` key under `properties/monkey/system_info/system_info_collectors_classes`:
```json
"system_info_collectors_classes": {
"title": "System info collectors",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/system_info_collectors_classes"
},
"default": [
"HostnameCollector",
"MyNewCollector" <=================================
],
"description": "Determines which system information collectors will collect information."
},
```
#### Telemetry processing
1. Add a process function under `monkey_island/cc/telemetry/processing/system_info_collectors/{DATA_NAME_HERE}.py`. The function should parse the System Info Collector's result. See `processing/system_info_collectors/environment.py` for example.
2. Add that function to `SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS` under `monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py`.

View File

@ -17,7 +17,6 @@ class SmbMimikatz(ConfigTemplate):
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [445],
"monkey.system_info.system_info_collector_classes": [
"ProcessListCollector",
"MimikatzCollector",
],
}

View File

@ -16,7 +16,6 @@ class WmiMimikatz(ConfigTemplate):
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [135],
"monkey.system_info.system_info_collector_classes": [
"ProcessListCollector",
"MimikatzCollector",
],
}

View File

@ -9,3 +9,4 @@ POST_BREACH_TIMESTOMPING = "Modify files' timestamps"
POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC = "Signed script proxy execution"
POST_BREACH_ACCOUNT_DISCOVERY = "Account discovery"
POST_BREACH_CLEAR_CMD_HISTORY = "Clear command history"
POST_BREACH_PROCESS_LIST_COLLECTION = "Collect running processes"

View File

@ -1,2 +1 @@
PROCESS_LIST_COLLECTOR = "ProcessListCollector"
MIMIKATZ_COLLECTOR = "MimikatzCollector"

View File

@ -176,6 +176,9 @@ class AutomatedMaster(IMaster):
)
def _run_pba(self, pba: Tuple[str, Dict]):
# TODO: This is the class's name right now. We need `display_name` (see the
# ProcessListCollection PBA). This is shown in the Security report as the PBA
# name and is checked against in the T1082's mongo query in the ATT&CK report.
name = pba[0]
options = pba[1]

View File

@ -2,31 +2,36 @@ import logging
import psutil
from common.common_consts.system_info_collectors_names import PROCESS_LIST_COLLECTOR
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
from common.common_consts.post_breach_consts import POST_BREACH_PROCESS_LIST_COLLECTION
from infection_monkey.post_breach.pba import PBA
logger = logging.getLogger(__name__)
# Linux doesn't have WindowsError
applicable_exceptions = psutil.AccessDenied
try:
WindowsError
applicable_exceptions = (psutil.AccessDenied, WindowsError)
except NameError:
# noinspection PyShadowingBuiltins
WindowsError = psutil.AccessDenied
pass
class ProcessListCollector(SystemInfoCollector):
class ProcessListCollection(PBA):
# TODO: (?) Move all PBA consts into their classes
display_name = POST_BREACH_PROCESS_LIST_COLLECTION
def __init__(self):
super().__init__(name=PROCESS_LIST_COLLECTOR)
super().__init__(POST_BREACH_PROCESS_LIST_COLLECTION)
def collect(self) -> dict:
def run(self):
"""
Adds process information from the host to the system information.
Collects process information from the host.
Currently lists process name, ID, parent ID, command line
and the full image path of each process.
"""
logger.debug("Reading process list")
processes = {}
success_state = False
for process in psutil.process_iter():
try:
processes[process.pid] = {
@ -36,10 +41,10 @@ class ProcessListCollector(SystemInfoCollector):
"cmdline": " ".join(process.cmdline()),
"full_image_path": process.exe(),
}
except (psutil.AccessDenied, WindowsError):
# we may be running as non root and some processes are impossible to acquire in
# Windows/Linux.
# In this case we'll just add what we know.
success_state = True
except applicable_exceptions:
# We may be running as non root and some processes are impossible to acquire in
# Windows/Linux. In this case, we'll just add what we know.
processes[process.pid] = {
"name": "null",
"pid": process.pid,
@ -49,4 +54,4 @@ class ProcessListCollector(SystemInfoCollector):
}
continue
return {"process_list": processes}
return self.command, (processes, success_state)

View File

@ -12,6 +12,7 @@ from infection_monkey.i_puppet import (
PortStatus,
PostBreachData,
)
from infection_monkey.post_breach.actions.collect_processes_list import ProcessListCollection
DOT_1 = "10.0.0.1"
DOT_2 = "10.0.0.2"

View File

@ -1,3 +1,4 @@
from common.common_consts.post_breach_consts import POST_BREACH_PROCESS_LIST_COLLECTION
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
@ -7,16 +8,18 @@ class T1082(AttackTechnique):
tech_id = "T1082"
relevant_systems = ["Linux", "Windows"]
unscanned_msg = "Monkey didn't gather any system info on the network."
scanned_msg = ""
scanned_msg = "Monkey tried gathering system info on the network but failed."
used_msg = "Monkey gathered system info from machines in the network."
# TODO: Remove the second item from this list after the TODO in `_run_pba()` in
# `automated_master.py` is resolved.
pba_names = [POST_BREACH_PROCESS_LIST_COLLECTION, "ProcessListCollection"]
query = [
query_for_system_info_collectors = [
{"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}},
{
"$project": {
"machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"},
"aws": "$data.aws",
"process_list": "$data.process_list",
"ssh_info": "$data.ssh_info",
"azure_info": "$data.Azure",
}
@ -30,15 +33,6 @@ class T1082(AttackTechnique):
"used": {"$and": [{"$gt": ["$aws", {}]}]},
"name": {"$literal": "Amazon Web Services info"},
},
{
"used": {
"$and": [
{"$ifNull": ["$process_list", False]},
{"$gt": ["$process_list", {}]},
]
},
"name": {"$literal": "Running process list"},
},
{
"used": {
"$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}]
@ -62,19 +56,64 @@ class T1082(AttackTechnique):
{"$replaceRoot": {"newRoot": "$_id"}},
]
query_for_running_processes_list = [
{
"$match": {
"$and": [
{"telem_category": "post_breach"},
{"$or": [{"data.name": pba_name} for pba_name in pba_names]},
{"$or": [{"data.os": os} for os in relevant_systems]},
]
}
},
{
"$project": {
"_id": 0,
"machine": {
"hostname": {"$arrayElemAt": ["$data.hostname", 0]},
"ips": [{"$arrayElemAt": ["$data.ip", 0]}],
},
"collections": [
{
"used": {"$arrayElemAt": [{"$arrayElemAt": ["$data.result", 0]}, 1]},
"name": {"$literal": "List of running processes"},
}
],
}
},
]
@staticmethod
def get_report_data():
def get_technique_status_and_data():
system_info = list(mongo.db.telemetry.aggregate(T1082.query))
if system_info:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, system_info)
system_info_data = list(
mongo.db.telemetry.aggregate(T1082.query_for_system_info_collectors)
)
system_info_status = (
ScanStatus.USED.value if system_info_data else ScanStatus.UNSCANNED.value
)
status, system_info = get_technique_status_and_data()
pba_data = list(mongo.db.telemetry.aggregate(T1082.query_for_running_processes_list))
successful_PBAs = mongo.db.telemetry.count(
{
"$and": [
{"$or": [{"data.name": pba_name} for pba_name in T1082.pba_names]},
{"$or": [{"data.os": os} for os in T1082.relevant_systems]},
{"data.result.1": True},
]
}
)
pba_status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
technique_data = system_info_data + pba_data
# ScanStatus values are in order of precedence; used > scanned > unscanned
technique_status = max(system_info_status, pba_status)
return (technique_status, technique_data)
status, technique_data = get_technique_status_and_data()
data = {"title": T1082.technique_title()}
data.update({"system_info": system_info})
data.update({"technique_data": technique_data})
data.update(T1082.get_mitigation_by_status(status))
data.update(T1082.get_message_and_status(status))

View File

@ -94,5 +94,13 @@ POST_BREACH_ACTIONS = {
"info": "Attempts to clear the command history.",
"attack_techniques": ["T1146"],
},
{
"type": "string",
"enum": ["ProcessListCollection"],
"title": "Process List Collector",
"safe": True,
"info": "Collects a list of running processes on the machine.",
"attack_techniques": ["T1082"],
},
],
}

View File

@ -1,6 +1,5 @@
from common.common_consts.system_info_collectors_names import (
MIMIKATZ_COLLECTOR,
PROCESS_LIST_COLLECTOR,
)
SYSTEM_INFO_COLLECTOR_CLASSES = {
@ -16,13 +15,5 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
"info": "Collects credentials from Windows credential manager.",
"attack_techniques": ["T1003", "T1005"],
},
{
"type": "string",
"enum": [PROCESS_LIST_COLLECTOR],
"title": "Process List Collector",
"safe": True,
"info": "Collects a list of running processes on the machine.",
"attack_techniques": ["T1082"],
},
],
}

View File

@ -1,6 +1,5 @@
from common.common_consts.system_info_collectors_names import (
MIMIKATZ_COLLECTOR,
PROCESS_LIST_COLLECTOR,
)
MONKEY = {
@ -71,6 +70,7 @@ MONKEY = {
"ScheduleJobs",
"Timestomping",
"AccountDiscovery",
"ProcessListCollection",
],
},
},
@ -85,7 +85,6 @@ MONKEY = {
"uniqueItems": True,
"items": {"$ref": "#/definitions/system_info_collector_classes"},
"default": [
PROCESS_LIST_COLLECTOR,
MIMIKATZ_COLLECTOR,
],
},

View File

@ -1,8 +1,14 @@
import copy
from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER
from common.common_consts.post_breach_consts import (
POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER,
POST_BREACH_PROCESS_LIST_COLLECTION,
)
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import (
check_antivirus_existence,
)
from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_backdoor_user import (
check_new_user_communication,
)
@ -10,15 +16,22 @@ from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_backdo
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
def process_communicate_as_backdoor_user_telemetry(telemetry_json):
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
def process_communicate_as_backdoor_user_telemetry(telemetry_json, current_monkey):
message = telemetry_json["data"]["result"][0]
success = telemetry_json["data"]["result"][1]
check_new_user_communication(current_monkey, success, message)
def process_process_list_collection_telemetry(telemetry_json, current_monkey):
check_antivirus_existence(telemetry_json, current_monkey)
POST_BREACH_TELEMETRY_PROCESSING_FUNCS = {
POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER: process_communicate_as_backdoor_user_telemetry,
# TODO: Remove the line below and un-comment the next one after the TODO in `_run_pba()` in
# `automated_master.py` is resolved.
"ProcessListCollection": process_process_list_collection_telemetry,
# POST_BREACH_PROCESS_LIST_COLLECTION: process_process_list_collection_telemetry,
}
@ -44,7 +57,10 @@ def process_post_breach_telemetry(telemetry_json):
post_breach_action_name = telemetry_json["data"]["name"]
if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json)
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"])
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](
telemetry_json, current_monkey
)
telemetry_json["data"] = convert_telem_data_to_list(telemetry_json["data"])

View File

@ -1,16 +1,11 @@
import logging
import typing
from common.common_consts.system_info_collectors_names import PROCESS_LIST_COLLECTOR
from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import (
check_antivirus_existence,
)
logger = logging.getLogger(__name__)
SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {
PROCESS_LIST_COLLECTOR: [check_antivirus_existence],
}
SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {}
class SystemInfoTelemetryDispatcher(object):

View File

@ -1,7 +1,6 @@
import json
import common.common_consts.zero_trust_consts as zero_trust_consts
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import (
ANTI_VIRUS_KNOWN_PROCESS_NAMES,
@ -11,9 +10,7 @@ from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_serv
)
def check_antivirus_existence(process_list_json, monkey_guid):
current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
def check_antivirus_existence(telemetry_json, current_monkey):
process_list_event = Event.create_event(
title="Process list",
message="Monkey on {} scanned the process list".format(current_monkey.hostname),
@ -21,7 +18,7 @@ def check_antivirus_existence(process_list_json, monkey_guid):
)
events = [process_list_event]
av_processes = filter_av_processes(process_list_json["process_list"])
av_processes = filter_av_processes(telemetry_json["data"]["result"][0])
for process in av_processes:
events.append(

View File

@ -37,9 +37,9 @@ class T1082 extends React.Component {
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1082.getSystemInfoColumns()}
data={this.props.data.system_info}
data={this.props.data.technique_data}
showPagination={false}
defaultPageSize={this.props.data.system_info.length}
defaultPageSize={this.props.data.technique_data.length}
/> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div>

View File

@ -5,6 +5,10 @@ export default function parsePbaResults(results) {
const SHELL_STARTUP_NAME = 'Modify shell startup file';
const CMD_HISTORY_NAME = 'Clear command history';
// TODO: Remove line 10 and un-comment line 11 after the TODO in `_run_pba()` in
// `automated_master.py` is resolved.
const PROCESS_LIST_COLLECTION = 'ProcessListCollection';
// const PROCESS_LIST_COLLECTION = 'Process list collection';
const multipleResultsPbas = [SHELL_STARTUP_NAME, CMD_HISTORY_NAME]
@ -41,10 +45,17 @@ function aggregateMultipleResultsPba(results) {
}
}
function modifyProcessListCollectionResult(result) {
result[0] = "Found " + Object.keys(result[0]).length.toString() + " running processes";
}
// check for pbas with multiple results and aggregate their results
for (let i = 0; i < results.length; i++)
for (let i = 0; i < results.length; i++) {
if (multipleResultsPbas.includes(results[i].name))
aggregateResults(results[i]);
if (results[i].name === PROCESS_LIST_COLLECTION)
modifyProcessListCollectionResult(results[i].result);
}
// if no modifications were made to the results, i.e. if no pbas had mutiple results, return `results` as it is
let noResultsModifications = true;

View File

@ -104,7 +104,6 @@
}
},
"system_info_collector_classes": [
"ProcessListCollector",
"MimikatzCollector"
]
}

View File

@ -101,7 +101,6 @@
"smb_service_name": "InfectionMonkey",
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
"system_info_collector_classes": [
"ProcessListCollector",
"MimikatzCollector"
],
"tcp_scan_timeout": 3000,

View File

@ -146,9 +146,7 @@
},
"system_info": {
"system_info_collector_classes": [
"environmentcollector",
"hostnamecollector",
"processlistcollector",
"mimikatzcollector"
]
}

View File

@ -98,7 +98,6 @@ Timestomping # unused class (monkey/infection_monkey/post_breach/actions/timest
SignedScriptProxyExecution # unused class (monkey/infection_monkey/post_breach/actions/use_signed_scripts.py:15)
EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collectors/environment_collector.py:19)
HostnameCollector # unused class (monkey/infection_monkey/system_info/collectors/hostname_collector.py:10)
ProcessListCollector # unused class (monkey/infection_monkey/system_info/collectors/process_list_collector.py:18)
_.coinit_flags # unused attribute (monkey/infection_monkey/system_info/windows_info_collector.py:11)
_.representations # unused attribute (monkey/monkey_island/cc/app.py:180)
_.log_message # unused method (monkey/infection_monkey/transport/http.py:188)