Implemented file restoration endpoint, file upload front end

This commit is contained in:
VakarisZ 2019-02-26 19:52:57 +02:00
parent 7281f2284b
commit 33e78ba4e8
7 changed files with 157 additions and 33 deletions

View File

@ -120,7 +120,8 @@ def init_app(mongo_url):
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:file_name>')
'/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

View File

@ -4,25 +4,35 @@ from cc.services.config import ConfigService
import os
from werkzeug.utils import secure_filename
import logging
from cc.database import mongo
import copy
LOG = logging.getLogger(__name__)
UPLOADS_DIR = "./monkey_island/cc/userUploads"
GET_FILE_DIR = "./userUploads"
# What endpoints front end uses to identify which files to work with
LINUX_PBA_TYPE = 'PBAlinux'
WINDOWS_PBA_TYPE = 'PBAwindows'
# Where to find file info in config
PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach']
WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH)
WINDOWS_PBA_INFO.append('windows_file_info')
LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH)
LINUX_PBA_INFO.append('linux_file_info')
class FileUpload(flask_restful.Resource):
# Used by monkey. can't secure.
def get(self, path):
return send_from_directory(UPLOADS_DIR, path)
def get(self, file_type, file_name):
req_data = request.data
def get(self, file_type):
# Verify that file_name is indeed a file from config
if file_type == LINUX_PBA_TYPE:
filename = ConfigService.get_config_value(copy.deepcopy(LINUX_PBA_INFO))['name']
else:
filename = ConfigService.get_config_value(copy.deepcopy(WINDOWS_PBA_INFO))['name']
return send_from_directory(GET_FILE_DIR, filename)
def post(self, file_type):
filename = ''
if file_type == 'PBAlinux':
filename = FileUpload.upload_pba_file(request)
elif file_type == 'PBAwindows':
filename = FileUpload.upload_pba_file(request, False)
filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE))
response = Response(
response=filename,
@ -30,32 +40,40 @@ class FileUpload(flask_restful.Resource):
return response
def delete(self, file_type):
config = ConfigService.get_config(should_decrypt=False)
if file_type == 'PBAlinux':
file_info = 'linux_file_info'
else:
file_info = 'windows_file_info'
filename = config['monkey']['behaviour']['custom_post_breach'][file_info]['name']
file_conf_path = LINUX_PBA_INFO if file_type == 'PBAlinux' else WINDOWS_PBA_INFO
filename = ConfigService.get_config_value(file_conf_path)['name']
file_path = os.path.join(UPLOADS_DIR, filename)
try:
if os.path.exists(file_path):
os.remove(file_path)
ConfigService.set_config_value(file_conf_path, {'size': '0', 'name': ''})
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):
config = ConfigService.get_config(should_decrypt=False)
filename = secure_filename(request_.files['filepond'].filename)
file_path = os.path.join(UPLOADS_DIR, filename)
request_.files['filepond'].save(file_path)
file_size = os.path.getsize(file_path)
ConfigService.set_config_value((LINUX_PBA_INFO if is_linux else WINDOWS_PBA_INFO),
{'size': file_size, 'name': filename})
return filename
@staticmethod
def get_file_info_db_paths(is_linux=True):
"""
Gets PBA file size and name parameter config paths for linux and windows
:param is_linux: determines whether to get windows or linux file params
:return: returns tuple of filename and file size paths in config
"""
if is_linux:
file_info = 'linux_file_info'
else:
file_info = 'windows_file_info'
config['monkey']['behaviour']['custom_post_breach'][file_info]['size'] = file_size
config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] = filename
ConfigService.update_config(config, should_encrypt=False)
return filename
config_path = 'monkey.behaviour.custom_post_breach.' + file_info + '.'
size_path = config_path + 'size'
name_path = config_path + 'name'
return name_path, size_path

View File

@ -85,6 +85,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)

View File

