diff --git a/mythic-docker/app/__init__.py b/mythic-docker/app/__init__.py
index c4531c5ff..7fc9bd615 100755
--- a/mythic-docker/app/__init__.py
+++ b/mythic-docker/app/__init__.py
@@ -14,6 +14,7 @@
mythic_admin_password = str(settings.get("ADMIN_PASSWORD", "mythic_password"))
default_operation_name = str(settings.get("DEFAULT_OPERATION_NAME", "Operation Chimera"))
nginx_port = str(settings.get("NGINX_PORT", 7443))
+nginx_host = str(settings.get("NGINX_HOST", "127.0.0.1"))
listen_port = str(settings.get("SERVER_PORT", 17443))
allowed_ip_blocks = settings.get("ALLOWED_IP_BLOCKS", "0.0.0.0/0").split(",")
server_header = settings.get("SERVER_HEADER", "nginx 1.2")
@@ -40,6 +41,7 @@
"No MYTHIC_JWT_SECRET environment variable found")
sys.exit(1)
redis_port = int(settings.get("REDIS_PORT", 6379))
+redis_host = settings.get("REDIS_HOST", "127.0.0.1")
# --------------------------------------------
# --------------------------------------------
# IP to bind to for the server, 0.0.0.0 means all local IPv4 addresses
@@ -151,7 +153,7 @@ def format(self, record):
mythic = Sanic(__name__, strict_slashes=False, log_config=mythic_logging)
-mythic.config["SERVER_IP_ADDRESS"] = "127.0.0.1"
+mythic.config["SERVER_IP_ADDRESS"] = nginx_host
mythic.config["SERVER_PORT"] = nginx_port
mythic.config["DB_HOST"] = db_host
mythic.config["DB_PORT"] = db_port
diff --git a/mythic-docker/app/api/payloadtype_api.py b/mythic-docker/app/api/payloadtype_api.py
index 5d38a1834..7e74481e8 100755
--- a/mythic-docker/app/api/payloadtype_api.py
+++ b/mythic-docker/app/api/payloadtype_api.py
@@ -478,9 +478,17 @@ async def import_payload_type_func(ptype, operator, rabbitmqName):
return {"status": "success", "new": new_payload, **payload_type.to_json()}
except Exception as e:
logger.exception("exception on importing payload type {}".format(payload_type.ptype))
+ asyncio.create_task(
+ send_all_operations_message(
+ message=f"{rabbitmqName}'s sync with Mythic failed:\n" + str(e),
+ level="warning", source="payload_type_import"))
return {"status": "error", "error": str(e)}
except Exception as e:
logger.exception("failed to import a payload type: " + str(e))
+ asyncio.create_task(
+ send_all_operations_message(
+ message=f"{rabbitmqName}'s sync with Mythic failed:\n" + str(e),
+ level="warning", source="payload_type_import"))
return {"status": "error", "error": str(e)}
diff --git a/mythic-docker/app/api/rabbitmq_api.py b/mythic-docker/app/api/rabbitmq_api.py
index e35d8cbd7..9610bf4d5 100755
--- a/mythic-docker/app/api/rabbitmq_api.py
+++ b/mythic-docker/app/api/rabbitmq_api.py
@@ -645,6 +645,8 @@ async def create_tasking(self, task: MythicTask) -> MythicTask:
from app.api.task_api import add_all_payload_info
payload_info = await add_all_payload_info(payload)
+ if payload_info["status"] == "error":
+ return payload_info
payload_json["commands"] = payload_info["commands"]
payload_json["c2info"] = payload_info["c2info"]
payload_json["build_parameters"] = payload_info["build_parameters"]
@@ -920,6 +922,8 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
from app.api.task_api import add_all_payload_info
payload_info = await add_all_payload_info(payload)
+ if payload_info["status"] == "error":
+ return payload_info
payload_info = {**payload_info, **payload.to_json()}
return {"status": "success", "response": payload_info}
else:
diff --git a/mythic-docker/app/api/response_api.py b/mythic-docker/app/api/response_api.py
index 7768dcb72..24f73e355 100755
--- a/mythic-docker/app/api/response_api.py
+++ b/mythic-docker/app/api/response_api.py
@@ -562,33 +562,39 @@ async def post_agent_response(agent_message, callback):
rabbit_message["task"]["callback"] = task.callback.to_json()
# get the information for the callback's associated payload
payload_info = await add_all_payload_info(task.callback.registered_payload)
- rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
- "build_parameters"
- ]
- rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
- tags = await app.db_objects.execute(
- db_model.tasktag_query.where(db_model.TaskTag.task == task))
- rabbit_message["task"]["tags"] = [t.tag for t in tags]
- rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None
- rabbit_message["response"] = parsed_response["process_response"]
- if app.debugging_enabled:
- await send_all_operations_message(
- message=f"Sending message to {task.callback.registered_payload.payload_type.ptype}'s container for processing of a 'process_response' message:\n{str(parsed_response['process_container'])}",
- level="info", source="debug", operation=task.callback.operation)
- status = await send_pt_rabbitmq_message(payload_type=task.callback.registered_payload.payload_type.ptype,
- command="process_container",
- username="",
- reference_id=task.id,
- message_body=js.dumps(rabbit_message))
- if status["status"] == "error" and "type" in status:
- logger.error("response_api.py: sending process_response message: " + status["error"])
- await app.db_objects.create(Response, task=task,
- response="Container not running, failed to process process_response data, saving here")
- await app.db_objects.create(Response, task=task, response=parsed_response["process_response"])
- task.callback.registered_payload.payload_type.container_count = 0
- await app.db_objects.update(task.callback.registered_payload.payload_type)
- if status["status"] == "error":
- logger.error("response_api.py: sending process_response message: " + status["error"])
+ if payload_info["status"] == "error":
+ asyncio.create_task(
+ send_all_operations_message(
+ message=f"Failed to process post_response message for task {task.id}:\n{payload_info['error']}",
+ level="warning", source=f"task_response_{task.id}"))
+ else:
+ rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
+ "build_parameters"
+ ]
+ rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
+ tags = await app.db_objects.execute(
+ db_model.tasktag_query.where(db_model.TaskTag.task == task))
+ rabbit_message["task"]["tags"] = [t.tag for t in tags]
+ rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None
+ rabbit_message["response"] = parsed_response["process_response"]
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Sending message to {task.callback.registered_payload.payload_type.ptype}'s container for processing of a 'process_response' message:\n{str(parsed_response['process_container'])}",
+ level="info", source="debug", operation=task.callback.operation)
+ status = await send_pt_rabbitmq_message(payload_type=task.callback.registered_payload.payload_type.ptype,
+ command="process_container",
+ username="",
+ reference_id=task.id,
+ message_body=js.dumps(rabbit_message))
+ if status["status"] == "error" and "type" in status:
+ logger.error("response_api.py: sending process_response message: " + status["error"])
+ await app.db_objects.create(Response, task=task,
+ response="Container not running, failed to process process_response data, saving here")
+ await app.db_objects.create(Response, task=task, response=parsed_response["process_response"])
+ task.callback.registered_payload.payload_type.container_count = 0
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ elif status["status"] == "error":
+ logger.error("response_api.py: sending process_response message: " + status["error"])
except Exception as pc:
logger.error("response_api.py: " + str(sys.exc_info()[-1].tb_lineno) + str(pc))
if app.debugging_enabled:
diff --git a/mythic-docker/app/api/task_api.py b/mythic-docker/app/api/task_api.py
index 04a1c8c9e..adc6c18e9 100755
--- a/mythic-docker/app/api/task_api.py
+++ b/mythic-docker/app/api/task_api.py
@@ -1017,12 +1017,14 @@ async def issue_dynamic_parameter_call(command: str, parameter_name: str, payloa
rabbitmq_message = callback.to_json()
# get the information for the callback's associated payload
payload_info = await add_all_payload_info(callback.registered_payload)
+ if payload_info["status"] == "error":
+ return {"status": "error", "error": payload_info["error"]}
rabbitmq_message["build_parameters"] = payload_info[
"build_parameters"
]
rabbitmq_message["c2info"] = payload_info["c2info"]
except Exception as e:
- return {"status": "error", "error": "Failed to get callback and payload information"}, False
+ return {"status": "error", "error": "Failed to get callback and payload information"}
status, successfully_sent = await payload_rpc.call(message={
"action": parameter_name,
"command": command,
@@ -1238,6 +1240,8 @@ async def submit_task_to_container(task, username, params: str = None):
rabbit_message["task"]["callback"] = task.callback.to_json()
# get the information for the callback's associated payload
payload_info = await add_all_payload_info(task.callback.registered_payload)
+ if payload_info["status"] == "error":
+ return payload_info
rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
"build_parameters"
]
@@ -1280,6 +1284,8 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user
rabbit_message["task"]["callback"] = task.callback.to_json()
# get the information for the callback's associated payload
payload_info = await add_all_payload_info(task.callback.registered_payload)
+ if payload_info["status"] == "error":
+ return payload_info
rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
"build_parameters"
]
@@ -1308,54 +1314,64 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user
async def add_all_payload_info(payload):
- rabbit_message = {}
- if payload.uuid in cached_payload_info:
- rabbit_message["build_parameters"] = cached_payload_info[payload.uuid][
- "build_parameters"
- ]
- rabbit_message["commands"] = cached_payload_info[payload.uuid]["commands"]
- rabbit_message["c2info"] = cached_payload_info[payload.uuid]["c2info"]
- else:
- cached_payload_info[payload.uuid] = {}
- build_parameters = {}
- build_params = await app.db_objects.execute(
- db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload)
- )
- for bp in build_params:
- build_parameters[bp.build_parameter.name] = bp.parameter
- rabbit_message["build_parameters"] = build_parameters
- # cache it for later
- cached_payload_info[payload.uuid]["build_parameters"] = build_parameters
- c2_profile_parameters = []
- payloadc2profiles = await app.db_objects.execute(
- db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
- )
- for pc2p in payloadc2profiles:
- # for each profile, we need to get all of the parameters and supplied values for just that profile
- param_dict = {}
- c2_param_instances = await app.db_objects.execute(
- db_model.c2profileparametersinstance_query.where(
- (C2ProfileParametersInstance.payload == payload)
- & (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile)
- )
+ rabbit_message = {"status": "success"}
+ try:
+ if payload.uuid in cached_payload_info:
+ rabbit_message["build_parameters"] = cached_payload_info[payload.uuid][
+ "build_parameters"
+ ]
+ rabbit_message["commands"] = cached_payload_info[payload.uuid]["commands"]
+ rabbit_message["c2info"] = cached_payload_info[payload.uuid]["c2info"]
+ else:
+ cached_payload_info[payload.uuid] = {}
+ build_parameters = {}
+ build_params = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload)
)
- # save all the variables off to a dictionary for easy looping
- for instance in c2_param_instances:
- param = instance.c2_profile_parameters
- param_dict[param.name] = instance.value
-
- c2_profile_parameters.append(
- {"parameters": param_dict, **pc2p.c2_profile.to_json()}
+ for bp in build_params:
+ build_parameters[bp.build_parameter.name] = bp.parameter
+ rabbit_message["build_parameters"] = build_parameters
+ # cache it for later
+ cached_payload_info[payload.uuid]["build_parameters"] = build_parameters
+ c2_profile_parameters = []
+ payloadc2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
)
- rabbit_message["c2info"] = c2_profile_parameters
- cached_payload_info[payload.uuid]["c2info"] = c2_profile_parameters
- stamped_commands = await app.db_objects.execute(db_model.payloadcommand_query.where(
- db_model.PayloadCommand.payload == payload
- ))
- commands = [c.command.cmd for c in stamped_commands]
- rabbit_message["commands"] = commands
- cached_payload_info[payload.uuid]["commands"] = commands
- return rabbit_message
+ for pc2p in payloadc2profiles:
+ # for each profile, we need to get all of the parameters and supplied values for just that profile
+ param_dict = {}
+ c2_param_instances = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
+ (C2ProfileParametersInstance.payload == payload)
+ & (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile)
+ )
+ )
+ # save all the variables off to a dictionary for easy looping
+ for instance in c2_param_instances:
+ param = instance.c2_profile_parameters
+ param_dict[param.name] = instance.value
+
+ c2_profile_parameters.append(
+ {"parameters": param_dict, **pc2p.c2_profile.to_json()}
+ )
+ rabbit_message["c2info"] = c2_profile_parameters
+ cached_payload_info[payload.uuid]["c2info"] = c2_profile_parameters
+ stamped_commands = await app.db_objects.execute(db_model.payloadcommand_query.where(
+ db_model.PayloadCommand.payload == payload
+ ))
+ commands = [c.command.cmd for c in stamped_commands]
+ rabbit_message["commands"] = commands
+ cached_payload_info[payload.uuid]["commands"] = commands
+ return rabbit_message
+ except Exception as e:
+ rabbit_message["status"] = "error"
+ rabbit_message["error"] = str(e)
+ from app.api.operation_api import send_all_operations_message
+ asyncio.create_task(
+ send_all_operations_message(
+ message=f"Failed to fetch Payload info for {payload.uuid}:\n{str(e)}",
+ level="warning"))
+ return rabbit_message
async def add_command_attack_to_task(task, command):
diff --git a/mythic-docker/app/routes/routes.py b/mythic-docker/app/routes/routes.py
index b279748be..6d96ff374 100755
--- a/mythic-docker/app/routes/routes.py
+++ b/mythic-docker/app/routes/routes.py
@@ -40,6 +40,7 @@
import app.database_models.model as db_model
from sanic.log import logger
from uuid import uuid4
+import asyncio
env = Environment(loader=PackageLoader("app", "templates"), autoescape=True)
@@ -64,8 +65,7 @@ async def respect_pivot(my_links, request):
server_port = host_field[1]
updated_links["server_ip"] = server_ip
updated_links["server_port"] = server_port
- updated_links["login"] = "{}://{}/login".format(request.scheme, request.host)
- updated_links["register"] = "{}://{}/register".format(request.scheme, request.host)
+ updated_links["login"] = "/login"
return updated_links
@@ -369,59 +369,66 @@ async def setup_initial_info(sanic, loop):
async def initial_setup():
# create mythic_admin
import multiprocessing
- max_worker_connection = int(400 / (multiprocessing.cpu_count() + 1))
- app.websocket_pool = await asyncpg.create_pool(mythic.config["DB_POOL_ASYNCPG_CONNECT_STRING"],
- max_size=max_worker_connection)
- # redis automatically creates a pool behind the scenes
- app.redis_pool = redis.Redis(host="127.0.0.1", port=app.redis_port, db=0)
- # clear the database on start
- keys = app.redis_pool.keys("*")
- for k in keys:
- app.redis_pool.delete(k)
- operators = await app.db_objects.execute(Operator.select())
- if len(operators) != 0:
- logger.info("Users already exist, aborting initial install")
- return
- salt = str(uuid4())
- password = await crypto.hash_SHA512(salt + mythic_admin_password)
try:
- admin, created = await app.db_objects.get_or_create(
- Operator, username=mythic_admin_user, password=password, admin=True, active=True, salt=salt
+ max_worker_connection = int(400 / (multiprocessing.cpu_count() + 1))
+ app.websocket_pool = await asyncpg.create_pool(mythic.config["DB_POOL_ASYNCPG_CONNECT_STRING"],
+ max_size=max_worker_connection)
+ # redis automatically creates a pool behind the scenes
+ app.redis_pool = redis.Redis(host=app.redis_host, port=app.redis_port, db=0)
+ # clear the database on start
+ keys = app.redis_pool.keys("*")
+ for k in keys:
+ app.redis_pool.delete(k)
+ operators = await app.db_objects.execute(Operator.select())
+ if len(operators) != 0:
+ logger.info("Users already exist, aborting initial install")
+ return
+ salt = str(uuid4())
+ password = await crypto.hash_SHA512(salt + mythic_admin_password)
+ try:
+ admin, created = await app.db_objects.get_or_create(
+ Operator, username=mythic_admin_user, password=password, admin=True, active=True, salt=salt
+ )
+ except Exception as e:
+ print(e)
+ return
+ logger.info("Created Admin")
+ # create default operation
+ operation, created = await app.db_objects.get_or_create(
+ Operation,
+ name=default_operation_name,
+ admin=admin,
+ complete=False,
)
- except Exception as e:
- print(e)
- return
- logger.info("Created Admin")
- # create default operation
- operation, created = await app.db_objects.get_or_create(
- Operation,
- name=default_operation_name,
- admin=admin,
- complete=False,
- )
- logger.info("Created Operation")
- await app.db_objects.get_or_create(
- OperatorOperation, operator=admin, operation=operation
- )
- admin.current_operation = operation
- await app.db_objects.update(admin)
- logger.info("Registered Admin with the default operation")
- logger.info("Started parsing ATT&CK data...")
- file = open("./app/default_files/other_info/attack.json", "r")
- attack = js.load(file) # this is a lot of data and might take a hot second to load
- for obj in attack["techniques"]:
- await app.db_objects.create(ATTACK, **obj)
- file.close()
- logger.info("Created all ATT&CK entries")
- file = open("./app/default_files/other_info/artifacts.json", "r")
- artifacts_file = js.load(file)
- for artifact in artifacts_file["artifacts"]:
+ logger.info("Created Operation")
await app.db_objects.get_or_create(
- Artifact, name=artifact["name"], description=artifact["description"]
+ OperatorOperation, operator=admin, operation=operation
)
- file.close()
- logger.info("Created all base artifacts")
- logger.info("Successfully finished initial setup")
+ admin.current_operation = operation
+ await app.db_objects.update(admin)
+ logger.info("Registered Admin with the default operation")
+ logger.info("Started parsing ATT&CK data...")
+ file = open("./app/default_files/other_info/attack.json", "r")
+ attack = js.load(file) # this is a lot of data and might take a hot second to load
+ for obj in attack["techniques"]:
+ await app.db_objects.create(ATTACK, **obj)
+ file.close()
+ logger.info("Created all ATT&CK entries")
+ file = open("./app/default_files/other_info/artifacts.json", "r")
+ artifacts_file = js.load(file)
+ for artifact in artifacts_file["artifacts"]:
+ await app.db_objects.get_or_create(
+ Artifact, name=artifact["name"], description=artifact["description"]
+ )
+ file.close()
+ logger.info("Created all base artifacts")
+ logger.info("Successfully finished initial setup")
+ except Exception as e:
+ from app.api.operation_api import send_all_operations_message
+ asyncio.create_task(
+ send_all_operations_message(
+ message=f"Worker failed to initialize:\n {str(e)}",
+ level="warning"))
# /static serves out static images and files
@@ -440,5 +447,4 @@ async def initial_setup():
links["index"] = mythic.url_for("index")
links["login"] = links["WEB_BASE"] + "/login"
links["logout"] = mythic.url_for("logout")
-links["register"] = links["WEB_BASE"] + "/register"
links["settings"] = mythic.url_for("settings")
diff --git a/mythic-react-docker/mythic/src/components/App.js b/mythic-react-docker/mythic/src/components/App.js
index 31a624d84..0483a8424 100644
--- a/mythic-react-docker/mythic/src/components/App.js
+++ b/mythic-react-docker/mythic/src/components/App.js
@@ -61,7 +61,7 @@ export function App(props) {
const theme = React.useMemo( () => createMuiTheme({
palette: {
primary: {
- main: "#617AB1"
+ main: "#7f93c0"
},
secondary: {
main: "#a791c3"
@@ -86,6 +86,10 @@ export function App(props) {
primary: themeMode === 'dark' ? '#fff' : '#000',
secondary: themeMode === 'dark' ? 'rgba(255, 255, 255, 0.7)': 'rgba(0, 0, 0, 0.54)'
},
+ textBackgroundColor: themeMode === 'dark' ? '#74828b' : '#d9dbdc',
+ textBackgroundColorMythic: themeMode === 'dark' ? '#436b9f': '#aadcf5',
+ textBackgroundColorSuccess: themeMode === 'dark' ? '#09a21a' : '#70e373',
+ textBackgroundColorError: themeMode === 'dark' ? '#9f1616' : '#f19da3',
graphGroup: themeMode === 'dark' ? '#394c5d' : "#d3d7e8"
},
folderColor: '#f1d592',
@@ -111,7 +115,7 @@ export function App(props) {
}}>
{props.message}+
{props.message}} - style={{overflowX: "auto"}} + style={{}} />