forked from p15670423/monkey
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:
parent
b30de00305
commit
a36fc81755
|
@ -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 """
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue