Skip to content

Commit

Permalink
updating a few bug fixes in the new ui, adding a hide multiple option…
Browse files Browse the repository at this point in the history
…, and updating the payloadtype pypi file/docker containers to fix an issue with setting custom task statuses
  • Loading branch information
its-a-feature committed Jan 31, 2022
1 parent 40d6d89 commit 1d1d428
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 38 deletions.
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The following the table details the supported versions of Mythic.

| Version | Supported |
| ------- | ------------------ |
| 2.3.6 | :white_check_mark: |
| 2.3.7 | :white_check_mark: |
| < 2.3.0 | :x: |


Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.3.6
2.3.7
9 changes: 7 additions & 2 deletions mythic-docker/app/api/credential_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@ async def create_credential_func(operator, operation, data):
realm=data["realm"],
operation=operation,
credential=data["credential"].encode(),
metadata=data["metadata"]
)
cred.comment = cred.comment + " " + data["comment"] if cred.comment != data["comment"] else cred.comment
cred.metadata = cred.metadata + " " + data["metadata"] if cred.metadata != data["metadata"] else cred.metadata
await app.db_objects.update(cred)
status["new"] = False
except Exception as e:
# we got here because the credential doesn't exist, so we need to create it
Expand All @@ -120,8 +122,11 @@ async def create_credential_func(operator, operation, data):
realm=data["realm"],
operation=operation,
credential=data["credential"].encode(),
metadata=data["metadata"]
)
cred.comment = cred.comment + " " + data["comment"] if cred.comment != data["comment"] else cred.comment
cred.metadata = cred.metadata + " " + data["metadata"] if cred.metadata != data[
"metadata"] else cred.metadata
await app.db_objects.update(cred)
status["new"] = False
except Exception as e:
# we got here because the credential doesn't exist, so we need to create it
Expand Down
58 changes: 48 additions & 10 deletions mythic-docker/app/api/rabbitmq_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
# message.routing_key,
# message.body.decode('utf-8')
# ))
# logger.info(message.routing_key)
logger.info(message.routing_key)
if pieces[1] == "status":
if len(pieces) == 8:
if int(pieces[7]) > valid_payload_container_version_bounds[1] or \
Expand Down Expand Up @@ -269,9 +269,8 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
message=error_message, level="warning", source="payload_import_sync_error"))
return
from app.api.task_api import check_and_issue_task_callback_functions
logger.info(f"RABBITMQ GOT TASK INFO BACK FROM CONTAINER FOR {pieces[4]}")
logger.info(f"RABBITMQ GOT CREATE_TASK INFO BACK FROM CONTAINER FOR {pieces[4]} WITH STATUS CODE {pieces[5]}")
task = await app.db_objects.get(db_model.task_query, id=pieces[4])
logger.info(f"RABBITMQ FETCHED TASK INFO BACK FROM CONTAINER FOR {pieces[4]}")
logger.info(response_message)

