forked from p15670423/monkey
Island, UI: implement the endpoint for stopping all monkeys, change the UI to call this endpoint and send a timestamp of button press
This commit is contained in:
parent
9d7c7073c3
commit
4fdd3370ca
|
@ -19,9 +19,12 @@ class ControlChannel(IControlChannel):
|
||||||
self._control_channel_server = server
|
self._control_channel_server = server
|
||||||
|
|
||||||
def should_agent_stop(self) -> bool:
|
def should_agent_stop(self) -> bool:
|
||||||
|
if not self._control_channel_server:
|
||||||
|
logger.error("Agent should stop because it can't connect to the C&C server.")
|
||||||
|
return True
|
||||||
try:
|
try:
|
||||||
response = requests.get( # noqa: DUO123
|
response = requests.get( # noqa: DUO123
|
||||||
f"{self._control_channel_server}/api/monkey_control/{self._agent_id}",
|
f"https://{self._control_channel_server}/api/monkey_control/needs-to-stop/{self._agent_id}",
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
|
|
|
@ -11,6 +11,7 @@ from monkey_island.cc.database import database, mongo
|
||||||
from monkey_island.cc.resources.agent_controls import (
|
from monkey_island.cc.resources.agent_controls import (
|
||||||
StartedOnIsland,
|
StartedOnIsland,
|
||||||
StopAgentCheck,
|
StopAgentCheck,
|
||||||
|
StopAllAgents,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||||
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
||||||
|
@ -131,25 +132,23 @@ def init_api_resources(api):
|
||||||
"/api/monkey/<string:guid>/<string:config_format>",
|
"/api/monkey/<string:guid>/<string:config_format>",
|
||||||
)
|
)
|
||||||
api.add_resource(Bootloader, "/api/bootloader/<string:os>")
|
api.add_resource(Bootloader, "/api/bootloader/<string:os>")
|
||||||
api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/")
|
api.add_resource(LocalRun, "/api/local-monkey")
|
||||||
api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/")
|
api.add_resource(StopAgentCheck, "/api/local-monkey")
|
||||||
api.add_resource(
|
api.add_resource(ClientRun, "/api/client-monkey")
|
||||||
Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/<string:monkey_guid>"
|
api.add_resource(Telemetry, "/api/telemetry", "/api/telemetry/<string:monkey_guid>")
|
||||||
)
|
|
||||||
|
|
||||||
api.add_resource(IslandMode, "/api/island-mode")
|
api.add_resource(IslandMode, "/api/island-mode")
|
||||||
api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/")
|
api.add_resource(IslandConfiguration, "/api/configuration/island")
|
||||||
api.add_resource(ConfigurationExport, "/api/configuration/export")
|
api.add_resource(ConfigurationExport, "/api/configuration/export")
|
||||||
api.add_resource(ConfigurationImport, "/api/configuration/import")
|
api.add_resource(ConfigurationImport, "/api/configuration/import")
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
MonkeyDownload,
|
MonkeyDownload,
|
||||||
"/api/monkey/download",
|
"/api/monkey/download",
|
||||||
"/api/monkey/download/",
|
|
||||||
"/api/monkey/download/<string:path>",
|
"/api/monkey/download/<string:path>",
|
||||||
)
|
)
|
||||||
api.add_resource(NetMap, "/api/netmap", "/api/netmap/")
|
api.add_resource(NetMap, "/api/netmap")
|
||||||
api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/")
|
api.add_resource(Edge, "/api/netmap/edge")
|
||||||
api.add_resource(Node, "/api/netmap/node", "/api/netmap/node/")
|
api.add_resource(Node, "/api/netmap/node")
|
||||||
api.add_resource(NodeStates, "/api/netmap/nodeStates")
|
api.add_resource(NodeStates, "/api/netmap/nodeStates")
|
||||||
|
|
||||||
api.add_resource(SecurityReport, "/api/report/security")
|
api.add_resource(SecurityReport, "/api/report/security")
|
||||||
|
@ -160,9 +159,9 @@ def init_api_resources(api):
|
||||||
api.add_resource(MonkeyExploitation, "/api/exploitations/monkey")
|
api.add_resource(MonkeyExploitation, "/api/exploitations/monkey")
|
||||||
|
|
||||||
api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/<string:finding_id>")
|
api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/<string:finding_id>")
|
||||||
api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/")
|
api.add_resource(TelemetryFeed, "/api/telemetry-feed")
|
||||||
api.add_resource(Log, "/api/log", "/api/log/")
|
api.add_resource(Log, "/api/log")
|
||||||
api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/")
|
api.add_resource(IslandLog, "/api/log/island/download")
|
||||||
api.add_resource(PBAFileDownload, "/api/pba/download/<string:filename>")
|
api.add_resource(PBAFileDownload, "/api/pba/download/<string:filename>")
|
||||||
api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH)
|
api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH)
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
|
@ -172,10 +171,11 @@ def init_api_resources(api):
|
||||||
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
|
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
|
||||||
)
|
)
|
||||||
api.add_resource(PropagationCredentials, "/api/propagation-credentials/<string:guid>")
|
api.add_resource(PropagationCredentials, "/api/propagation-credentials/<string:guid>")
|
||||||
api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/")
|
api.add_resource(RemoteRun, "/api/remote-monkey")
|
||||||
api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/")
|
api.add_resource(VersionUpdate, "/api/version-update")
|
||||||
api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island")
|
api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island")
|
||||||
api.add_resource(StopAgentCheck, "/api/monkey_control/<int:monkey_guid>")
|
api.add_resource(StopAgentCheck, "/api/monkey_control/needs-to-stop/<int:monkey_guid>")
|
||||||
|
api.add_resource(StopAllAgents, "/api/monkey_control/stop-all-agents")
|
||||||
api.add_resource(ScoutSuiteAuth, "/api/scoutsuite_auth/<string:provider>")
|
api.add_resource(ScoutSuiteAuth, "/api/scoutsuite_auth/<string:provider>")
|
||||||
api.add_resource(AWSKeys, "/api/aws_keys")
|
api.add_resource(AWSKeys, "/api/aws_keys")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .agent_controls import AgentControls
|
|
@ -0,0 +1,7 @@
|
||||||
|
from mongoengine import Document, FloatField
|
||||||
|
|
||||||
|
|
||||||
|
class AgentControls(Document):
|
||||||
|
|
||||||
|
# Timestamp of the last "kill all agents" command
|
||||||
|
last_stop_all = FloatField(default=None)
|
|
@ -1,4 +1,4 @@
|
||||||
from mongoengine import EmbeddedDocument
|
from mongoengine import EmbeddedDocument, BooleanField
|
||||||
|
|
||||||
|
|
||||||
class Config(EmbeddedDocument):
|
class Config(EmbeddedDocument):
|
||||||
|
@ -8,5 +8,6 @@ class Config(EmbeddedDocument):
|
||||||
See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist
|
See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alive = BooleanField()
|
||||||
meta = {"strict": False}
|
meta = {"strict": False}
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
|
||||||
|
from monkey_island.cc.services.infection_lifecycle import should_agent_die
|
||||||
|
|
||||||
|
|
||||||
class StopAgentCheck(flask_restful.Resource):
|
class StopAgentCheck(flask_restful.Resource):
|
||||||
def get(self, monkey_guid: int):
|
def get(self, monkey_guid: int):
|
||||||
if monkey_guid % 2:
|
return {"stop_agent": should_agent_die(monkey_guid)}
|
||||||
return {"stop_agent": True}
|
|
||||||
else:
|
|
||||||
return {"stop_agent": False}
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import flask_restful
|
||||||
|
from flask import make_response, request
|
||||||
|
|
||||||
|
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.infection_lifecycle import set_stop_all
|
||||||
|
|
||||||
|
|
||||||
|
class StopAllAgents(flask_restful.Resource):
|
||||||
|
@jwt_required
|
||||||
|
def post(self):
|
||||||
|
data = json.loads(request.data)
|
||||||
|
if data["kill_time"]:
|
||||||
|
set_stop_all(data["kill_time"])
|
||||||
|
return make_response({}, 200)
|
||||||
|
else:
|
||||||
|
return make_response({}, 400)
|
|
@ -6,7 +6,7 @@ from flask import jsonify, make_response, request
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||||
from monkey_island.cc.services.database import Database
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle
|
from monkey_island.cc.services.infection_lifecycle import get_completed_steps
|
||||||
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
|
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -21,8 +21,6 @@ class Root(flask_restful.Resource):
|
||||||
return self.get_server_info()
|
return self.get_server_info()
|
||||||
elif action == "reset":
|
elif action == "reset":
|
||||||
return jwt_required(Database.reset_db)()
|
return jwt_required(Database.reset_db)()
|
||||||
elif action == "killall":
|
|
||||||
return jwt_required(InfectionLifecycle.kill_all)()
|
|
||||||
elif action == "is-up":
|
elif action == "is-up":
|
||||||
return {"is-up": True}
|
return {"is-up": True}
|
||||||
else:
|
else:
|
||||||
|
@ -33,5 +31,5 @@ class Root(flask_restful.Resource):
|
||||||
return jsonify(
|
return jsonify(
|
||||||
ip_addresses=local_ip_addresses(),
|
ip_addresses=local_ip_addresses(),
|
||||||
mongo=str(mongo.db),
|
mongo=str(mongo.db),
|
||||||
completed_steps=InfectionLifecycle.get_completed_steps(),
|
completed_steps=get_completed_steps(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from flask import jsonify
|
||||||
|
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
||||||
|
from monkey_island.cc.services.infection_lifecycle import init_agent_controls
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -23,6 +24,7 @@ class Database(object):
|
||||||
if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
|
if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
|
||||||
]
|
]
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
|
init_agent_controls()
|
||||||
logger.info("DB was reset")
|
logger.info("DB was reset")
|
||||||
return jsonify(status="OK")
|
return jsonify(status="OK")
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from flask import jsonify
|
from monkey_island.cc.models import Monkey
|
||||||
|
from monkey_island.cc.models.agent_controls import AgentControls
|
||||||
from monkey_island.cc.database import mongo
|
|
||||||
from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
|
from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
|
@ -16,42 +14,60 @@ from monkey_island.cc.services.reporting.report_generation_synchronisation impor
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class InfectionLifecycle:
|
def set_stop_all(time: float):
|
||||||
@staticmethod
|
for monkey in Monkey.objects():
|
||||||
def kill_all():
|
monkey.config.alive = False
|
||||||
mongo.db.monkey.update(
|
monkey.save()
|
||||||
{"dead": False},
|
agent_controls = AgentControls.objects.first()
|
||||||
{"$set": {"config.alive": False, "modifytime": datetime.now()}},
|
agent_controls.last_stop_all = time
|
||||||
upsert=False,
|
agent_controls.save()
|
||||||
multi=True,
|
|
||||||
)
|
|
||||||
logger.info("Kill all monkeys was called")
|
|
||||||
return jsonify(status="OK")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_completed_steps():
|
|
||||||
is_any_exists = NodeService.is_any_monkey_exists()
|
|
||||||
infection_done = NodeService.is_monkey_finished_running()
|
|
||||||
|
|
||||||
if infection_done:
|
def should_agent_die(guid: int) -> bool:
|
||||||
InfectionLifecycle._on_finished_infection()
|
monkey = Monkey.objects(guid=str(guid)).first()
|
||||||
report_done = ReportService.is_report_generated()
|
return _is_monkey_marked_dead(monkey) or _is_monkey_killed_manually(monkey)
|
||||||
else: # Infection is not done
|
|
||||||
report_done = False
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
run_server=True,
|
|
||||||
run_monkey=is_any_exists,
|
|
||||||
infection_done=infection_done,
|
|
||||||
report_done=report_done,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
def _is_monkey_marked_dead(monkey: Monkey) -> bool:
|
||||||
def _on_finished_infection():
|
return monkey.config.alive
|
||||||
# Checking is_report_being_generated here, because we don't want to wait to generate a
|
|
||||||
# report; rather,
|
|
||||||
# we want to skip and reply.
|
def _is_monkey_killed_manually(monkey: Monkey) -> bool:
|
||||||
if not is_report_being_generated() and not ReportService.is_latest_report_exists():
|
if monkey.has_parent():
|
||||||
safe_generate_reports()
|
launch_timestamp = monkey.get_parent().launch_time
|
||||||
if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED:
|
else:
|
||||||
TestTelemStore.export_telems()
|
launch_timestamp = monkey.launch_time
|
||||||
|
kill_timestamp = AgentControls.objects.first().last_stop_all
|
||||||
|
return int(kill_timestamp) >= int(launch_timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
def init_agent_controls():
|
||||||
|
AgentControls().save()
|
||||||
|
|
||||||
|
|
||||||
|
def get_completed_steps():
|
||||||
|
is_any_exists = NodeService.is_any_monkey_exists()
|
||||||
|
infection_done = NodeService.is_monkey_finished_running()
|
||||||
|
|
||||||
|
if infection_done:
|
||||||
|
_on_finished_infection()
|
||||||
|
report_done = ReportService.is_report_generated()
|
||||||
|
else: # Infection is not done
|
||||||
|
report_done = False
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
run_server=True,
|
||||||
|
run_monkey=is_any_exists,
|
||||||
|
infection_done=infection_done,
|
||||||
|
report_done=report_done,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _on_finished_infection():
|
||||||
|
# Checking is_report_being_generated here, because we don't want to wait to generate a
|
||||||
|
# report; rather,
|
||||||
|
# we want to skip and reply.
|
||||||
|
if not is_report_being_generated() and not ReportService.is_latest_report_exists():
|
||||||
|
safe_generate_reports()
|
||||||
|
if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED:
|
||||||
|
TestTelemStore.export_telems()
|
||||||
|
|
|
@ -78,6 +78,7 @@ class PreviewPaneComponent extends AuthComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove this
|
||||||
forceKillRow(asset) {
|
forceKillRow(asset) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -84,9 +84,14 @@ class MapPageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
killAllMonkeys = () => {
|
killAllMonkeys = () => {
|
||||||
this.authFetch('/api?action=killall')
|
this.authFetch('/api/agent_control/stop-all-agents',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({kill_time: Date.now()})
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => this.setState({killPressed: (res.status === 'OK')}));
|
.then(res => {this.setState({killPressed: true}); console.log(res)});
|
||||||
};
|
};
|
||||||
|
|
||||||
renderKillDialogModal = () => {
|
renderKillDialogModal = () => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ Everything in this file is what Vulture found as dead code but either isn't real
|
||||||
dead or is kept deliberately. Referencing these in a file like this makes sure that
|
dead or is kept deliberately. Referencing these in a file like this makes sure that
|
||||||
Vulture doesn't mark these as dead again.
|
Vulture doesn't mark these as dead again.
|
||||||
"""
|
"""
|
||||||
|
from monkey_island.cc import app
|
||||||
from monkey_island.cc.models import Report
|
from monkey_island.cc.models import Report
|
||||||
|
|
||||||
fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37)
|
fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37)
|
||||||
|
@ -100,6 +101,8 @@ EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collec
|
||||||
ProcessListCollector # unused class (monkey/infection_monkey/system_info/collectors/process_list_collector.py:18)
|
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)
|
_.coinit_flags # unused attribute (monkey/infection_monkey/system_info/windows_info_collector.py:11)
|
||||||
_.representations # unused attribute (monkey/monkey_island/cc/app.py:180)
|
_.representations # unused attribute (monkey/monkey_island/cc/app.py:180)
|
||||||
|
_.representations # unused attribute (monkey/monkey_island/cc/app.py:180)
|
||||||
|
app.url_map.strict_slashes
|
||||||
_.log_message # unused method (monkey/infection_monkey/transport/http.py:188)
|
_.log_message # unused method (monkey/infection_monkey/transport/http.py:188)
|
||||||
_.log_message # unused method (monkey/infection_monkey/transport/http.py:109)
|
_.log_message # unused method (monkey/infection_monkey/transport/http.py:109)
|
||||||
_.version_string # unused method (monkey/infection_monkey/transport/http.py:148)
|
_.version_string # unused method (monkey/infection_monkey/transport/http.py:148)
|
||||||
|
|
Loading…
Reference in New Issue