@ -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);
@ -173,11 +179,15 @@ class AppComponent extends AuthComponent {
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
<Route path='/login' render={(props) => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}
removePBAfiles={this.state.removePBAfiles}
setRemovePBAfiles={this.setRemovePBAfiles}/>)}
{this.renderRoute('/run-monkey', <RunMonkeyPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}
removePBAfiles={this.state.removePBAfiles}
setRemovePBAfiles={this.setRemovePBAfiles}/>)}
{this.renderRoute('/report', <ReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
</Col>

View File

@ -3,7 +3,7 @@ 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, registerPlugin } from 'react-filepond';
import { FilePond } from 'react-filepond';
import 'filepond/dist/filepond.min.css';
class ConfigurePageComponent extends AuthComponent {
@ -21,7 +21,9 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none',
sections: [],
selectedSection: 'basic',
allMonkeysAreDead: true
allMonkeysAreDead: true,
PBAwinFile: [],
PBAlinuxFile: []
};
}
@ -95,6 +97,7 @@ class ConfigurePageComponent extends AuthComponent {
};
resetConfig = () => {
this.removePBAfiles();
this.authFetch('/api/configuration/island',
{
method: 'POST',
@ -112,6 +115,16 @@ class ConfigurePageComponent extends AuthComponent {
});
};
removePBAfiles(){
// We need to clean files from widget, local state and configuration (to sync with bac end)
if (this.hasOwnProperty('PBAlinuxPond')){
this.PBAlinuxPond.removeFile();
this.PBAwindowsPond.removeFile();
}
this.setState({PBAlinuxFile: []});
this.setState({PBAwinFile: []});
}
onReadFile = (event) => {
try {
this.setState({
@ -153,16 +166,75 @@ class ConfigurePageComponent extends AuthComponent {
};
PBAwindows = () => {
if (! this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info){
}
return (<FilePond server='/api/fileUpload/PBAwindows'/>)
return (<FilePond
server='/api/fileUpload/PBAwindows'
files={this.getWinPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAwinFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAwindowsPond = ref}
/>)
};
PBAlinux = () => {
return (<FilePond server='/api/fileUpload/PBAlinux'/>)
return (<FilePond
server='/api/fileUpload/PBAlinux'
files={this.getLinuxPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAlinuxFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAlinuxPond = ref}
onload={this.props.setRemovePBAfiles(false)}
/>)
};
getWinPBAfile(){
if (this.props.removePBAfiles){
// If env was reset we need to remove files in react state
/*if (this.hasOwnProperty('PBAwinFile')){
this.setState({PBAwinFile: ''})
}*/
} else if (this.state.PBAwinFile.length !== 0){
console.log("Getting from local state")
return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true)
} else {
console.log("Getting from config")
return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info)
}
}
getLinuxPBAfile(){
if (this.props.removePBAfiles) {
// If env was reset we need to remove files in react state
/*if (this.hasOwnProperty('PBAlinuxFile')){
this.setState({PBAlinuxFile: ''})
}*/
} else if (this.state.PBAlinuxFile.length !== 0){
console.log("Getting from local state")
return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true)
} else {
console.log("Getting from config")
return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info)
}
}
static getPBAfile(fileSrc, isMock=false){
let PBAfile = [{
source: fileSrc.name,
options: {
type: 'limbo'
}
}];
if (isMock){
PBAfile[0].options.file = fileSrc
}
return PBAfile
}
render() {
let displayedSchema = {};
@ -180,6 +252,12 @@ class ConfigurePageComponent extends AuthComponent {
},
windows_file: {
"ui:widget": this.PBAwindows
},
linux_file_info: {
classNames: "linux-pba-file-info"
},
windows_file_info: {
classNames: "windows-pba-file-info"
}
}
}
@ -188,7 +266,6 @@ class ConfigurePageComponent extends AuthComponent {
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>
@ -276,7 +353,6 @@ class ConfigurePageComponent extends AuthComponent {
</div>
: ''}
</div>
</Col>
);
}

View File

@ -108,6 +108,7 @@ class StartOverPageComponent extends AuthComponent {
this.setState({
cleaned: true
});
this.props.setRemovePBAfiles(true)
}
});
}

View File

@ -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;
}