task.display_params = response_message["task"]["display_params"]
Expand Down Expand Up @@ -404,18 +403,22 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
task.completed = True
elif task.completed:
# this means it was already previously marked as completed
logger.info(f"RABBITMQ CREATE_TASKING status {pieces[5]} updating task {task.id} to 'completed'")
task.status = "completed"
else:
task.status = "submitted"
elif pieces[5] == "completed":
logger.info(f"RABBITMQ CREATE_TASKING status {pieces[5]} updating task {task.id} to 'completed'")
task.status = "completed"
task.status_timestamp_processed = task.timestamp
task.completed = True
elif pieces[5] == "preprocessing":
task.status = "submitted"
else:
task.status = pieces[5].lower()
task.status_timestamp_submitted = task.timestamp
await app.db_objects.update(task)
logger.info(f"RABBITMQ CALLED UPDATE ON TASK BACK FROM CONTAINER FOR {pieces[4]}")
logger.info(f"RABBITMQ CALLED UPDATE ON TASK BACK FROM CONTAINER FOR {pieces[4]} WITH STATUS {task.status} FROM CREATE_TASKING")
if task.completed:
asyncio.create_task(check_and_issue_task_callback_functions(taskOriginal=task,
task_completed=True))
Expand Down Expand Up @@ -514,7 +517,7 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
(db_model.Task.parent_task == task) &
(db_model.Task.completed == False)
))
#logger.info(f"task_callback_function with pieces[5] = {pieces[5]}")
logger.info(f"task_callback_function with status {pieces[5]} for task {task.id}")
if pieces[5] == "success":
# check if there are subtasks created for this task, if so, this should not go to
# submitted
Expand All @@ -541,6 +544,7 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
and not (task.opsec_post_blocked and not task.opsec_post_bypassed):
# this task isn't done, it is a script only, and you're not blocked
# so instead of going to submitted, it should be marked as done
logger.info(f"Callback_Function marking task {task.id} as 'completed'")
task.status = "completed"
task.completed = True
await app.db_objects.update(task)
Expand Down Expand Up @@ -568,10 +572,12 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
#logger.info(f"callback handler for task {task.id} but not any of the others fired")
#logger.info(f"{task.id} - completed {task.completed}, subtasks {subtasks}")
elif pieces[5] == "completed":
task.status = "processed"
task.status_timestamp_processed = task.timestamp

if not task.completed:
task.status = "processed"
task.status_timestamp_processed = task.timestamp
task.completed = True
logger.info(f"Updating task {task.id} status to completed in task_callback_function rabbitmq")
task.status = "completed"
await app.db_objects.update(task)
asyncio.create_task(check_and_issue_task_callback_functions(taskOriginal=task,
Expand All @@ -583,6 +589,9 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
response_message[
"updating_piece"]
))
else:
task.status = "completed"
await app.db_objects.update(task)
elif pieces[5] == "error":
task.status = "Task Handler Error"
task.completed = True
Expand All @@ -596,6 +605,10 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
response_message[
"updating_piece"]
))
else:
task.status = pieces[5]
logger.info(f"called update on task {task.id} with status {task.status}")
await app.db_objects.update(task)
elif pieces[3] == "sync_classes":
if pieces[6] == "":
# this was an auto sync from starting a container
Expand Down Expand Up @@ -752,14 +765,14 @@ async def create_tasking(self, task: MythicTask) -> MythicTask:


