Refactored configuration import and added a check to decide if configuration is encrypted or not. This solved a bug where invalid json was treated as credential error.

This commit is contained in:
VakarisZ 2021-06-11 11:37:35 +03:00
parent b30de00305
commit a36fc81755
4 changed files with 51 additions and 39 deletions

View File

@ -54,13 +54,5 @@ class DomainControllerNameFetchError(FailedExploitationError):
""" Raise on failed attempt to extract domain controller's name """ """ Raise on failed attempt to extract domain controller's name """
class InvalidCredentialsError(Exception):
""" Raise when credentials supplied are invalid """
class NoCredentialsError(Exception):
""" Raise when no credentials have been supplied """
class InvalidConfigurationError(Exception): class InvalidConfigurationError(Exception):
""" Raise when configuration is invalid """ """ Raise when configuration is invalid """

View File

@ -6,14 +6,14 @@ from json.decoder import JSONDecodeError
import flask_restful import flask_restful
from flask import request from flask import request
from common.utils.exceptions import ( from common.utils.exceptions import InvalidConfigurationError
InvalidConfigurationError,
InvalidCredentialsError,
NoCredentialsError,
)
from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.utils.config_encryption import decrypt_config from monkey_island.cc.services.utils.config_encryption import (
InvalidCredentialsError,
decrypt_config,
is_encrypted,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -21,8 +21,7 @@ logger = logging.getLogger(__name__)
class ImportStatuses: class ImportStatuses:
UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required" UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required"
INVALID_CONFIGURATION = "invalid_configuration" INVALID_CONFIGURATION = "invalid_configuration"
PASSWORD_REQUIRED = "password_required" INVALID_CREDENTIALS = "invalid_credentials"
WRONG_PASSWORD = "wrong_password"
IMPORTED = "imported" IMPORTED = "imported"
@ -57,7 +56,8 @@ class ConfigurationImport(flask_restful.Resource):
).form_response() ).form_response()
except InvalidCredentialsError: except InvalidCredentialsError:
return ResponseContents( return ResponseContents(
import_status=ImportStatuses.WRONG_PASSWORD, message="Wrong password supplied" import_status=ImportStatuses.INVALID_CREDENTIALS,
message="Invalid credentials provided",
).form_response() ).form_response()
except InvalidConfigurationError: except InvalidConfigurationError:
return ResponseContents( return ResponseContents(
@ -65,20 +65,30 @@ class ConfigurationImport(flask_restful.Resource):
message="Invalid configuration supplied. " message="Invalid configuration supplied. "
"Maybe the format is outdated or the file has been corrupted.", "Maybe the format is outdated or the file has been corrupted.",
).form_response() ).form_response()
except NoCredentialsError:
return ResponseContents(
import_status=ImportStatuses.PASSWORD_REQUIRED,
).form_response()
@staticmethod @staticmethod
def _get_plaintext_config_from_request(request_contents: dict) -> dict: def _get_plaintext_config_from_request(request_contents: dict) -> dict:
try: if ConfigurationImport.is_config_encrypted(request_contents["config"]):
config = json.loads(request_contents["config"]) return decrypt_config(request_contents["config"], request_contents["password"])
except JSONDecodeError: else:
config = decrypt_config(request_contents["config"], request_contents["password"]) try:
return config return json.loads(request_contents["config"])
except JSONDecodeError:
raise InvalidConfigurationError
@staticmethod @staticmethod
def import_config(config_json): def import_config(config_json):
if not ConfigService.update_config(config_json, should_encrypt=True): if not ConfigService.update_config(config_json, should_encrypt=True):
raise InvalidConfigurationError raise InvalidConfigurationError
@staticmethod
def is_config_encrypted(config: str):
try:
if config.startswith("{"):
return False
elif is_encrypted(config):
return True
else:
raise InvalidConfigurationError
except Exception:
raise InvalidConfigurationError

View File

@ -6,7 +6,7 @@ from typing import Dict
import pyAesCrypt import pyAesCrypt
from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError from common.utils.exceptions import InvalidConfigurationError
BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef
@ -28,9 +28,6 @@ def encrypt_config(config: Dict, password: str) -> str:
def decrypt_config(cyphertext: str, password: str) -> Dict: def decrypt_config(cyphertext: str, password: str) -> Dict:
if not password:
raise NoCredentialsError
cyphertext = base64.b64decode(cyphertext) cyphertext = base64.b64decode(cyphertext)
ciphertext_config_stream = io.BytesIO(cyphertext) ciphertext_config_stream = io.BytesIO(cyphertext)
dec_plaintext_config_stream = io.BytesIO() dec_plaintext_config_stream = io.BytesIO()
@ -51,6 +48,15 @@ def decrypt_config(cyphertext: str, password: str) -> Dict:
raise InvalidCredentialsError raise InvalidCredentialsError
else: else:
logger.info("The provided configuration file is corrupt.") logger.info("The provided configuration file is corrupt.")
raise ex raise InvalidConfigurationError
plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8"))
return plaintext_config return plaintext_config
def is_encrypted(ciphertext: str) -> bool:
ciphertext = base64.b64decode(ciphertext)
return ciphertext.startswith(b"AES")
class InvalidCredentialsError(Exception):
""" Raise when credentials supplied are invalid """

View File

@ -40,8 +40,8 @@ const ConfigImportModal = (props: Props) => {
}, [configContents]) }, [configContents])
function sendConfigToServer(): Promise<string> { function sendConfigToServer() {
return authComponent.authFetch(configImportEndpoint, authComponent.authFetch(configImportEndpoint,
{ {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@ -53,27 +53,30 @@ const ConfigImportModal = (props: Props) => {
} }
).then(res => res.json()) ).then(res => res.json())
.then(res => { .then(res => {
if (res['import_status'] === 'password_required') { if (res['import_status'] === 'invalid_credentials') {
setUploadStatus(UploadStatuses.success); setUploadStatus(UploadStatuses.success);
setShowPassword(true); if (showPassword){
} else if (res['import_status'] === 'wrong_password') { setErrorMessage(res['message']);
setErrorMessage(res['message']); } else {
setShowPassword(true);
setErrorMessage('');
}
} else if (res['import_status'] === 'invalid_configuration') { } else if (res['import_status'] === 'invalid_configuration') {
setUploadStatus(UploadStatuses.error); setUploadStatus(UploadStatuses.error);
setErrorMessage(res['message']); setErrorMessage(res['message']);
} else if (res['import_status'] === 'unsafe_options_verification_required') { } else if (res['import_status'] === 'unsafe_options_verification_required') {
setUploadStatus(UploadStatuses.success);
setErrorMessage('');
if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) {
setShowUnsafeOptionsConfirmation(true); setShowUnsafeOptionsConfirmation(true);
setCandidateConfig(res['config']); setCandidateConfig(res['config']);
} else { } else {
setUnsafeOptionsVerified(true); setUnsafeOptionsVerified(true);
setConfigContents(res['config']);
} }
} else if (res['import_status'] === 'imported'){ } else if (res['import_status'] === 'imported'){
resetState(); resetState();
props.onClose(true); props.onClose(true);
} }
return res['import_status'];
}) })
} }
@ -93,6 +96,7 @@ const ConfigImportModal = (props: Props) => {
} }
function uploadFile(event) { function uploadFile(event) {
setShowPassword(false);
let reader = new FileReader(); let reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
setConfigContents(event.target.result); setConfigContents(event.target.result);