From fc1f12c24d7ace3a8626b8455cace2a7e0942833 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 17:02:10 +0300 Subject: [PATCH] Implemented safety check on import. --- .../cc/resources/configuration_import.py | 15 ++- .../ImportConfigModal.tsx | 100 ++++++++++++------ vulture_allowlist.py | 3 +- 3 files changed, 84 insertions(+), 34 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 7e3327cbe..10a33263a 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -21,6 +21,8 @@ class ResponseContents: import_status: str = "imported" message: str = "" status_code: int = 200 + config: str = "" + config_schema: str = "" def form_response(self): return self.__dict__, self.status_code @@ -40,8 +42,17 @@ class ConfigurationImport(flask_restful.Resource): config = json.loads(request_contents["config"]) except JSONDecodeError: config = decrypt_config(request_contents["config"], request_contents["password"]) - ConfigurationImport.import_config(config) - return ResponseContents().form_response() + + if request_contents["unsafeOptionsVerified"]: + ConfigurationImport.import_config(config) + return ResponseContents().form_response() + else: + return ResponseContents( + config=config, + config_schema=ConfigService.get_config_schema(), + import_status="unsafe_options_verification_required", + status_code=403, + ).form_response() except InvalidCredentialsError: return ResponseContents( import_status="wrong_password", message="Wrong password supplied", status_code=403 diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 9da33cd4a..7023b2546 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -1,11 +1,14 @@ import {Button, Modal, Form, Alert} from 'react-bootstrap'; import React, {useEffect, useState} from 'react'; +import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import AuthComponent from '../AuthComponent'; import '../../styles/components/configuration-components/ImportConfigModal.scss'; +import UnsafeOptionsConfirmationModal + from '../configuration-components/UnsafeOptionsConfirmationModal.js'; import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; -import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; type Props = { @@ -19,9 +22,15 @@ const ConfigImportModal = (props: Props) => { const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [configContents, setConfigContents] = useState(null); + const [candidateConfig, setCandidateConfig] = useState(null); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [errorMessage, setErrorMessage] = useState(''); + const [unsafeOptionsVerified, setUnsafeOptionsVerified] = useState(false); + const [showUnsafeOptionsConfirmation, + setShowUnsafeOptionsConfirmation] = useState(false); + const [fileFieldKey, setFileFieldKey] = useState(Date.now()); + const authComponent = new AuthComponent({}); useEffect(() => { @@ -38,21 +47,31 @@ const ConfigImportModal = (props: Props) => { headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ config: configContents, - password: password + password: password, + unsafeOptionsVerified: unsafeOptionsVerified }) } ).then(res => res.json()) .then(res => { if (res['import_status'] === 'password_required') { + setUploadStatus(UploadStatuses.success); setShowPassword(true); - } else if (res['import_status'] === 'wrong_password'){ + } else if (res['import_status'] === 'wrong_password') { setErrorMessage(res['message']); - } - if (res['import_status'] === 'invalid_configuration'){ + } else if (res['import_status'] === 'invalid_configuration') { setUploadStatus(UploadStatuses.error); setErrorMessage(res['message']); - } else { - setUploadStatus(UploadStatuses.success); + } else if (res['import_status'] === 'unsafe_options_verification_required') { + if (isUnsafeOptionSelected(res['config_schema'], res['config'])) { + setShowUnsafeOptionsConfirmation(true); + setCandidateConfig(JSON.stringify(res['config'])); + } else { + setUnsafeOptionsVerified(true); + setConfigContents(res['config']); + } + } else if (res['import_status'] === 'imported'){ + resetState(); + props.onClose(true); } return res['import_status']; }) @@ -68,6 +87,9 @@ const ConfigImportModal = (props: Props) => { setConfigContents(null); setErrorMessage(''); setShowPassword(false); + setShowUnsafeOptionsConfirmation(false); + setUnsafeOptionsVerified(false); + setFileFieldKey(Date.now()); // Resets the file input } function uploadFile(event) { @@ -76,50 +98,66 @@ const ConfigImportModal = (props: Props) => { setConfigContents(event.target.result); }; reader.readAsText(event.target.files[0]); - } - function onImportClick() { - sendConfigToServer().then((importStatus) => { - if(importStatus === 'imported'){ - resetState(); - props.onClose(true); - } - }); + function showVerificationDialog() { + return ( + { + resetState(); + }} + onContinueClick={() => { + setUnsafeOptionsVerified(true); + setConfigContents(candidateConfig); + }} + /> + ); } return ( - {resetState(); props.onClose(false)}} - size={'lg'} - className={'config-import-modal'}> - - Configuration import + < + Modal + show={props.show} + onHide={() => { + resetState(); + props.onClose(false) + }} + size={'lg'} + className={'config-import-modal'}> + < Modal.Header + closeButton> + < Modal.Title> + Configuration + import + + {showVerificationDialog()}
- + className={'file-input'} + key={fileFieldKey}/> - {showPassword && } + {showPassword && } - { errorMessage && - - - {errorMessage} - + {errorMessage && + + + {errorMessage} + }
diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 8ee281d60..2c937ee4f 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -169,7 +169,8 @@ build_from_config_file_contents # unused method 'build_from_config_file_content mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26: ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) -import_status # \monkey_island\cc\resources\configuration_import.py:19 +import_status # monkey_island\cc\resources\configuration_import.py:19 +config_schema # monkey_island\cc\resources\configuration_import.py:25 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23)