async def get_file(task_id: int = None, callback_id: int = None, filename: str = None, limit_by_callback: bool = True,
max_results: int = 1,
file_id: str = None, get_contents: bool = True) -> dict:
max_results: int = 1, comment: str = None, file_id: str = None, get_contents: bool = True) -> dict:
"""
Get file data and contents by name (ex: from create_file and a specified saved_file_name parameter).
The search can be limited to just this callback (or the entire operation) and return just the latest or some number of matching results.
:param task_id: The ID number of the task performing this action (task.id) - if this isn't provided, the callback id must be provided
:param callback_id: The ID number of the callback for this action - if this isn't provided, the task_id must be provided
:param filename: The name of the file to search for (Case sensitive)
:param comment: The comment of the file to search for (Case insensitive)
:param file_id: If no filename specified, then can search for a specific file by this UUID
:param limit_by_callback: Set this to True if you only want to search for files that are tied to this callback. This is useful if you're doing this as part of another command that previously loaded files into this callback's memory.
:param max_results: The number of results you want back. 1 will be the latest file uploaded with that name, -1 will be all results.
Expand Down Expand Up @@ -2517,6 +2530,30 @@ async def add_commands_to_payload(payload_uuid: str, commands: [str]):
return {"status": "success"}


async def add_commands_to_callback(task_id: int, commands: [str]):
"""
Register additional commands that are in the callback. This is useful if a user selects to load script_only commands, so you want to inform mythic that the command is now available, but maybe not actually send anything down to the agent.
:param task_id: The ID of the task that's loading commands.
:param commands: An array of command names that should be added to this payload.
:return: Success or Error
"""
try:
task = await app.db_objects.get(db_model.task_query, id=task_id)
except Exception as e:
return {"status": "error", "error": "Callback not found"}
try:
for cmd in commands:
command = await app.db_objects.get(db_model.command_query, cmd=cmd, payload_type=task.callback.registered_payload.payload_type)
await app.db_objects.get_or_create(db_model.LoadedCommands,
callback=task.callback,
command=command,
operator=task.operator,
version=command.version)
except Exception as e:
return {"status": "error", "error": "Failed to find command or load it: " + str(e)}
return {"status": "success"}


async def update_loaded_commands(task_id: int, commands: [str], add: bool = None, remove: bool = None):
"""
Add or Remove loaded commands for the callback associated with task_id
Expand Down Expand Up @@ -3336,7 +3373,8 @@ def get_rpc_functions():
"get_responses": get_responses,
"get_task_for_id": get_task_for_id,
"get_commands": get_commands,
"add_command_to_payload": add_commands_to_payload,
"add_commands_to_payload": add_commands_to_payload,
"add_commands_to_callback": add_commands_to_callback,
"create_agentstorage": create_agentstorage,
"get_agentstorage": get_agentstorage,
"delete_agentstorage": delete_agentstorage,
Expand Down
2 changes: 1 addition & 1 deletion mythic-docker/app/api/response_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ async def background_process_agent_responses(agent_responses: dict, callback: db
)
asyncio.create_task(log_to_siem(mythic_object=resp, mythic_source="response_new"))
task.timestamp = datetime.datetime.utcnow()
logger.info(f"Setting task status to: {task.status} with completion status: {task.completed}")
logger.info(f"Setting task {task.id} status to: {task.status} with completion status: {task.completed}")
await app.db_objects.update(task)
if marked_as_complete:
asyncio.create_task(check_and_issue_task_callback_functions(task))
Expand Down
23 changes: 15 additions & 8 deletions mythic-docker/app/api/task_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,13 +1181,15 @@ async def check_and_issue_task_callback_functions(taskOriginal: Task, task_compl
from app.api.operation_api import send_all_operations_message
subtask_triggered_task_completion = False
task = await app.db_objects.get(db_model.task_query, id=taskOriginal.id)
logger.info(f"issuing task callback functions for task {task.id} with status: {task.status}")
if updating_task is not None:
#logger.info("updating_task is not None, updating piece is: " + updating_piece)
updatingTask = await app.db_objects.get(db_model.task_query, id=updating_task)
if updating_piece == "subtask_callback_function_completed":
if updating_piece == "subtask_callback_function_completed" and not updatingTask.subtask_callback_function_completed:
updatingTask.subtask_callback_function_completed = True
if updatingTask.status.startswith("Error: "):
updatingTask.status = "completed"
#if updatingTask.status.startswith("Error: "):
# updatingTask.status = "completed"
updatingTask.completed = True
await app.db_objects.update(updatingTask)
# task's subtask just completed. check to see if there's anything else that needs to be handled
# i.e. task might now be done and potentially need its completion handler addressed
Expand Down Expand Up @@ -1230,10 +1232,11 @@ async def check_and_issue_task_callback_functions(taskOriginal: Task, task_compl
#logger.info(
# f"Still have {group_tasks} group tasks for group {updatingTask.subtask_group_name} that need to be completed")
return
elif updating_piece == "group_callback_function_completed":
elif updating_piece == "group_callback_function_completed" and not updatingTask.group_callback_function_completed:
updatingTask.group_callback_function_completed = True
if updatingTask.status.startswith("Error: "):
updatingTask.status = "completed"
updatingTask.completed = True
#if updatingTask.status.startswith("Error: "):
# updatingTask.status = "completed"
await app.db_objects.update(updatingTask)
# we need to update all of the other tasks in that group to the same thing
groupTasks = await app.db_objects.execute(db_model.task_query.where(
Expand Down Expand Up @@ -1275,7 +1278,7 @@ async def check_and_issue_task_callback_functions(taskOriginal: Task, task_compl
"error in completed_callback_function not None submit_task_callback_to_container: " + status[
"error"])
return
if updating_task == task.id and updating_piece == "completed_callback_function_completed":
if updating_task == task.id and updating_piece == "completed_callback_function_completed" and not task.completed_callback_function_completed:
# we just got back from executing this function for this task
logger.info("updating_task == task.id and updating_piece == completed_callback_function_completed")
task.completed_callback_function_completed = True
Expand Down Expand Up @@ -1343,7 +1346,9 @@ async def check_and_issue_task_callback_functions(taskOriginal: Task, task_compl
# this task is done, there's a parent task, and we didn't kick off additional tasks
if task.parent_task.command.script_only:
task.parent_task.completed = True
task.parent_task.status = "completed"
if task.parent_task.status == "preprocessing":
logger.info(f"updating parent task, {task.parent_task.id} to completed")
task.parent_task.status = "completed"
await app.db_objects.update(task.parent_task)
# parent task is done, now process it for completion handlers and such
await check_and_issue_task_callback_functions(task.parent_task)
Expand Down Expand Up @@ -1694,6 +1699,7 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user
return {"status": "error", "error": "Payload Type container not running"}
if task.callback.registered_payload.payload_type.container_running:
rabbit_message = {"params": task.params, "command": task.command.cmd, "task": task.to_json()}
#logger.info(f"rabbitmq_message to container with task status: {task.status}, {rabbit_message['task']['status']}")
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)
Expand All @@ -1713,6 +1719,7 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user
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]
# by default tasks are created in a preprocessing state,
#logger.info(js.dumps(rabbit_message, indent=4))
result = await send_pt_rabbitmq_message(
task.callback.registered_payload.payload_type.ptype,
"task_callback_function",
Expand Down
14 changes: 7 additions & 7 deletions mythic-react-docker/mythic/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
{
"files": {
"main.js": "/new/static/js/main.5fce6e23.chunk.js",
"main.js.map": "/new/static/js/main.5fce6e23.chunk.js.map",
"main.js": "/new/static/js/main.e252ba08.chunk.js",
"main.js.map": "/new/static/js/main.e252ba08.chunk.js.map",
"runtime-main.js": "/new/static/js/runtime-main.7a88a52c.js",
"runtime-main.js.map": "/new/static/js/runtime-main.7a88a52c.js.map",
"static/css/2.b0c8ef5b.chunk.css": "/new/static/css/2.b0c8ef5b.chunk.css",
"static/js/2.5a512fc3.chunk.js": "/new/static/js/2.5a512fc3.chunk.js",
"static/js/2.5a512fc3.chunk.js.map": "/new/static/js/2.5a512fc3.chunk.js.map",
"static/js/2.e9dbcd84.chunk.js": "/new/static/js/2.e9dbcd84.chunk.js",
"static/js/2.e9dbcd84.chunk.js.map": "/new/static/js/2.e9dbcd84.chunk.js.map",
"index.html": "/new/index.html",
"static/css/2.b0c8ef5b.chunk.css.map": "/new/static/css/2.b0c8ef5b.chunk.css.map",
"static/js/2.5a512fc3.chunk.js.LICENSE.txt": "/new/static/js/2.5a512fc3.chunk.js.LICENSE.txt",
"static/js/2.e9dbcd84.chunk.js.LICENSE.txt": "/new/static/js/2.e9dbcd84.chunk.js.LICENSE.txt",
"static/media/mythic.7189479f.svg": "/new/static/media/mythic.7189479f.svg",
"static/media/mythic_red_small.793b41cc.svg": "/new/static/media/mythic_red_small.793b41cc.svg"
},
"entrypoints": [
"static/js/runtime-main.7a88a52c.js",
"static/css/2.b0c8ef5b.chunk.css",
"static/js/2.5a512fc3.chunk.js",
"static/js/main.5fce6e23.chunk.js"
"static/js/2.e9dbcd84.chunk.js",
"static/js/main.e252ba08.chunk.js"
]
}
Loading

0 comments on commit 1d1d428

Please sign in to comment.