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 """
class InvalidCredentialsError(Exception):
""" Raise when credentials supplied are invalid """
class NoCredentialsError(Exception):
""" Raise when no credentials have been supplied """
class InvalidConfigurationError(Exception):
""" Raise when configuration is invalid """

View File

@ -6,14 +6,14 @@ from json.decoder import JSONDecodeError
import flask_restful
from flask import request
from common.utils.exceptions import (
InvalidConfigurationError,
InvalidCredentialsError,
NoCredentialsError,
)
from common.utils.exceptions import InvalidConfigurationError
from monkey_island.cc.resources.auth.auth import jwt_required
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__)
@ -21,8 +21,7 @@ logger = logging.getLogger(__name__)
class ImportStatuses:
UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required"
INVALID_CONFIGURATION = "invalid_configuration"
PASSWORD_REQUIRED = "password_required"
WRONG_PASSWORD = "wrong_password"
INVALID_CREDENTIALS = "invalid_credentials"
IMPORTED = "imported"
@ -57,7 +56,8 @@ class ConfigurationImport(flask_restful.Resource):
).form_response()
except InvalidCredentialsError:
return ResponseContents(
import_status=ImportStatuses.WRONG_PASSWORD, message="Wrong password supplied"
import_status=ImportStatuses.INVALID_CREDENTIALS,
message="Invalid credentials provided",
).form_response()
except InvalidConfigurationError:
return ResponseContents(
@ -65,20 +65,30 @@ class ConfigurationImport(flask_restful.Resource):
message="Invalid configuration supplied. "
"Maybe the format is outdated or the file has been corrupted.",
).form_response()
except NoCredentialsError:
return ResponseContents(
import_status=ImportStatuses.PASSWORD_REQUIRED,
).form_response()
@staticmethod
def _get_plaintext_config_from_request(request_contents: dict) -> dict:
try:
config = json.loads(request_contents["config"])
except JSONDecodeError:
config = decrypt_config(request_contents["config"], request_contents["password"])
return config
if ConfigurationImport.is_config_encrypted(request_contents["config"]):
return decrypt_config(request_contents["config"], request_contents["password"])
else:
try:
return json.loads(request_contents["config"])
except JSONDecodeError:
raise InvalidConfigurationError
@staticmethod
def import_config(config_json):
if not ConfigService.update_config(config_json, should_encrypt=True):
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
from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError
from common.utils.exceptions import InvalidConfigurationError
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:
if not password:
raise NoCredentialsError
cyphertext = base64.b64decode(cyphertext)
ciphertext_config_stream = io.BytesIO(cyphertext)
dec_plaintext_config_stream = io.BytesIO()
@ -51,6 +48,15 @@ def decrypt_config(cyphertext: str, password: str) -> Dict:
raise InvalidCredentialsError
else:
logger.info("The provided configuration file is corrupt.")
raise ex
raise InvalidConfigurationError
plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8"))
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])
function sendConfigToServer(): Promise<string> {
return authComponent.authFetch(configImportEndpoint,
function sendConfigToServer() {
authComponent.authFetch(configImportEndpoint,
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
@ -53,27 +53,30 @@ const ConfigImportModal = (props: Props) => {
}
).then(res => res.json())
.then(res => {
if (res['import_status'] === 'password_required') {
if (res['import_status'] === 'invalid_credentials') {
setUploadStatus(UploadStatuses.success);
setShowPassword(true);
} else if (res['import_status'] === 'wrong_password') {
setErrorMessage(res['message']);
if (showPassword){
setErrorMessage(res['message']);
} else {
setShowPassword(true);
setErrorMessage('');
}
} else if (res['import_status'] === 'invalid_configuration') {
setUploadStatus(UploadStatuses.error);
setErrorMessage(res['message']);
} else if (res['import_status'] === 'unsafe_options_verification_required') {
setUploadStatus(UploadStatuses.success);
setErrorMessage('');
if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) {
setShowUnsafeOptionsConfirmation(true);
setCandidateConfig(res['config']);
} else {
setUnsafeOptionsVerified(true);
setConfigContents(res['config']);
}
} else if (res['import_status'] === 'imported'){
resetState();
props.onClose(true);
}
return res['import_status'];
})
}
@ -93,6 +96,7 @@ const ConfigImportModal = (props: Props) => {
}
function uploadFile(event) {
setShowPassword(false);
let reader = new FileReader();
reader.onload = (event) => {
setConfigContents(event.target.result);