few UI and back-end fixes

This commit is contained in:
its-a-feature
2026-02-06 08:24:31 -06:00
parent 99f2b6cb2f
commit 0484154eae
16 changed files with 74 additions and 40 deletions
+7
View File
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.4.23] - 2026-02-08
### Changed
- Updated the payload type syncing to resync for all payload types, not just new wrappers
- this fixes a bug where payload types syncing for the first time after their wrapper synced wouldn't match them up
## [3.4.22] - 2026-02-02
### Changed
+8
View File
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.98] - 2026-02-08
### Changed
- Updated the default shortcuts to remove Reporting and add CreatePayload
- Updated Task Search on Parameters to include agent parameters, not just original and display
- Updated callback table bulk actions to include a note about callback last checkin times not updates in those windows
## [0.3.97] - 2026-02-02
### Changed
+7 -5
View File
@@ -19,6 +19,11 @@ export const taskTimestampDisplayFieldOptions = [
}
]
export const taskingContextFieldsOptions = ["impersonation_context", "cwd", "user", "host", "ip", "pid", "process_short_name", "extra_info", "architecture"].sort();
export const defaultShortcuts = [
"ActiveCallbacks", "Payloads", "PayloadTypesAndC2",
"Operations", "SearchFiles", "SearchProxies",
"CreatePayload", "Eventing",
].sort();
export const operatorSettingDefaults = {
fontSize: 12,
navBarOpen: false,
@@ -39,6 +44,7 @@ export const operatorSettingDefaults = {
hideTaskingContext: false,
taskingContextFields: ["impersonation_context", "cwd"],
["experiment-responseStreamLimit"]: 200,
sideShortcuts: defaultShortcuts,
palette: {
primary: {
dark: "#75859b",
@@ -150,11 +156,7 @@ export const operatorSettingDefaults = {
}
},
}
export const defaultShortcuts = [
"ActiveCallbacks", "Payloads", "PayloadTypesAndC2",
"Operations", "SearchFiles", "SearchProxies",
"Reporting", "Eventing",
].sort();
export const mePreferences = makeVar(operatorSettingDefaults);
@@ -8,6 +8,7 @@ import {hideCallbacksMutation} from './CallbackMutations';
import {snackActions} from "../../utilities/Snackbar";
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
import { DataGrid } from '@mui/x-data-grid';
import DialogContentText from '@mui/material/DialogContentText';
const callbacksAndFeaturesQuery = gql`
@@ -175,6 +176,9 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Hide Multiple Callbacks at Once</DialogTitle>
<DialogContentText style={{textAlign: "center"}}>
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
</DialogContentText>
<CustomSelectTable initialData={initialData}
selectedData={selectedData}
sortModel={{ field: 'last_checkin', sort: 'asc' }}
@@ -217,6 +221,9 @@ export function CallbacksTabsSelectMultipleDialog({onClose, onSubmit}) {
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Select Multiple Callbacks</DialogTitle>
<DialogContentText style={{textAlign: "center"}}>
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
</DialogContentText>
<CustomSelectTable initialData={initialData}
selectedData={selectedData}
sortModel={{ field: 'display_id', sort: 'desc' }}
@@ -5,6 +5,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import {useQuery, gql } from '@apollo/client';
import {CallbacksTableLastCheckinCell, CallbacksTablePayloadTypeCell, CallbacksTableIPCell} from "./CallbacksTableRow";
import { DataGrid } from '@mui/x-data-grid';
import DialogContentText from '@mui/material/DialogContentText';
const callbacksAndFeaturesQuery = gql`
@@ -140,6 +141,9 @@ export function CallbacksTabsOpenMultipleDialog({onClose, tabType, onOpenTabs})
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Select Callback to open {tabType} tab</DialogTitle>
<DialogContentText style={{textAlign: "center"}}>
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
</DialogContentText>
<CustomSelectTable initialData={initialData}
selectedData={selectedData}
onRowClick={onRowClick}
@@ -10,6 +10,7 @@ import {CallbacksTableIPCell, CallbacksTableLastCheckinCell} from "./CallbacksTa
import { DataGrid } from '@mui/x-data-grid';
import { validate as uuidValidate } from 'uuid';
import {snackActions} from "../../utilities/Snackbar";
import DialogContentText from '@mui/material/DialogContentText';
const callbacksAndFeaturesQuery = gql`
@@ -291,6 +292,9 @@ export function CallbacksTabsTaskMultipleDialog({onClose, callback}) {
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">Task Multiple {callback.payload.payloadtype.name} Callbacks at Once</DialogTitle>
<DialogContentText style={{textAlign: "center"}}>
<b>Note: </b> Last checkin times are based on when this window opened and won't refresh.
</DialogContentText>
<CustomSelectTable initialData={initialData}
selectedData={selectedData} />
<Grid size={12}>
@@ -36,12 +36,12 @@ query responseQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status:
const parameterSearch = gql`
${taskingDataFragment}
query parametersQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status: String!, $host: String!, $filterOperator: String!) {
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}]}) {
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}]}) {
aggregate {
count(columns: id)
}
}
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}]}) {
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status},operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}]}) {
...taskData
}
}
@@ -75,12 +75,12 @@ query commandQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status:
const commandAndParameterSearch = gql`
${taskingDataFragment}
query parametersQuery($search: String!, $offset: Int!, $fetchLimit: Int!, $status: String!, $host: String!, $filterOperator: String!) {
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
task_aggregate(distinct_on: id, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
aggregate {
count(columns: id)
}
}
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
task(limit: $fetchLimit, distinct_on: id, offset: $offset, order_by: {id: desc}, where: {status: {_ilike: $status}, operator: {username: {_like: $filterOperator}}, callback: {host: {_ilike: $host}}, _or: [{original_params: {_ilike: $search}}, {display_params: {_ilike: $search}}, {params: {_ilike: $search}}, {command: {cmd: {_ilike: $search}}}, {command_name: {_ilike: $search}}]}) {
...taskData
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ import {jwtDecode} from 'jwt-decode';
import {meState} from './cache';
import {getSkewedNow} from "./components/utilities/Time";
export const mythicUIVersion = "0.3.97";
export const mythicUIVersion = "0.3.98";
let fetchingNewToken = false;
+1 -1
View File
@@ -1 +1 @@
3.4.22
3.4.23
+22 -20
View File
@@ -304,10 +304,8 @@ func payloadTypeSync(in PayloadTypeSyncMessage) error {
logging.LogError(err, "Failed to create new payloadtype")
return err
}
// when we get a new wrapper, resync payloads that might support it
if payloadtype.Wrapper {
go reSyncPayloadTypes()
}
// resync payloads that might support it
go reSyncPayloadTypes()
}
} else if err == nil {
// the payload exists in the database, so we need to go down the track of updating/adding/removing information
@@ -1411,24 +1409,28 @@ func reSyncPayloadTypes() {
logging.LogError(err, "Failed to fetch payload types from database")
} else {
for _, pt := range payloadTypes {
err = updatePayloadTypeC2Profiles(PayloadTypeSyncMessage{
PayloadType: PayloadType{
Name: pt.Name,
SupportedC2Profiles: pt.SupportedC2.StructStringValue(),
if len(pt.SupportedC2.StructValue()) > 0 {
err = updatePayloadTypeC2Profiles(PayloadTypeSyncMessage{
PayloadType: PayloadType{
Name: pt.Name,
SupportedC2Profiles: pt.SupportedC2.StructStringValue(),
},
},
},
pt,
)
if err != nil {
logging.LogError(err, "Failed to update payload type c2 mapping")
pt,
)
if err != nil {
logging.LogError(err, "Failed to update payload type c2 mapping")
}
}
err = updatePayloadTypeWrappers(PayloadTypeSyncMessage{
PayloadType: PayloadType{
Name: pt.Name,
SupportedWrapperPayloadTypes: pt.SupportedWrapping.StructStringValue(),
}}, pt)
if err != nil {
logging.LogError(err, "Failed to update payload type wrapper mapping")
if len(pt.SupportedWrapping.StructValue()) > 0 {
err = updatePayloadTypeWrappers(PayloadTypeSyncMessage{
PayloadType: PayloadType{
Name: pt.Name,
SupportedWrapperPayloadTypes: pt.SupportedWrapping.StructStringValue(),
}}, pt)
if err != nil {
logging.LogError(err, "Failed to update payload type wrapper mapping")
}
}
err = updatePayloadTypeTranslationContainer(PayloadTypeSyncMessage{
PayloadType: PayloadType{
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/spf13/viper"
)
const mythicServerVersion = "3.4.22"
const mythicServerVersion = "3.4.23"
type Config struct {
// server configuration
@@ -1,17 +1,17 @@
{
"files": {
"main.css": "/new/static/css/main.262ce320.css",
"main.js": "/new/static/js/main.5b69de90.js",
"main.js": "/new/static/js/main.e045fb0a.js",
"sql-wasm.wasm": "/new/sql-wasm-7f26ab750fcdafabbe18c6525a66a0bd.wasm",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/graphql.png": "/new/static/media/graphql.8f15978b39b0870a9f0e.png",
"static/media/Mythic_Logo.svg": "/new/static/media/Mythic_Logo.6842c911bebe36d6f83fc7ced4a2cd99.svg",
"index.html": "/new/index.html",
"main.262ce320.css.map": "/new/static/css/main.262ce320.css.map",
"main.5b69de90.js.map": "/new/static/js/main.5b69de90.js.map"
"main.e045fb0a.js.map": "/new/static/js/main.e045fb0a.js.map"
},
"entrypoints": [
"static/css/main.262ce320.css",
"static/js/main.5b69de90.js"
"static/js/main.e045fb0a.js"
]
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.5b69de90.js"></script><link href="/new/static/css/main.262ce320.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.e045fb0a.js"></script><link href="/new/static/css/main.262ce320.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>