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:
VakarisZ 2021-12-03 16:21:31 +02:00
parent 9d7c7073c3
commit 4fdd3370ca
13 changed files with 121 additions and 67 deletions

View File

@ -19,9 +19,12 @@ class ControlChannel(IControlChannel):
self._control_channel_server = server
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:
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,
proxies=ControlClient.proxies,
timeout=SHORT_REQUEST_TIMEOUT,

View File

@ -11,6 +11,7 @@ from monkey_island.cc.database import database, mongo
from monkey_island.cc.resources.agent_controls import (
StartedOnIsland,
StopAgentCheck,
StopAllAgents,
)
from monkey_island.cc.resources.attack.attack_report import AttackReport
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.add_resource(Bootloader, "/api/bootloader/<string:os>")
api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/")
api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/")
api.add_resource(
Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/<string:monkey_guid>"
)
api.add_resource(LocalRun, "/api/local-monkey")
api.add_resource(StopAgentCheck, "/api/local-monkey")
api.add_resource(ClientRun, "/api/client-monkey")
api.add_resource(Telemetry, "/api/telemetry", "/api/telemetry/<string:monkey_guid>")
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(ConfigurationImport, "/api/configuration/import")
api.add_resource(
MonkeyDownload,
"/api/monkey/download",
"/api/monkey/download/",
"/api/monkey/download/<string:path>",
)
api.add_resource(NetMap, "/api/netmap", "/api/netmap/")
api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/")
api.add_resource(Node, "/api/netmap/node", "/api/netmap/node/")
api.add_resource(NetMap, "/api/netmap")
api.add_resource(Edge, "/api/netmap/edge")
api.add_resource(Node, "/api/netmap/node")
api.add_resource(NodeStates, "/api/netmap/nodeStates")
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(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/<string:finding_id>")
api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/")
api.add_resource(Log, "/api/log", "/api/log/")
api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/")
api.add_resource(TelemetryFeed, "/api/telemetry-feed")
api.add_resource(Log, "/api/log")
api.add_resource(IslandLog, "/api/log/island/download")
api.add_resource(PBAFileDownload, "/api/pba/download/<string:filename>")
api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH)
api.add_resource(
@ -172,10 +171,11 @@ def init_api_resources(api):
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
)
api.add_resource(PropagationCredentials, "/api/propagation-credentials/<string:guid>")
api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/")
api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/")
api.add_resource(RemoteRun, "/api/remote-monkey")
api.add_resource(VersionUpdate, "/api/version-update")
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(AWSKeys, "/api/aws_keys")

View File

@ -0,0 +1 @@
from .agent_controls import AgentControls

View File

@ -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)

View File

@ -1,4 +1,4 @@
from mongoengine import EmbeddedDocument
from mongoengine import EmbeddedDocument, BooleanField
class Config(EmbeddedDocument):
@ -8,5 +8,6 @@ class Config(EmbeddedDocument):
See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist
"""
alive = BooleanField()
meta = {"strict": False}
pass

View File

@ -1,9 +1,8 @@
import flask_restful
from monkey_island.cc.services.infection_lifecycle import should_agent_die
class StopAgentCheck(flask_restful.Resource):
def get(self, monkey_guid: int):
if monkey_guid % 2:
return {"stop_agent": True}
else:
return {"stop_agent": False}
return {"stop_agent": should_agent_die(monkey_guid)}

View File

@ -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)

View File

@ -6,7 +6,7 @@ from flask import jsonify, make_response, request
from monkey_island.cc.database import mongo
from monkey_island.cc.resources.auth.auth import jwt_required
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
logger = logging.getLogger(__name__)
@ -21,8 +21,6 @@ class Root(flask_restful.Resource):
return self.get_server_info()
elif action == "reset":
return jwt_required(Database.reset_db)()
elif action == "killall":
return jwt_required(InfectionLifecycle.kill_all)()
elif action == "is-up":
return {"is-up": True}
else:
@ -33,5 +31,5 @@ class Root(flask_restful.Resource):
return jsonify(
ip_addresses=local_ip_addresses(),
mongo=str(mongo.db),
completed_steps=InfectionLifecycle.get_completed_steps(),
completed_steps=get_completed_steps(),
)

View File

@ -4,6 +4,7 @@ from flask import jsonify
from monkey_island.cc.database import mongo
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
logger = logging.getLogger(__name__)
@ -23,6 +24,7 @@ class Database(object):
if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
]
ConfigService.init_config()
init_agent_controls()
logger.info("DB was reset")
return jsonify(status="OK")

View File

@ -1,9 +1,7 @@
import logging
from datetime import datetime
from flask import jsonify
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.agent_controls import AgentControls
from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService
@ -16,25 +14,43 @@ from monkey_island.cc.services.reporting.report_generation_synchronisation impor
logger = logging.getLogger(__name__)
class InfectionLifecycle:
@staticmethod
def kill_all():
mongo.db.monkey.update(
{"dead": False},
{"$set": {"config.alive": False, "modifytime": datetime.now()}},
upsert=False,
multi=True,
)
logger.info("Kill all monkeys was called")
return jsonify(status="OK")
def set_stop_all(time: float):
for monkey in Monkey.objects():
monkey.config.alive = False
monkey.save()
agent_controls = AgentControls.objects.first()
agent_controls.last_stop_all = time
agent_controls.save()
@staticmethod
def get_completed_steps():
def should_agent_die(guid: int) -> bool:
monkey = Monkey.objects(guid=str(guid)).first()
return _is_monkey_marked_dead(monkey) or _is_monkey_killed_manually(monkey)
def _is_monkey_marked_dead(monkey: Monkey) -> bool:
return monkey.config.alive
def _is_monkey_killed_manually(monkey: Monkey) -> bool:
if monkey.has_parent():
launch_timestamp = monkey.get_parent().launch_time
else:
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:
InfectionLifecycle._on_finished_infection()
_on_finished_infection()
report_done = ReportService.is_report_generated()
else: # Infection is not done
report_done = False
@ -46,8 +62,8 @@ class InfectionLifecycle:
report_done=report_done,
)
@staticmethod
def _on_finished_infection():
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.

View File

@ -78,6 +78,7 @@ class PreviewPaneComponent extends AuthComponent {
});
}
// TODO remove this
forceKillRow(asset) {
return (
<tr>

View File

@ -84,9 +84,14 @@ class MapPageComponent extends AuthComponent {
}
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 => this.setState({killPressed: (res.status === 'OK')}));
.then(res => {this.setState({killPressed: true}); console.log(res)});
};
renderKillDialogModal = () => {

View File

@ -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
Vulture doesn't mark these as dead again.
"""
from monkey_island.cc import app
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)
@ -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)
_.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)
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:109)
_.version_string # unused method (monkey/infection_monkey/transport/http.py:148)