mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
reworked file browser to better handle large data, registration now creates deactivated accounts until an admin activates them, postgres password now randomized on first start in .env file and shared via docker-compose, can now filter by tasks with comments, payloads management now can show automatically generated payloads via a toggle, further reduced what operators can do without an active operation
This commit is contained in:
@@ -1 +1,2 @@
|
||||
COMPOSE_PROJECT_NAME=apfell
|
||||
COMPOSE_PROJECT_NAME=mythic
|
||||
POSTGRES_PASSWORD=super_secret_mythic_user_password
|
||||
@@ -9,7 +9,7 @@ A cross-platform, post-exploit, red teaming framework built with python3, docker
|
||||
* Objective By the Sea 2019 talk on JXA: https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf
|
||||
* Objective By the sea 2019 Video: https://www.youtube.com/watch?v=E-QEsGsq3uI&list=PLliknDIoYszvTDaWyTh6SYiTccmwOsws8&index=17
|
||||
|
||||
* Current Version: 2.1.13
|
||||
* Current Version: 2.1.14
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ services:
|
||||
labels:
|
||||
NAME: "mythic_postgres"
|
||||
restart: on-failure
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
rabbitmq:
|
||||
build: ./rabbitmq-docker
|
||||
container_name: mythic_rabbitmq
|
||||
@@ -27,6 +29,8 @@ services:
|
||||
- ./mythic-docker:/Mythic
|
||||
labels:
|
||||
NAME: "mythic_server"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
depends_on:
|
||||
- postgres
|
||||
- rabbitmq
|
||||
|
||||
@@ -32,11 +32,11 @@ languageName = "English"
|
||||
[[Languages.en.menu.shortcuts]]
|
||||
name = "<i class='fab fa-fw fa-github'></i> GitHub repo"
|
||||
identifier = "ds"
|
||||
url = "https://github.com/its-a-feature/Apfell"
|
||||
url = "https://github.com/its-a-feature/Mythic"
|
||||
weight = 10
|
||||
|
||||
[[Languages.en.menu.shortcuts]]
|
||||
name = "<i class='fas fa-fw fa-bookmark'></i> Mythic Documentation"
|
||||
identifier = "hugodoc"
|
||||
url = "https://docs.apfell.net/"
|
||||
url = "https://docs.mythic-c2.net/"
|
||||
weight = 20
|
||||
|
||||
@@ -8,6 +8,7 @@ import ujson as json
|
||||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
import os
|
||||
|
||||
# --------------------------------------------
|
||||
# --------------------------------------------
|
||||
@@ -39,7 +40,7 @@ listen_ip = (
|
||||
)
|
||||
db_name = "mythic_db"
|
||||
db_user = "mythic_user"
|
||||
db_pass = "super_secret_mythic_user_password"
|
||||
db_pass = os.environ['POSTGRES_PASSWORD']
|
||||
max_log_count = (
|
||||
1 # if log_size > 0, rotate and make a max of max_log_count files to hold logs
|
||||
)
|
||||
|
||||
@@ -320,6 +320,8 @@ async def get_access_log_data(request, user):
|
||||
entries = 1000
|
||||
page = 1
|
||||
total_entries = 0
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part an of operation to view this"})
|
||||
try:
|
||||
if request.method == "POST":
|
||||
data = request.json
|
||||
@@ -369,7 +371,10 @@ async def get_access_log_data(request, user):
|
||||
["auth:user", "auth:apitoken_user"], False
|
||||
) # user or user-level api token are ok
|
||||
async def download_access_log_data(request, user):
|
||||
if os.path.exists("mythic_access.log"):
|
||||
return await file("mythic_access.log", filename="mythic_access.log")
|
||||
if user["current_operation"] != "":
|
||||
if os.path.exists("mythic_access.log"):
|
||||
return await file("mythic_access.log", filename="mythic_access.log")
|
||||
else:
|
||||
return json({"status": "error", "error": "file does not exist"})
|
||||
else:
|
||||
return json({"status": "error", "error": "file does not exist"})
|
||||
return json({"status": "error", "error": "Only an admin"})
|
||||
|
||||
@@ -35,7 +35,7 @@ async def create_artifact(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot add base artifacts"}
|
||||
)
|
||||
@@ -68,7 +68,7 @@ async def update_artifact(request, user, id):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot modify base artifacts"}
|
||||
)
|
||||
@@ -102,7 +102,7 @@ async def delete_artifact(request, user, id):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot remove base artifacts"}
|
||||
)
|
||||
@@ -309,7 +309,7 @@ async def remove_artifact_tasks(request, user, aid):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot remove task artifacts"}
|
||||
)
|
||||
@@ -339,7 +339,7 @@ async def create_artifact_task_manually(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot create task artifacts"}
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ async def create_browserscript(request, user):
|
||||
except Exception as e:
|
||||
return json({"status": "error", "error": "failed to parse json"})
|
||||
pieces = {}
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot create browserscripts"}
|
||||
)
|
||||
@@ -143,7 +143,7 @@ async def modify_browserscript(request, user, bid):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot modify browser scripts"}
|
||||
)
|
||||
@@ -317,7 +317,7 @@ async def remove_browserscript(request, user, bid):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot remove browser scripts"}
|
||||
)
|
||||
@@ -361,6 +361,10 @@ async def import_browserscript(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot modify browser scripts"}
|
||||
)
|
||||
# get json data
|
||||
try:
|
||||
if request.files:
|
||||
|
||||
@@ -107,7 +107,7 @@ async def start_c2profile(request, info, user):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot start c2 profiles"}
|
||||
)
|
||||
@@ -134,7 +134,7 @@ async def stop_c2profile(request, info, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Spectators cannot stop c2 profiles"})
|
||||
return await stop_c2profile_func(info, user["username"])
|
||||
|
||||
@@ -164,7 +164,7 @@ async def status_c2profile(request, info, user):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot query c2 profiles"}
|
||||
)
|
||||
@@ -201,6 +201,8 @@ async def download_container_file_for_c2profiles(request, info, user):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return json({"status": "error", "error": "failed to find C2 Profile"})
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of an operation to see this"})
|
||||
try:
|
||||
status = await send_c2_rabbitmq_message(
|
||||
profile.name, "get_config", "", user["username"]
|
||||
@@ -225,7 +227,7 @@ async def upload_container_file_for_c2profiles(request, info, user):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{
|
||||
"status": "error",
|
||||
@@ -263,7 +265,7 @@ async def delete_c2profile(request, info, user):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot delete c2 profiles"}
|
||||
)
|
||||
@@ -390,7 +392,7 @@ async def update_c2_profile(request, profile, user):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot modify c2 profiles"}
|
||||
)
|
||||
@@ -430,7 +432,7 @@ async def save_c2profile_parameter_value_instance(request, info, user):
|
||||
)
|
||||
data = request.json # all of the name,value pairs instances we want to save
|
||||
try:
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{
|
||||
"status": "error",
|
||||
@@ -568,7 +570,7 @@ async def delete_c2profile_parameter_value_instance(request, instance_name, user
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{
|
||||
"status": "error",
|
||||
|
||||
@@ -32,6 +32,7 @@ import threading
|
||||
from time import sleep as tsleep
|
||||
import socket
|
||||
from app.api.siem_logger import log_to_siem
|
||||
import sys
|
||||
|
||||
|
||||
@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["GET"])
|
||||
@@ -264,8 +265,8 @@ async def parse_agent_message(data: str, request):
|
||||
return "", 404, new_callback, agent_uuid
|
||||
# now to parse out what we're doing, everything is decrypted at this point
|
||||
# shuttle everything out to the appropriate api files for processing
|
||||
if keep_logs:
|
||||
logger.info("Agent -> Mythic: " + js.dumps(decrypted))
|
||||
#if keep_logs:
|
||||
# logger.info("Agent -> Mythic: " + js.dumps(decrypted))
|
||||
# print(decrypted)
|
||||
response_data = {}
|
||||
if decrypted["action"] == "get_tasking":
|
||||
@@ -321,7 +322,7 @@ async def parse_agent_message(data: str, request):
|
||||
"type": "AES256",
|
||||
}
|
||||
else:
|
||||
return "", 404, new_callback, UUID
|
||||
return "", 404, new_callback, agent_uuid
|
||||
# staging is it's own thing, so return here instead of following down
|
||||
elif decrypted["action"] == "staging_dh":
|
||||
response_data, staging_info = await staging_dh(decrypted, UUID)
|
||||
@@ -332,13 +333,13 @@ async def parse_agent_message(data: str, request):
|
||||
"type": "AES256",
|
||||
}
|
||||
else:
|
||||
return "", 404, new_callback, UUID
|
||||
return "", 404, new_callback, agent_uuid
|
||||
elif decrypted["action"] == "update_info":
|
||||
response_data = await update_callback(decrypted, UUID)
|
||||
agent_uuid = UUID
|
||||
else:
|
||||
await send_all_operations_message("Unknown action:" + str(decrypted["action"]), "warning")
|
||||
return "", 404, new_callback, ""
|
||||
return "", 404, new_callback, agent_uuid
|
||||
# now that we have the right response data, format the response message
|
||||
if (
|
||||
"delegates" in decrypted
|
||||
@@ -373,8 +374,9 @@ async def parse_agent_message(data: str, request):
|
||||
response_data["delegates"].append({d_uuid: del_message})
|
||||
# special encryption will be handled by the appropriate stager call
|
||||
# base64 ( UID + ENC(response_data) )
|
||||
if keep_logs:
|
||||
logger.info("Mythic -> Agent: " + js.dumps(response_data))
|
||||
#if keep_logs:
|
||||
# logger.info("Mythic -> Agent: " + js.dumps(response_data))
|
||||
#print(response_data)
|
||||
final_msg = await crypt.encrypt_message(response_data, enc_key, UUID)
|
||||
#if enc_key["type"] is None:
|
||||
# return (
|
||||
@@ -387,11 +389,13 @@ async def parse_agent_message(data: str, request):
|
||||
# data=js.dumps(response_data).encode(), key=enc_key["enc_key"]
|
||||
# )
|
||||
# return base64.b64encode(UUID.encode() + enc_data).decode(), 200
|
||||
return final_msg, 200, new_callback, UUID
|
||||
return final_msg, 200, new_callback, agent_uuid
|
||||
except Exception as e:
|
||||
print(sys.exc_info()[-1].tb_lineno)
|
||||
print("callback.py: " + str(e))
|
||||
await send_all_operations_message(f"Exception dealing with message: {str(decoded)}\nfrom {request.host} as {request.method} method with headers: \n{request.headers}",
|
||||
"warning")
|
||||
return "", 404, new_callback, UUID
|
||||
return "", 404, new_callback, agent_uuid
|
||||
|
||||
|
||||
@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["POST"])
|
||||
@@ -405,7 +409,7 @@ async def create_manual_callback(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot create manual callbacks"}
|
||||
)
|
||||
@@ -992,7 +996,7 @@ async def remove_graph_edge(request, id, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot remove graph edges"}
|
||||
)
|
||||
@@ -1169,17 +1173,17 @@ async def send_socks_data(data, callback: Callback):
|
||||
for d in data:
|
||||
if callback.id in cached_socks:
|
||||
msg = js.dumps(d).encode()
|
||||
print("******* SENDING DATA BACK TO PROXYCHAINS *****")
|
||||
print(msg)
|
||||
#print("******* SENDING DATA BACK TO PROXYCHAINS *****")
|
||||
#print(msg)
|
||||
msg = int.to_bytes(len(msg), 4, "big") + msg
|
||||
total_msg += msg
|
||||
# cached_socks[callback.id]['socket'].sendall(int.to_bytes(len(msg), 4, "big"))
|
||||
else:
|
||||
print("****** NO CACHED SOCKS, MUST BE CLOSED *******")
|
||||
#else:
|
||||
#print("****** NO CACHED SOCKS, MUST BE CLOSED *******")
|
||||
cached_socks[callback.id]["socket"].sendall(total_msg)
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
print("******** EXCEPTION IN SEND SOCKS DATA *****\n{}".format(str(e)))
|
||||
#print("******** EXCEPTION IN SEND SOCKS DATA *****\n{}".format(str(e)))
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
@@ -1193,9 +1197,9 @@ async def get_socks_data(callback: Callback):
|
||||
#print("Just got socks data to give to agent")
|
||||
except:
|
||||
break
|
||||
if len(data) > 0:
|
||||
print("******* SENDING THE FOLLOWING TO THE AGENT ******")
|
||||
print(data)
|
||||
#if len(data) > 0:
|
||||
#print("******* SENDING THE FOLLOWING TO THE AGENT ******")
|
||||
#print(data)
|
||||
return data
|
||||
|
||||
|
||||
@@ -1222,27 +1226,12 @@ def thread_read_socks(port: int, callback_id: int) -> None:
|
||||
elif len(size) == 0:
|
||||
tsleep(1)
|
||||
continue
|
||||
else:
|
||||
print(
|
||||
"############# only got {} bytes ############".format(
|
||||
str(len(size))
|
||||
)
|
||||
)
|
||||
# print("now trying to read in: {} bytes".format(str(size)))
|
||||
msg = sock.recv(size)
|
||||
if len(msg) < size:
|
||||
print("########### didn't read the full size ########")
|
||||
# print("got socks data from user")
|
||||
try:
|
||||
cached_socks[callback_id]["queue"].append(js.loads(msg.decode()))
|
||||
print("just read from proxychains and added to queue for agent to pick up")
|
||||
#print("just read from proxychains and added to queue for agent to pick up")
|
||||
except Exception as d:
|
||||
print(
|
||||
"*" * 10
|
||||
+ "Got exception from appending data to cached_socks"
|
||||
+ "*" * 10
|
||||
)
|
||||
print(d)
|
||||
if callback_id not in cached_socks:
|
||||
#print("*" * 10 + "Got closing socket" + "*" * 10)
|
||||
sock.close()
|
||||
@@ -1339,7 +1328,7 @@ async def update_callback_web(request, id, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Spectators cannot update callbacks"})
|
||||
data = request.json
|
||||
try:
|
||||
@@ -1492,7 +1481,7 @@ async def remove_callback(request, id, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot make callbacks inactive"}
|
||||
)
|
||||
@@ -1561,7 +1550,7 @@ async def get_callback_keys(request, user, id):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Spectators cannot get callback keys"})
|
||||
try:
|
||||
query = await db_model.operation_query()
|
||||
|
||||
@@ -19,6 +19,8 @@ async def get_all_commands(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of a current operation to see this"})
|
||||
all_commands = []
|
||||
query = await db_model.command_query()
|
||||
commands = await db_objects.execute(
|
||||
@@ -50,6 +52,8 @@ async def check_command(request, user, ptype, cmd):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of a current operation to see this"})
|
||||
status = {"status": "success"}
|
||||
cmd = unquote_plus(cmd)
|
||||
try:
|
||||
@@ -97,6 +101,8 @@ async def get_all_parameters_for_command(request, user, id):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of a current operation to see this"})
|
||||
try:
|
||||
query = await db_model.command_query()
|
||||
command = await db_objects.get(query, id=id)
|
||||
@@ -124,6 +130,8 @@ async def get_all_attack_mappings_for_command(request, user, id):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of a current operation to see this"})
|
||||
try:
|
||||
query = await db_model.command_query()
|
||||
command = await db_objects.get(query, id=id)
|
||||
@@ -149,7 +157,7 @@ async def remove_attack_mapping_for_command(request, user, id, t_num):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot remove MITRE mappings"}
|
||||
)
|
||||
@@ -181,7 +189,7 @@ async def create_attack_mappings_for_command(request, user, id, t_num):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot add MITRE mappings"}
|
||||
)
|
||||
@@ -217,7 +225,7 @@ async def adjust_attack_mappings_for_command(request, user, id, t_num):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json(
|
||||
{"status": "error", "error": "Spectators cannot modify MITRE mappings"}
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ async def create_credential(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["view_mode"] == "spectator":
|
||||
if user["view_mode"] == "spectator" or user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Spectators cannot add credentials"})
|
||||
if user["current_operation"] != "":
|
||||
try:
|
||||
|
||||
@@ -316,6 +316,7 @@ async def create_filemeta_in_database_func(data):
|
||||
operation = task.callback.operation
|
||||
except Exception as e:
|
||||
print("{} {}".format(str(sys.exc_info()[-1].tb_lineno), str(e)))
|
||||
print("file_api.py")
|
||||
return {"status": "error", "error": "failed to find task"}
|
||||
try:
|
||||
if "full_path" in data and data["full_path"] != "":
|
||||
@@ -387,6 +388,7 @@ async def create_filemeta_in_database_func(data):
|
||||
await log_to_siem(task.to_json(), mythic_object="file_screenshot")
|
||||
except Exception as e:
|
||||
print("{} {}".format(str(sys.exc_info()[-1].tb_lineno), str(e)))
|
||||
print("file_api.py")
|
||||
return {"status": "error", "error": "failed to create file"}
|
||||
status = {"status": "success"}
|
||||
return {**status, **filemeta.to_json()}
|
||||
|
||||
@@ -54,54 +54,17 @@ async def get_filebrowser_tree_for_operation(operation_name):
|
||||
try:
|
||||
query = await db_model.filebrowserobj_query()
|
||||
objs = await db_objects.execute(
|
||||
query.where(db_model.FileBrowserObj.operation == operation)
|
||||
)
|
||||
output = {}
|
||||
roots = []
|
||||
sorted_obj = []
|
||||
for o in objs:
|
||||
ojson = o.to_json()
|
||||
ojson["files"] = []
|
||||
try:
|
||||
for f in o.files:
|
||||
fjson = f.to_json()
|
||||
if f.task is not None and f.task.comment != "":
|
||||
fjson["comment"] = f.task.comment
|
||||
ojson["files"].append(fjson)
|
||||
except Exception as e:
|
||||
pass
|
||||
if ojson["parent"] is None:
|
||||
roots.append(ojson)
|
||||
else:
|
||||
sorted_obj.append(ojson)
|
||||
sorted_obj = sorted(sorted_obj, key=lambda i: i["parent"])
|
||||
for r in roots:
|
||||
# print(r)
|
||||
if r["host"] not in output:
|
||||
output[r["host"]] = {}
|
||||
# this is a root level node, there can be multiple
|
||||
output[r["host"]][r["name"]] = treelib.Tree()
|
||||
# print("adding root")
|
||||
# print(r)
|
||||
output[r["host"]][r["name"]].create_node(r["name"], r["id"], data=r)
|
||||
# print(output)
|
||||
# print("now to add in the children")
|
||||
for o in sorted_obj:
|
||||
for k, v in output[o["host"]].items():
|
||||
if o["parent_path"].startswith(k):
|
||||
# print("trying to add")
|
||||
# print(o)
|
||||
output[o["host"]][k].create_node(
|
||||
o["name"], o["id"], parent=o["parent"], data=o
|
||||
)
|
||||
query.where(
|
||||
(db_model.FileBrowserObj.operation == operation) &
|
||||
(db_model.FileBrowserObj.is_file == False) &
|
||||
(db_model.FileBrowserObj.parent == None)
|
||||
))
|
||||
final_output = {}
|
||||
for k, v in output.items():
|
||||
final_output[k] = {
|
||||
"children": [],
|
||||
"data": {"host": k, "is_file": False, "name": k},
|
||||
}
|
||||
for root, t in v.items():
|
||||
final_output[k]["children"].append(js.loads(t.to_json(with_data=True)))
|
||||
for e in objs:
|
||||
e_json = e.to_json()
|
||||
if e_json["host"] not in final_output:
|
||||
final_output[e_json["host"]] = []
|
||||
final_output[e_json["host"]].append(e_json)
|
||||
return {"status": "success", "output": final_output}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -119,7 +82,7 @@ async def store_response_into_filebrowserobj(operation, task, response):
|
||||
"status": "error",
|
||||
"error": "Failed to parse and handle file browser objects",
|
||||
}
|
||||
if "host" not in response:
|
||||
if "host" not in response or response["host"] == "" or response["host"] is None:
|
||||
response["host"] = task.callback.host
|
||||
# now that we have the immediate parent and all parent hierarchy create, deal with current obj and sub objects
|
||||
try:
|
||||
@@ -223,11 +186,14 @@ async def store_response_into_filebrowserobj(operation, task, response):
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
print(sys.exc_info()[-1].tb_lineno)
|
||||
print(e)
|
||||
print("file_browser_api.py: " + str(e))
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
async def add_upload_file_to_file_browser(operation, task, file, data):
|
||||
|
||||
if "full_path" not in data or data["full_path"] is None or data["full_path"] == "":
|
||||
return
|
||||
data["is_file"] = True
|
||||
data["permissions"] = {}
|
||||
data["success"] = True
|
||||
@@ -241,17 +207,18 @@ async def add_upload_file_to_file_browser(operation, task, file, data):
|
||||
full_path = PureWindowsPath(data["full_path"])
|
||||
data["name"] = full_path.name
|
||||
data["parent_path"] = str(full_path.parents[0])
|
||||
if "host" not in data:
|
||||
if "host" not in data or data["host"] is None or data["host"] == "":
|
||||
data["host"] = file.host
|
||||
await store_response_into_filebrowserobj(operation, task, data)
|
||||
try:
|
||||
await store_response_into_filebrowserobj(operation, task, data)
|
||||
fbo_query = await db_model.filebrowserobj_query()
|
||||
fbo = await db_objects.get(fbo_query, operation=operation,
|
||||
host=data["host"].encode("unicode-escape"),
|
||||
full_path=data["full_path"].encode("unicode-escape"))
|
||||
file.file_browser = fbo
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
print(sys.exc_info()[-1].tb_lineno)
|
||||
print("file_browser_api.py: " + str(e))
|
||||
return
|
||||
|
||||
|
||||
@@ -323,7 +290,7 @@ async def create_and_check_parents(operation, task, response):
|
||||
return parent_obj
|
||||
except Exception as e:
|
||||
print(sys.exc_info()[-1].tb_lineno)
|
||||
print(e)
|
||||
print("file_browser_api.py: " + str(e))
|
||||
return None
|
||||
|
||||
|
||||
@@ -543,3 +510,42 @@ async def get_filebrowsobj_permissions_by_path(request, user):
|
||||
"error": "failed to find that file browsing object in your current operation",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mythic.route(mythic.config["API_BASE"] + "/filebrowserobj/<fid:int>/files", methods=["GET"])
|
||||
@inject_user()
|
||||
@scoped(
|
||||
["auth:user", "auth:apitoken_user"], False
|
||||
) # user or user-level api token are ok
|
||||
async def get_filebrowsobj_files(request, user, fid):
|
||||
if user["auth"] not in ["access_token", "apitoken"]:
|
||||
abort(
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
query = await db_model.operation_query()
|
||||
operation = await db_objects.get(query, name=user["current_operation"])
|
||||
query = await db_model.filebrowserobj_query()
|
||||
files = await db_objects.execute(query.where(
|
||||
(db_model.FileBrowserObj.operation == operation) &
|
||||
(db_model.FileBrowserObj.parent == fid)
|
||||
))
|
||||
except Exception as e:
|
||||
return json(
|
||||
{
|
||||
"status": "error",
|
||||
"error": "failed to find that file browsing object in your current operation",
|
||||
}
|
||||
)
|
||||
try:
|
||||
output = []
|
||||
for f in files:
|
||||
f_json = f.to_json()
|
||||
if f.is_file:
|
||||
output.append({f_json["name"]: {"data": f_json}})
|
||||
else:
|
||||
output.append({f_json["name"]: {"data": f_json, "children": []}})
|
||||
return json({"status": "success", "files": output})
|
||||
except Exception as e:
|
||||
return json({"status": "error", "error": str(e)})
|
||||
|
||||
@@ -60,6 +60,8 @@ async def create_operator(request, user):
|
||||
data = request.json
|
||||
if user["view_mode"] == "spectator":
|
||||
return json({"status": "error", "error": "Spectators cannot create users"})
|
||||
if not user["admin"]:
|
||||
return json({"status": "error", "error": "Only admins can create new users"})
|
||||
if "username" not in data or data["username"] == "":
|
||||
return json({"status": "error", "error": '"username" field is required'})
|
||||
if not isinstance(data["username"], str) or not len(data["username"]):
|
||||
@@ -70,11 +72,10 @@ async def create_operator(request, user):
|
||||
}
|
||||
)
|
||||
password = await crypto.hash_SHA512(data["password"])
|
||||
admin = False # cannot create a user initially as admin
|
||||
# we need to create a new user
|
||||
try:
|
||||
new_operator = await db_objects.create(
|
||||
Operator, username=data["username"], password=password, admin=admin
|
||||
Operator, username=data["username"], password=password, admin=False
|
||||
)
|
||||
success = {"status": "success"}
|
||||
new_user = new_operator.to_json()
|
||||
@@ -237,6 +238,8 @@ async def remove_operator(request, oid, user):
|
||||
return json({"status": "error", "error": "failed to find operator"})
|
||||
try:
|
||||
op.deleted = True
|
||||
op.active = False
|
||||
op.admin = False
|
||||
await db_objects.update(op)
|
||||
success = {"status": "success"}
|
||||
return json({**success, **op.to_json()})
|
||||
|
||||
@@ -31,6 +31,8 @@ async def get_all_payloadtypes(request, user):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of an operation to view this"})
|
||||
query = await db_model.payloadtype_query()
|
||||
wrapquery = await db_model.wrappedpayloadtypes_query()
|
||||
payloads = await db_objects.execute(
|
||||
@@ -80,6 +82,8 @@ async def get_one_payloadtype(request, user, ptype):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of an operation to view this"})
|
||||
query = await db_model.payloadtype_query()
|
||||
payloadtype = await db_objects.get(query, id=ptype)
|
||||
query = await db_model.buildparameter_query()
|
||||
@@ -121,6 +125,8 @@ async def update_one_payloadtype(request, user, ptype):
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
try:
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Not part of an operation"})
|
||||
query = await db_model.payloadtype_query()
|
||||
payloadtype = await db_objects.get(query, id=ptype)
|
||||
data = request.json
|
||||
@@ -190,6 +196,8 @@ async def get_commands_for_payloadtype(request, user, ptype):
|
||||
status_code=403,
|
||||
message="Cannot access via Cookies. Use CLI or access via JS in browser",
|
||||
)
|
||||
if user["current_operation"] == "":
|
||||
return json({"status": "error", "error": "Must be part of a current operation to see this"})
|
||||
try:
|
||||
query = await db_model.payloadtype_query()
|
||||
payloadtype = await db_objects.get(query, id=ptype)
|
||||
|
||||
@@ -1086,6 +1086,12 @@ async def rabbit_heartbeat_callback(message: aio_pika.IncomingMessage):
|
||||
# just listen for c2 heartbeats and update the database as necessary
|
||||
async def start_listening():
|
||||
logger.debug("starting to consume rabbitmq messages")
|
||||
task = None
|
||||
task2 = None
|
||||
task3 = None
|
||||
task4 = None
|
||||
task5 = None
|
||||
tasks = [task, task2, task3, task4, task5]
|
||||
try:
|
||||
task = asyncio.ensure_future(connect_and_consume_c2())
|
||||
task2 = asyncio.ensure_future(connect_and_consume_heartbeats())
|
||||
@@ -1094,6 +1100,9 @@ async def start_listening():
|
||||
task5 = asyncio.ensure_future(connect_and_consume_c2_rpc())
|
||||
await asyncio.wait_for([task, task2, task3, task4, task5], None)
|
||||
except Exception as e:
|
||||
for t in tasks:
|
||||
if t is not None:
|
||||
task.cancel()
|
||||
await asyncio.sleep(3)
|
||||
|
||||
|
||||
|
||||
@@ -521,9 +521,10 @@ async def post_agent_response(agent_message, UUID):
|
||||
if host != file_meta.host:
|
||||
file_meta.host = host.encode("unicode-escape")
|
||||
await db_objects.update(file_meta)
|
||||
await add_upload_file_to_file_browser(task.callback.operation, task, file_meta,
|
||||
{"host": host,
|
||||
"full_path": parsed_response["full_path"]})
|
||||
if file_meta.full_path != "":
|
||||
await add_upload_file_to_file_browser(task.callback.operation, task, file_meta,
|
||||
{"host": host,
|
||||
"full_path": parsed_response["full_path"]})
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.exception(
|
||||
|
||||
@@ -672,6 +672,8 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
|
||||
# it's not registered, so check the free clear tasking
|
||||
if data["command"] == "clear":
|
||||
# this means we're going to be clearing out some tasks depending on our access levels
|
||||
if "params" not in data:
|
||||
data["params"] = ""
|
||||
task = await db_objects.create(
|
||||
Task,
|
||||
callback=cb,
|
||||
@@ -695,7 +697,7 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
|
||||
+ ": "
|
||||
+ t["command"]
|
||||
+ " "
|
||||
+ t["params"]
|
||||
+ t["original_params"]
|
||||
)
|
||||
await db_objects.create(Response, task=task, response=rsp)
|
||||
return {"status": "success", **task.to_json()}
|
||||
|
||||
@@ -4,7 +4,7 @@ from jinja2 import Environment, PackageLoader
|
||||
from sanic_jwt.decorators import scoped, inject_user
|
||||
from app.routes.routes import respect_pivot
|
||||
|
||||
env = Environment(loader=PackageLoader("app", "templates"))
|
||||
env = Environment(loader=PackageLoader("app", "templates"), autoescape=True)
|
||||
|
||||
|
||||
@mythic.route("/payloads/", methods=["GET"])
|
||||
|
||||
@@ -4,7 +4,7 @@ from jinja2 import Environment, PackageLoader
|
||||
from sanic_jwt.decorators import scoped, inject_user
|
||||
from app.routes.routes import respect_pivot
|
||||
|
||||
env = Environment(loader=PackageLoader("app", "templates"))
|
||||
env = Environment(loader=PackageLoader("app", "templates"), autoescape=True)
|
||||
|
||||
|
||||
@mythic.route("/reporting/full_timeline")
|
||||
|
||||
@@ -41,7 +41,7 @@ import glob
|
||||
from app.api.callback_api import start_all_socks_after_restart
|
||||
import sys
|
||||
|
||||
env = Environment(loader=PackageLoader("app", "templates"))
|
||||
env = Environment(loader=PackageLoader("app", "templates"), autoescape=True)
|
||||
|
||||
|
||||
async def respect_pivot(my_links, request):
|
||||
@@ -87,6 +87,7 @@ class Login(BaseEndpoint):
|
||||
async def get(self, request):
|
||||
form = LoginForm(request)
|
||||
errors = {}
|
||||
successful_creation = request.args.pop("success", False)
|
||||
errors["username_errors"] = "<br>".join(form.username.errors)
|
||||
errors["password_errors"] = "<br>".join(form.password.errors)
|
||||
template = env.get_template("login.html")
|
||||
@@ -94,6 +95,7 @@ class Login(BaseEndpoint):
|
||||
links=await respect_pivot(links, request),
|
||||
form=form,
|
||||
errors=errors,
|
||||
successful_creation=successful_creation,
|
||||
config={},
|
||||
view_utc_time=False,
|
||||
http="https" if use_ssl else "http",
|
||||
@@ -112,7 +114,7 @@ class Login(BaseEndpoint):
|
||||
user = await db_objects.get(query, username=username)
|
||||
if await user.check_password(password):
|
||||
if not user.active:
|
||||
form.username.errors = ["account is deactivated, cannot log in"]
|
||||
form.username.errors = ["Account is not active, cannot log in"]
|
||||
else:
|
||||
try:
|
||||
user.last_login = datetime.datetime.utcnow()
|
||||
@@ -219,10 +221,8 @@ class Register(BaseEndpoint):
|
||||
# we need to create a new user
|
||||
try:
|
||||
user = await db_objects.create(
|
||||
Operator, username=username, password=password
|
||||
Operator, username=username, password=password, admin=False, active=False
|
||||
)
|
||||
user.last_login = datetime.datetime.utcnow()
|
||||
await db_objects.update(user) # update the last login time to be now
|
||||
query = await db_model.operation_query()
|
||||
operations = await db_objects.execute(query)
|
||||
for o in operations:
|
||||
@@ -233,47 +233,18 @@ class Register(BaseEndpoint):
|
||||
message="New user {} created".format(user.username),
|
||||
)
|
||||
await set_default_scripts(user)
|
||||
# print(result)
|
||||
# generate JWT token to be stored in a cookie
|
||||
access_token, output = await self.responses.get_access_token_output(
|
||||
request,
|
||||
{"user_id": user.id, "auth": "cookie"},
|
||||
self.config,
|
||||
self.instance,
|
||||
)
|
||||
refresh_token = await self.instance.auth.generate_refresh_token(
|
||||
request, {"user_id": user.id, "auth": "cookie"}
|
||||
)
|
||||
output.update({self.config.refresh_token_name(): refresh_token})
|
||||
# we want to make sure to store access/refresh token in JS before moving into the rest of the app
|
||||
template = env.get_template("register.html")
|
||||
content = template.render(
|
||||
links=await respect_pivot(links, request),
|
||||
form=form,
|
||||
errors=errors,
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
config={},
|
||||
view_utc_time=False,
|
||||
http="https" if use_ssl else "http",
|
||||
ws="wss" if use_ssl else "ws",
|
||||
)
|
||||
resp = response.html(content)
|
||||
resp.cookies[self.config.cookie_access_token_name()] = access_token
|
||||
resp.cookies[self.config.cookie_access_token_name()]["httponly"] = True
|
||||
resp.cookies[self.config.cookie_refresh_token_name()] = refresh_token
|
||||
resp.cookies[self.config.cookie_refresh_token_name()]["httponly"] = True
|
||||
return resp
|
||||
return response.redirect("/login?success=true")
|
||||
except Exception as e:
|
||||
# failed to insert into database
|
||||
print(e)
|
||||
form.username.errors = ["Username already exists"]
|
||||
form.username.errors = ["Failed to create user"]
|
||||
errors["username_errors"] = "<br>".join(form.username.errors)
|
||||
template = env.get_template("register.html")
|
||||
content = template.render(
|
||||
links=await respect_pivot(links, request),
|
||||
form=form,
|
||||
errors=errors,
|
||||
successful_creation=False,
|
||||
config={},
|
||||
view_utc_time=False,
|
||||
http="https" if use_ssl else "http",
|
||||
|
||||
@@ -4,7 +4,7 @@ from jinja2 import Environment, PackageLoader
|
||||
from sanic_jwt.decorators import scoped, inject_user
|
||||
from app.routes.routes import respect_pivot
|
||||
|
||||
env = Environment(loader=PackageLoader("app", "templates"))
|
||||
env = Environment(loader=PackageLoader("app", "templates"), autoescape=True)
|
||||
|
||||
|
||||
@mythic.route("/services/host_file", methods=["GET"])
|
||||
|
||||
@@ -913,8 +913,8 @@ async def ws_payloads_current_operation(request, ws, user):
|
||||
payloads = await db_objects.execute(
|
||||
query.where(
|
||||
(Payload.operation == operation)
|
||||
& (Payload.deleted == False)
|
||||
& (Payload.auto_generated == False)
|
||||
#& (Payload.deleted == False)
|
||||
#& (Payload.auto_generated == False)
|
||||
).order_by(Payload.id)
|
||||
)
|
||||
for p in payloads:
|
||||
@@ -1362,13 +1362,15 @@ async def ws_commands(request, ws):
|
||||
|
||||
# basic info of just new commmands for the payload types page
|
||||
@mythic.websocket("/ws/commands")
|
||||
@inject_user()
|
||||
@scoped(
|
||||
["auth:user", "auth:apitoken_user"], False
|
||||
) # user or user-level api token are ok
|
||||
async def ws_commands(request, ws):
|
||||
async def ws_commands(request, ws, user):
|
||||
if not await valid_origin_header(request):
|
||||
return
|
||||
|
||||
if user["current_operation"] == "":
|
||||
return
|
||||
try:
|
||||
async with aiopg.create_pool(mythic.config["DB_POOL_CONNECT_STRING"]) as pool:
|
||||
async with pool.acquire() as conn:
|
||||
|
||||
@@ -725,7 +725,7 @@ li span.badge {
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<font size="4" style="float:right;padding-right:10px">v2.1.13</font>
|
||||
<font size="4" style="float:right;padding-right:10px">v2.1.14</font>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -380,9 +380,6 @@
|
||||
</div>
|
||||
<div :key="'outputcontent' + task.id" class="collapse" :id="'cardbody' + task.id" >
|
||||
<div :key="'outputcontentcardbody' + task.id" class="response-background card-body shadow" style="padding-top:0;padding-bottom: 0" >
|
||||
<div :key="'showparams' + task.id" v-if="task.show_params">
|
||||
Modified Parameters: [[task.params]]
|
||||
</div>
|
||||
<span :key="'usescripted' + task.id" v-if="task.hasOwnProperty('scripted') && task.use_scripted" v-html="task.scripted"></span>
|
||||
<span v-else :key="'useregular' + task.id" class="card-text" v-for="rsp in task.response" :key="rsp.id" :id="'response' + rsp.id" style="white-space: pre-wrap"><span class="response"><pre>[[rsp.response]]</pre></span></span>
|
||||
</div>
|
||||
@@ -586,7 +583,7 @@
|
||||
<i class="far fa-file"></i>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="fas fa-folder" :style="Object.values(f)[0].children !== undefined ? 'color:#f1d592' : ''"></i>
|
||||
<i class="fas fa-folder" style="color:#f1d592"></i>
|
||||
</template>
|
||||
<template v-if="Object.values(f)[0].data.deleted">
|
||||
<span style="text-decoration:line-through">[[Object.values(f)[0].data.name]] (deleted from target)</span>
|
||||
@@ -600,7 +597,7 @@
|
||||
<template v-else-if="Object.values(f)[0].data.success === false">
|
||||
<i style="color:red" class="fas fa-exclamation-circle"></i>
|
||||
</template>
|
||||
<template v-if="Object.values(f)[0]['data']['files'].length > 0">
|
||||
<template v-if="Object.values(f)[0]['data']['files'] !== undefined && Object.values(f)[0]['data']['files'].length > 0">
|
||||
<a class="btn btn-md" :href='get_latest_download_path(Object.values(f)[0]["data"]["files"])' style="color:green"><i class="fas fa-download fa-lg"></i></a>
|
||||
<span style="color:indianred">[[ get_latest_download_chunk_count(Object.values(f)[0]["data"]["files"]) ]]</span>
|
||||
</template>
|
||||
@@ -726,11 +723,12 @@
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Changes go away when you leave the page and are only viewable to you.
|
||||
Filter displayed tasks by the operator who entered them, the task number, and/or the command itself.<br>
|
||||
These filters are only visible to you and will disappear when you leave the page.
|
||||
<table class="table-striped {{config['table-color']}} table" style="width:100%">
|
||||
<tr>
|
||||
<th style="width:7rem">Component</th>
|
||||
<th style="width:4rem">Filter</th>
|
||||
<th style="width:4rem">Enabled</th>
|
||||
<th>Values</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -743,7 +741,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Task</td>
|
||||
<td>Task Number</td>
|
||||
<td><span class="material-switch pull-right"><input class="form-control" type="checkbox" :checked="task_filters['task']['active']" v-model="task_filters['task']['active']" id="task_filter_task"><label for="task_filter_task" class="btn-warning"></label></span></td>
|
||||
<td>
|
||||
<template v-if="task_filters['task']['active']">
|
||||
@@ -761,6 +759,13 @@
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Has Comment</td>
|
||||
<td><span class="material-switch pull-right"><input class="form-control" type="checkbox" :checked="task_filters['comment']['active']" v-model="task_filters['comment']['active']" id="task_filter_comment"><label for="task_filter_comment" class="btn-warning"></label></span></td>
|
||||
<td>
|
||||
Only show tasks with comments
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -1532,7 +1532,8 @@ var task_data = new Vue({
|
||||
task_filters: {
|
||||
"task": {"active": false, "range_low": 0, "range_high": 1000000},
|
||||
"operator": {"active": false, "username": ""},
|
||||
"command": {"active": false, "cmd": ""}
|
||||
"command": {"active": false, "cmd": ""},
|
||||
"comment": {"active": false}
|
||||
},
|
||||
input_field_placeholder: {"data": "", "cid": -1},
|
||||
meta: meta,
|
||||
@@ -1943,6 +1944,8 @@ var task_data = new Vue({
|
||||
});
|
||||
$('#cardbody' + task.id).unbind('hidden.bs.collapse').on('hidden.bs.collapse', function () {
|
||||
all_tasks[task.callback][task.id]['expanded'] = false;
|
||||
Vue.set(all_tasks[task.callback][task.id], "response", {});
|
||||
Vue.set(all_tasks[task.callback][task.id], "scripted", "");
|
||||
$('#color-arrow' + task.id).removeClass('fa-minus').addClass('fa-plus');
|
||||
});
|
||||
},
|
||||
@@ -2258,19 +2261,22 @@ var task_data = new Vue({
|
||||
apply_filter: function (task) {
|
||||
// determine if the specified task should be displayed based on the task_filters set
|
||||
let status = true;
|
||||
if (this.task_filters['task']['active'] && task.id !== undefined) {
|
||||
if (this.task_filters['task']['active'] && task.id !== null) {
|
||||
status = status && task.id <= this.task_filters['task']['range_high'] && task.id >= this.task_filters['task']['range_low'];
|
||||
}
|
||||
if (this.task_filters['operator']['active'] && task.operator !== undefined) {
|
||||
if (this.task_filters['operator']['active'] && task.operator !== null) {
|
||||
status = status && task.operator.includes(this.task_filters['operator']['username']);
|
||||
}
|
||||
if (this.task_filters['command']['active']) {
|
||||
if (task.command === undefined) {
|
||||
status = status && task.params.includes(this.task_filters['command']['cmd']);
|
||||
if (task.command === null) {
|
||||
status = status && task.original_params.includes(this.task_filters['command']['cmd']);
|
||||
} else {
|
||||
status = status && task.command.includes(this.task_filters['command']['cmd']);
|
||||
}
|
||||
}
|
||||
if (this.task_filters['comment']['active']){
|
||||
status = status && task.comment !== "";
|
||||
}
|
||||
// if nothing is active, default to true
|
||||
return status;
|
||||
},
|
||||
@@ -2291,7 +2297,7 @@ var task_data = new Vue({
|
||||
task_data.path = "/";
|
||||
} else if (data['parent_path'] === undefined || data['parent_path'] === "") {
|
||||
data['parent_path'] = "";
|
||||
task_data.path = "";
|
||||
task_data.path = data['name'];
|
||||
} else if (data['parent_path'][0] === "/") {
|
||||
if (data['parent_path'] === "/") {
|
||||
task_data.path = data['parent_path'] + data['name'];
|
||||
@@ -2299,9 +2305,14 @@ var task_data = new Vue({
|
||||
task_data.path = data['parent_path'] + "/" + data['name'];
|
||||
}
|
||||
} else {
|
||||
task_data.path = data['parent_path'] + "\\" + data['name'];
|
||||
if(data['parent_path'][data['parent_path'].length -1] === "\\"){
|
||||
task_data.path = data['parent_path'] + data['name'];
|
||||
}else{
|
||||
task_data.path = data['parent_path'] + "\\" + data['name'];
|
||||
}
|
||||
|
||||
}
|
||||
task_data.folder = {"data": data, "children": children};
|
||||
Vue.set(task_data, "folder", {"data": data, "children": children});
|
||||
},
|
||||
update_file_browser_comment_live: function (data) {
|
||||
httpGetAsync("{{http}}://{{links.server_ip}}:{{links.server_port}}{{links.api_base}}/filebrowserobj/" + data.id, (response) => {
|
||||
@@ -2487,8 +2498,6 @@ var task_data = new Vue({
|
||||
} else {
|
||||
if(data.data.is_file){
|
||||
alertTop("info", "Cannot view children of files, only folders.");
|
||||
}else{
|
||||
alertTop("info", "Children of folder unknown.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2602,7 +2611,10 @@ Vue.component('browser-menu', {
|
||||
' <span v-for="n in depth" style="color:gray"> | </span>' +
|
||||
' <template> <i :class="iconClasses" @click="toggleChildren" ></i>\n' +
|
||||
" <span @click='test' class='browser-name' :style='data.deleted ? \"text-decoration:line-through\" : \"\" '> [[ data.name ]] </span>" +
|
||||
' <template v-if="data.success === true">' +
|
||||
' <template v-if="data.loading === true">' +
|
||||
' <div class="spinner-border text-info" style="width:1rem;height:1rem" role="status"> </div> fetching...' +
|
||||
' </template>' +
|
||||
' <template v-else-if="data.success === true">' +
|
||||
' <i style="color:green" class="fas fa-check"></i>' +
|
||||
' </template>' +
|
||||
' <template v-else-if="data.success === false">' +
|
||||
@@ -2643,7 +2655,7 @@ Vue.component('browser-menu', {
|
||||
},
|
||||
labelClasses() {
|
||||
let cls = "";
|
||||
if (this.children !== undefined) {
|
||||
if (!this.data.is_file) {
|
||||
cls += ' has-children ';
|
||||
}
|
||||
return cls;
|
||||
@@ -2662,14 +2674,59 @@ Vue.component('browser-menu', {
|
||||
task_data.$forceUpdate();
|
||||
return;
|
||||
}
|
||||
if(this.children !== undefined){
|
||||
if(this.depth === 0){
|
||||
this.$forceUpdate();
|
||||
task_data.setBrowserTableData(this.data, this.children);
|
||||
this.$forceUpdate();
|
||||
}else{
|
||||
alertTop("info", "Children of folder unknown, showing parent folder instead.");
|
||||
task_data.setBrowserTableData(this.$parent.data, this.$parent.children);
|
||||
this.$forceUpdate();
|
||||
task_data.$forceUpdate();
|
||||
return;
|
||||
}
|
||||
let me = this;
|
||||
me.data.loading = true;
|
||||
me.$forceUpdate();
|
||||
httpGetAsync("{{http}}://{{links.server_ip}}:{{links.server_port}}{{links.api_base}}/filebrowserobj/" + this.data.id + "/files", (response) => {
|
||||
try {
|
||||
let info = JSON.parse(response);
|
||||
if (info["status"] === "success") {
|
||||
setTimeout(() => {
|
||||
Vue.nextTick().then(function () {
|
||||
if(me.children === undefined){
|
||||
me.children = [];
|
||||
}
|
||||
for(let i = 0; i < info["files"].length; i++){
|
||||
let found = false;
|
||||
for(let j = 0; j < me.children.length; j++){
|
||||
if(Object.values(me.children[j])[0].data.id === Object.values(info["files"][i])[0].data.id){
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found){
|
||||
me.children.push(info["files"][i]);
|
||||
}
|
||||
}
|
||||
task_data.setBrowserTableData(me.data, me.children);
|
||||
setTimeout(() => { // setTimeout to put this into event queue
|
||||
// executed after render
|
||||
Vue.nextTick().then(function () {
|
||||
me.data.loading = false;
|
||||
me.$forceUpdate();
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
alertTop("danger", info["error"]);
|
||||
me.data.loading = false;
|
||||
me.$forceUpdate();
|
||||
}
|
||||
} catch (error) {
|
||||
me.data.loading = false;
|
||||
console.log(error.toString());
|
||||
me.$forceUpdate();
|
||||
alertTop("danger", "error fetching files in that folder.");
|
||||
}
|
||||
},
|
||||
"GET", null);
|
||||
},
|
||||
get_latest_download_path() {
|
||||
return "{{http}}://{{links.server_ip}}:{{links.server_port}}{{links.api_base}}/files/download/" + this.data['files'][0]['agent_file_id'];
|
||||
@@ -3284,7 +3341,7 @@ function add_new_response(rsp, from_websocket) {
|
||||
all_tasks[rsp['task']['callback']][rsp['task']['id']]['expanded'] = true;
|
||||
//now that the new response has been added, potentially update the scripted version
|
||||
if (Object.prototype.hasOwnProperty.call(browser_scripts, rsp['task']['command_id']) && all_tasks[rsp['task']['callback']][rsp['task']['id']]['use_scripted']) {
|
||||
all_tasks[rsp['task']['callback']][rsp['task']['id']]['scripted'] = browser_scripts[rsp['task']['command_id']](rsp['task'], Object.values(all_tasks[rsp['task']['callback']][rsp['task']['id']]['response']));
|
||||
Vue.set(all_tasks[rsp['task']['callback']][rsp['task']['id']], 'scripted', browser_scripts[rsp['task']['command_id']](rsp['task'], Object.values(all_tasks[rsp['task']['callback']][rsp['task']['id']]['response'])));
|
||||
}
|
||||
if (from_websocket) {
|
||||
//we want to make sure we have this expanded by default
|
||||
@@ -4027,45 +4084,15 @@ function startwebsocket_filebrowser() {
|
||||
} else if (event.data !== "") {
|
||||
let data = JSON.parse(event.data);
|
||||
if (!initial_group_done) {
|
||||
Vue.set(meta, "file_browser", data);
|
||||
for(const [key, value] of Object.entries(data)){
|
||||
for(let i = 0; i < value.length; i++){
|
||||
process_file_browser_data(value[i]);
|
||||
}
|
||||
}
|
||||
initial_group_done = true;
|
||||
} else {
|
||||
// now we have streaming updates and need to merge them in
|
||||
// first step is to find the right host
|
||||
if (data['host'] === undefined || data['host'].length === 0) {
|
||||
return
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(meta['file_browser'], data['host'])) {
|
||||
Vue.set(meta["file_browser"], data["host"], {
|
||||
"data": {
|
||||
"name": data['host'],
|
||||
"host": data['host']
|
||||
}, "children": []
|
||||
});
|
||||
}
|
||||
// what if we're adding a new top level root
|
||||
if (data['parent'] === null) {
|
||||
for (let i = 0; i < meta['file_browser'][data['host']]['children'].length; i++) {
|
||||
if (data['name'] in meta['file_browser'][data['host']]['children'][i]) {
|
||||
Object.assign(meta['file_browser'][data['host']]['children'][i][data['name']]['data'],
|
||||
meta['file_browser'][data['host']]['children'][i]['data'],
|
||||
data);
|
||||
task_data.$forceUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if we get here, we have a root element that's new, so add it
|
||||
let new_data = {};
|
||||
new_data[data['name']] = {"data": data, "children": []};
|
||||
meta['file_browser'][data['host']]['children'].push(new_data);
|
||||
task_data.$forceUpdate();
|
||||
} else {
|
||||
add_update_file_browser(data, meta['file_browser'][data['host']]);
|
||||
task_data.$forceUpdate();
|
||||
}
|
||||
setTimeout(() => { // setTimeout to put this into event queue
|
||||
// executed after render
|
||||
task_data.$forceUpdate();
|
||||
}, 0);
|
||||
process_file_browser_data(data);
|
||||
}
|
||||
} else {
|
||||
initial_group_done = true;
|
||||
@@ -4079,6 +4106,44 @@ function startwebsocket_filebrowser() {
|
||||
};
|
||||
}
|
||||
|
||||
function process_file_browser_data(data){
|
||||
// first step is to find the right host
|
||||
if (data['host'] === undefined || data['host'].length === 0) {
|
||||
return
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(meta['file_browser'], data['host'])) {
|
||||
Vue.set(meta["file_browser"], data["host"], {
|
||||
"data": {
|
||||
"name": data['host'],
|
||||
"host": data['host']
|
||||
}, "children": []
|
||||
});
|
||||
}
|
||||
// what if we're adding a new top level root
|
||||
if (data['parent'] === null) {
|
||||
for (let i = 0; i < meta['file_browser'][data['host']]['children'].length; i++) {
|
||||
if (data['name'] in meta['file_browser'][data['host']]['children'][i]) {
|
||||
Object.assign(meta['file_browser'][data['host']]['children'][i][data['name']]['data'],
|
||||
meta['file_browser'][data['host']]['children'][i]['data'],
|
||||
data);
|
||||
task_data.$forceUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if we get here, we have a root element that's new, so add it
|
||||
let new_data = {};
|
||||
new_data[data['name']] = {"data": data, "children": []};
|
||||
meta['file_browser'][data['host']]['children'].push(new_data);
|
||||
task_data.$forceUpdate();
|
||||
} else {
|
||||
add_update_file_browser(data, meta['file_browser'][data['host']]);
|
||||
task_data.$forceUpdate();
|
||||
}
|
||||
setTimeout(() => { // setTimeout to put this into event queue
|
||||
// executed after render
|
||||
task_data.$forceUpdate();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
||||
function add_update_file_browser(search, element) {
|
||||
@@ -4106,7 +4171,7 @@ function add_update_file_browser(search, element) {
|
||||
//if we get here, and parent is true, then we are the parent and failed to find the child, so we need to add it
|
||||
if (element['data']['id'] === search['parent']) {
|
||||
let new_data = {};
|
||||
new_data[search['name']] = {"data": search, "children": undefined};
|
||||
new_data[search['name']] = {"data": search, "children": []};
|
||||
if(element['children'] === undefined){
|
||||
Vue.set(element, "children", []);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div style="position:absolute; top:20%; left:35%">
|
||||
{% if successful_creation %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
Successfully created a new user! An admin must activate your account before you can log in.
|
||||
</div>
|
||||
{% endif %}
|
||||
<img src="/static/red_blue_login.png" style="height:calc(40vh)" class="center">
|
||||
<form action="" method="post">
|
||||
{{ errors.token_errors }}
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
<div class="card-header shadow border border-dark bg-header text-white">
|
||||
<h2 style="display:inline-block">Created <span class="operator">Payloads</span></h2>
|
||||
<button style="float:right" class="btn btn{{config['outline-buttons']}}danger btn-md" onclick="delete_selected_function()">Delete all Selected</button>
|
||||
<template v-if="view_auto_generated">
|
||||
<button style="float:right" class="btn btn{{config['outline-buttons']}}info btn-md" @click="toggle_auto_generated()">Hide Ephemeral</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button style="float:right" class="btn btn{{config['outline-buttons']}}success btn-md" @click="toggle_auto_generated()">Show Ephemeral</button>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<table class="table table-condensed table-striped {{config['table-color']}} border border-dark shadow" style="margin-bottom:0">
|
||||
<tr>
|
||||
@@ -14,21 +21,30 @@
|
||||
<th onclick="sort_table(this)" style="text-align:left"><b>File</b></th>
|
||||
<th onclick="sort_table(this)" style="text-align:left"><b>Description</b></th>
|
||||
<th onclick="sort_table(this)" style="text-align:left;width:10rem"><b>Creator</b></th>
|
||||
<th onclick="sort_table(this)" style="text-align:left;width:10rem"><b>Payload Type</b></th>
|
||||
<th onclick="sort_table(this)" style="text-align:center;width:10rem"><b>Payload Type</b></th>
|
||||
<template v-if="view_auto_generated">
|
||||
<th onclick="sort_table(this, 'int')" style="text-align:center;width:10rem">
|
||||
<b>Task</b>
|
||||
</th>
|
||||
</template>
|
||||
<th style="width:12rem;text-align:center" onclick="sort_table(this, 'date')"><b>Creation Time</b></th>
|
||||
</tr>
|
||||
<!-- Repeat this for each payload -->
|
||||
<tr v-for="p in payloads" :key="p.creation_time" v-show="!p.deleted">
|
||||
<tr v-for="p in payloads" :key="p.creation_time" v-show="p.auto_generated == false || view_auto_generated">
|
||||
<td style="text-align:center">
|
||||
<input style="padding-right:10px" type="checkbox" v-model="p.checked"> <button type="button" class="btn btn{{config['outline-buttons']}}danger btn-sm" @click="delete_button(p)" ><i class="fas fa-trash-alt"></i></button>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<div class="dropdown" style="display:inline">
|
||||
<template v-if="p.auto_generated">
|
||||
<button style="float:right" class="btn btn{{config['outline-buttons']}}secondary btn-sm" @click="show_parameters_button(p, false)">View Config</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="dropdown" style="display:inline">
|
||||
<button class="btn btn{{config['outline-buttons']}}secondary dropdown-toggle btn-sm" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :data-target="p.uuid">
|
||||
<i class="fas fa-edit"> Payload Actions</i>
|
||||
</button>
|
||||
<div class="dropdown-menu" :id="p.uuid">
|
||||
<button type="button" class="dropdown-item" @click="edit_filename(p)">Rename File</button>
|
||||
<button type="button" class="dropdown-item" @click="edit_filename(p)">Rename File</button>
|
||||
<button type="button" class="dropdown-item" @click="edit_description(p)">Edit Description</button>
|
||||
<button type="button" class="dropdown-item" @click="update_callback_alert(p)">
|
||||
<template v-if="p.callback_alert">Stop Alerting to new Callbacks</template>
|
||||
@@ -38,10 +54,12 @@
|
||||
<button type="button" class="dropdown-item" @click="show_parameters_button(p, true)">View Build Message</button>
|
||||
<button type="button" class="dropdown-item" @click="register_manual_callback(p)">Manually Register Callback</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<template v-if="p.deleted || p.build_phase !== 'success'">
|
||||
<template v-if="p.deleted || p.build_phase !== 'success' || p.auto_generated">
|
||||
<button class="btn btn{{config['outline-buttons']}}danger btn-sm" disabled type="button"><i class="fas fa-ban fa-lg"></i></button>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -62,7 +80,14 @@
|
||||
<td>[[ p['file_id']['filename'] ]]</td>
|
||||
<td>[[ p.tag ]]</td>
|
||||
<td>[[ p.operator ]]</td>
|
||||
<td style="text-align:left">[[ p['payload_type'] ]]</td>
|
||||
<td style="text-align:center">[[ p['payload_type'] ]]</td>
|
||||
<template v-if="view_auto_generated">
|
||||
<td style="text-align:center">
|
||||
<template v-if="p.task !== null">
|
||||
<a :href="'{{http}}://{{links.server_ip}}:{{links.server_port}}/tasks/' + p['task']['id']" target="_blank">[[ p['task']['id'] ]]</a>
|
||||
</template>
|
||||
</td>
|
||||
</template>
|
||||
<td style="text-align:center">[[ toLocalTime(p.creation_time) ]]</td>
|
||||
</tr>
|
||||
<!-- End of the repeating -->
|
||||
|
||||
@@ -4,7 +4,8 @@ var payloads = []; //all services data
|
||||
var payloads_table = new Vue({
|
||||
el: '#payloads_table',
|
||||
data: {
|
||||
payloads
|
||||
payloads,
|
||||
view_auto_generated: false
|
||||
},
|
||||
methods: {
|
||||
delete_button: function (p) {
|
||||
@@ -130,6 +131,9 @@ var payloads_table = new Vue({
|
||||
|
||||
}, "PUT", {"description": edit_payload_vue.edit_value});
|
||||
});
|
||||
},
|
||||
toggle_auto_generated: function(){
|
||||
this.view_auto_generated = !this.view_auto_generated;
|
||||
}
|
||||
},
|
||||
delimiters: ['[[', ']]']
|
||||
@@ -266,6 +270,7 @@ function startwebsocket_payloads() {
|
||||
} else if (event.data !== "") {
|
||||
let pdata = JSON.parse(event.data);
|
||||
if (pdata['deleted'] === false) {
|
||||
console.log(pdata);
|
||||
for (let i = 0; i < payloads_table.payloads.length; i++) {
|
||||
if (pdata['id'] === payloads_table.payloads[i]['id']) {
|
||||
//just update the data
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
[[token.token_type]]
|
||||
</td>
|
||||
<td>
|
||||
[[token.id]]: [[token.token_value]]
|
||||
[[token.token_value]]
|
||||
</td>
|
||||
<td>
|
||||
[[toLocalTime(token.creation_time)]]
|
||||
@@ -179,7 +179,6 @@
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control" id="apitokenCreateTokenType">
|
||||
<option value="C2">C2</option>
|
||||
<option value="User">User</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
@@ -109,11 +109,12 @@
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Changes go away when you leave the page and are only viewable to you.
|
||||
Filter displayed tasks by the operator who entered them, the task number, and/or the command itself.<br>
|
||||
These filters are only visible to you and will disappear when you leave the page.
|
||||
<table class="table-striped {{config['table-color']}} table" style="width:100%">
|
||||
<tr>
|
||||
<th style="width:7rem">Component</th>
|
||||
<th style="width:4rem">Filter</th>
|
||||
<th style="width:4rem">Enabled</th>
|
||||
<th>Values</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -126,7 +127,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Task</td>
|
||||
<td>Task Number</td>
|
||||
<td><span class="material-switch pull-right"><input class="form-control" type="checkbox" :checked="task_filters['task']['active']" v-model="task_filters['task']['active']" id="task_filter_task"><label for="task_filter_task" class="btn-warning"></label></span></td>
|
||||
<td>
|
||||
<template v-if="task_filters['task']['active']">
|
||||
@@ -144,6 +145,13 @@
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Has Comment</td>
|
||||
<td><span class="material-switch pull-right"><input class="form-control" type="checkbox" :checked="task_filters['comment']['active']" v-model="task_filters['comment']['active']" id="task_filter_comment"><label for="task_filter_comment" class="btn-warning"></label></span></td>
|
||||
<td>
|
||||
Only show tasks with comments
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -26,7 +26,8 @@ var callback_table = new Vue({
|
||||
task_filters: {
|
||||
"task": {"active": false, "range_low": 0, "range_high": 1000000},
|
||||
"operator": {"active": false, "username": ""},
|
||||
"command": {"active": false, "cmd": ""}
|
||||
"command": {"active": false, "cmd": ""},
|
||||
"comment": {"active": false}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -390,14 +391,21 @@ var callback_table = new Vue({
|
||||
apply_filter: function (task) {
|
||||
// determine if the specified task should be displayed based on the task_filters set
|
||||
let status = true;
|
||||
if (this.task_filters['task']['active'] && task.id !== undefined) {
|
||||
if (this.task_filters['task']['active'] && task.id !== null) {
|
||||
status = status && task.id <= this.task_filters['task']['range_high'] && task.id >= this.task_filters['task']['range_low'];
|
||||
}
|
||||
if (this.task_filters['operator']['active'] && task.operator !== undefined) {
|
||||
if (this.task_filters['operator']['active'] && task.operator !== null) {
|
||||
status = status && task.operator.includes(this.task_filters['operator']['username']);
|
||||
}
|
||||
if (this.task_filters['command']['active'] && task.command !== undefined) {
|
||||
status = status && task.command.includes(this.task_filters['command']['cmd']);
|
||||
if (this.task_filters['command']['active']) {
|
||||
if (task.command === null) {
|
||||
status = status && task.original_params.includes(this.task_filters['command']['cmd']);
|
||||
} else {
|
||||
status = status && task.command.includes(this.task_filters['command']['cmd']);
|
||||
}
|
||||
}
|
||||
if (this.task_filters['comment']['active']){
|
||||
status = status && task.comment !== "";
|
||||
}
|
||||
// if nothing is active, default to true
|
||||
return status;
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
From itsafeaturemythic/mythic_postgres:0.0.1
|
||||
COPY ["pg_hba.conf", "/var/lib/postgresql/pg_hba.conf"]
|
||||
COPY ["configuration.sql", "/docker-entrypoint-initdb.d/configuration.sql"]
|
||||
COPY ["configuration.sh", "/docker-entrypoint-initdb.d/configuration.sh"]
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
mv /var/lib/postgresql/pg_hba.conf /var/lib/postgresql/data/pg_hba.conf
|
||||
@@ -0,0 +1,2 @@
|
||||
\set user_pass '\'' `echo "$POSTGRES_PASSWORD"` '\'';
|
||||
ALTER USER mythic_user PASSWORD :user_pass;
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
# PostgreSQL Client Authentication Configuration File
|
||||
# ===================================================
|
||||
#
|
||||
# Refer to the "Client Authentication" section in the PostgreSQL
|
||||
# documentation for a complete description of this file. A short
|
||||
# synopsis follows.
|
||||
#
|
||||
# This file controls: which hosts are allowed to connect, how clients
|
||||
# are authenticated, which PostgreSQL user names they can use, which
|
||||
# databases they can access. Records take one of these forms:
|
||||
#
|
||||
# local DATABASE USER METHOD [OPTIONS]
|
||||
# host DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
# hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
# hostnossl DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
#
|
||||
# (The uppercase items must be replaced by actual values.)
|
||||
#
|
||||
# The first field is the connection type: "local" is a Unix-domain
|
||||
# socket, "host" is either a plain or SSL-encrypted TCP/IP socket,
|
||||
# "hostssl" is an SSL-encrypted TCP/IP socket, and "hostnossl" is a
|
||||
# plain TCP/IP socket.
|
||||
#
|
||||
# DATABASE can be "all", "sameuser", "samerole", "replication", a
|
||||
# database name, or a comma-separated list thereof. The "all"
|
||||
# keyword does not match "replication". Access to replication
|
||||
# must be enabled in a separate record (see example below).
|
||||
#
|
||||
# USER can be "all", a user name, a group name prefixed with "+", or a
|
||||
# comma-separated list thereof. In both the DATABASE and USER fields
|
||||
# you can also write a file name prefixed with "@" to include names
|
||||
# from a separate file.
|
||||
#
|
||||
# ADDRESS specifies the set of hosts the record matches. It can be a
|
||||
# host name, or it is made up of an IP address and a CIDR mask that is
|
||||
# an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that
|
||||
# specifies the number of significant bits in the mask. A host name
|
||||
# that starts with a dot (.) matches a suffix of the actual host name.
|
||||
# Alternatively, you can write an IP address and netmask in separate
|
||||
# columns to specify the set of hosts. Instead of a CIDR-address, you
|
||||
# can write "samehost" to match any of the server's own IP addresses,
|
||||
# or "samenet" to match any address in any subnet that the server is
|
||||
# directly connected to.
|
||||
#
|
||||
# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
|
||||
# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
|
||||
# "password" sends passwords in clear text; "md5" is preferred since
|
||||
# it sends encrypted passwords.
|
||||
#
|
||||
# OPTIONS are a set of options for the authentication in the format
|
||||
# NAME=VALUE. The available options depend on the different
|
||||
# authentication methods -- refer to the "Client Authentication"
|
||||
# section in the documentation for a list of which options are
|
||||
# available for which authentication methods.
|
||||
#
|
||||
# Database and user names containing spaces, commas, quotes and other
|
||||
# special characters must be quoted. Quoting one of the keywords
|
||||
# "all", "sameuser", "samerole" or "replication" makes the name lose
|
||||
# its special character, and just match a database or username with
|
||||
# that name.
|
||||
#
|
||||
# This file is read on server startup and when the postmaster receives
|
||||
# a SIGHUP signal. If you edit the file on a running system, you have
|
||||
# to SIGHUP the postmaster for the changes to take effect. You can
|
||||
# use "pg_ctl reload" to do that.
|
||||
|
||||
# Put your actual configuration here
|
||||
# ----------------------------------
|
||||
#
|
||||
# If you want to allow non-local connections, you need to add more
|
||||
# "host" records. In that case you will also need to make PostgreSQL
|
||||
# listen on a non-local interface via the listen_addresses
|
||||
# configuration parameter, or via the -i or -h command line switches.
|
||||
|
||||
# CAUTION: Configuring the system for local "trust" authentication
|
||||
# allows any local user to connect as any PostgreSQL user, including
|
||||
# the database superuser. If you do not trust all your local users,
|
||||
# use another authentication method.
|
||||
|
||||
|
||||
# TYPE DATABASE USER ADDRESS METHOD
|
||||
|
||||
# "local" is for Unix domain socket connections only
|
||||
local all all md5
|
||||
# IPv4 local connections:
|
||||
#host all all 127.0.0.1/32 trust
|
||||
# IPv6 local connections:
|
||||
#host all all ::1/128 trust
|
||||
# Allow replication connections from localhost, by a user with the
|
||||
# replication privilege.
|
||||
host all all all md5
|
||||
@@ -141,6 +141,14 @@ then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ "$(ls -A postgres-docker/database/)" ]; then
|
||||
echo -e "${BLUE}[*]${NC} Database exists already"
|
||||
else
|
||||
postgres_password=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1)
|
||||
base_replace="POSTGRES_PASSWORD=super_secret_mythic_user_password"
|
||||
echo -e "${BLUE}[*]${NC} Replacing static database password with randomized one"
|
||||
sed -i "s/$base_replace/POSTGRES_PASSWORD=$postgres_password/g" .env
|
||||
fi
|
||||
# start the main mythic components
|
||||
docker-compose up --build -d
|
||||
if [ $? -ne 0 ]
|
||||
|
||||
Reference in New Issue
Block a user