Skip to content

Commit 0831e8c

Browse files
committed
added UI config setting for timestamps and rabbitmq server updates
1 parent 9d2e452 commit 0831e8c

23 files changed

+313
-248
lines changed

CHANGELOG.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [3.3.1-rc46] - 2025-02-21
8+
9+
### Changed
10+
11+
- Updated some rabbitmq processing to reduce deadlocks and return quicker from timeout delays to the UI
12+
713
## [3.3.1-rc45] - 2025-02-20
814

915
### Changed

MythicReactUI/CHANGELOG.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.3.12] - 2025-02-21
8+
9+
### Changed
10+
11+
- Updated UI to allow changing what's displayed for the task's timestamp field via user settings
12+
713
## [0.3.11] - 2025-02-20
814

915
### Changed

MythicReactUI/src/cache.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ import {snackActions} from "./components/utilities/Snackbar";
55
export const meState = makeVar({loggedIn:false, user: null, access_token: null, refresh_token: null});
66
export const menuOpen = makeVar(false);
77
export const alertCount = makeVar(0);
8+
export const taskTimestampDisplayFieldOptions = [
9+
{
10+
name: "timestamp",
11+
display: "Latest Timestamp for anything task related"
12+
},
13+
{
14+
name: "status_timestamp_preprocessing",
15+
display: "When Operator Submitted Task"
16+
},
17+
{
18+
name: "status_timestamp_processing",
19+
display: "When Agent Picked up Task",
20+
}
21+
]
822
export const operatorSettingDefaults = {
923
fontSize: 12,
1024
fontFamily: "Verdana, Arial, sans-serif",
@@ -15,6 +29,7 @@ export const operatorSettingDefaults = {
1529
showCallbackGroups: false,
1630
useDisplayParamsForCLIHistory: true,
1731
interactType: "interact",
32+
taskTimestampDisplayField: "timestamp",
1833
callbacks_table_columns: ["Interact", "Host", "Domain", "User", "Description", "Last Checkin", "Agent", "IP", "PID"],
1934
callbacks_table_filters: {},
2035
autoTaskLsOnEmptyDirectories: false,

MythicReactUI/src/components/pages/Callbacks/CallbackMutations.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ export const taskingDataFragment = gql`
131131
status
132132
timestamp
133133
status_timestamp_submitted
134+
status_timestamp_processing
135+
status_timestamp_preprocessing
134136
command {
135137
cmd
136138
supported_ui_features

MythicReactUI/src/components/pages/Callbacks/TaskDisplay.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {GetMythicSetting} from "../../MythicComponents/MythicSavedUserSetting";
1919
import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwoTone';
2020
import CropRotateTwoToneIcon from '@mui/icons-material/CropRotateTwoTone';
2121
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
22+
import {operatorSettingDefaults} from "../../../cache";
2223

2324

2425
const PREFIX = 'TaskDisplay';
@@ -233,11 +234,13 @@ const GetOperatorDisplay = ({initialHideUsernameValue, task}) => {
233234
export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayChildren, toggleDisplayChildren, expanded }) => {
234235
const [displayComment, setDisplayComment] = React.useState(false);
235236
const [alertBadges, setAlertBadges] = React.useState(0);
236-
const initialHideUsernameValue = GetMythicSetting({setting_name: "hideUsernames", default_value: false});
237-
const initialShowIPValue = GetMythicSetting({setting_name: "showIP", default_value: false});
237+
const initialHideUsernameValue = GetMythicSetting({setting_name: "hideUsernames", default_value: operatorSettingDefaults.hideUsernames});
238+
const initialShowIPValue = GetMythicSetting({setting_name: "showIP", default_value: operatorSettingDefaults.showIP});
238239
const ipValue = JSON.parse(task.callback.ip)[0];
239-
const initialShowHostnameValue = GetMythicSetting({setting_name: "showHostname", default_value: false});
240-
const initialShowCallbackGroupsValue = GetMythicSetting({setting_name: "showCallbackGroups", default_value: false});
240+
const initialShowHostnameValue = GetMythicSetting({setting_name: "showHostname", default_value: operatorSettingDefaults.showHostname});
241+
const initialShowCallbackGroupsValue = GetMythicSetting({setting_name: "showCallbackGroups", default_value: operatorSettingDefaults.showCallbackGroups});
242+
const initialTaskTimestampDisplayField = GetMythicSetting({setting_name: "taskTimestampDisplayField", default_value: operatorSettingDefaults.taskTimestampDisplayField});
243+
const displayTimestamp = task[initialTaskTimestampDisplayField] ? task[initialTaskTimestampDisplayField] : task.timestamp;
241244
const toggleDisplayComment = (evt) => {
242245
evt.stopPropagation();
243246
setDisplayComment(!displayComment);
@@ -264,7 +267,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
264267
) : null}
265268
<div style={{lineHeight: 0}}>
266269
<Typography className={classes.taskAndTimeDisplay} onClick={preventPropagation}>
267-
[{toLocalTime(task.timestamp, me?.user?.view_utc_time || false)}]
270+
[{toLocalTime(displayTimestamp, me?.user?.view_utc_time || false)}]
268271
{" / "}
269272
<span style={{}}>
270273
{task.has_intercepted_response &&

MythicReactUI/src/components/pages/Settings/SettingsOperatorUIConfigDialog.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Select from '@mui/material/Select';
1717
import Input from '@mui/material/Input';
1818
import IconButton from '@mui/material/IconButton';
1919
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
20-
import { operatorSettingDefaults} from "../../../cache";
20+
import {operatorSettingDefaults, taskTimestampDisplayFieldOptions} from "../../../cache";
2121
import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
2222
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
2323
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
@@ -104,6 +104,9 @@ export function SettingsOperatorUIConfigDialog(props) {
104104
const initialUseDisplayParamsForCLIHistory = GetMythicSetting({setting_name: "useDisplayParamsForCLIHistory", default_value: operatorSettingDefaults.useDisplayParamsForCLIHistory});
105105
const [useDisplayParamsForCLIHistory, setUseDisplayParamsForCLIHistory] = React.useState(initialUseDisplayParamsForCLIHistory);
106106

107+
const initialTaskTimestampDisplayField = GetMythicSetting({setting_name: "taskTimestampDisplayField", default_value: operatorSettingDefaults.taskTimestampDisplayField});
108+
const [taskTimestampDisplayField, setTaskTimestampDisplayField] = React.useState(initialTaskTimestampDisplayField);
109+
107110
const initialPalette = GetMythicSetting({setting_name: 'palette', default_value: operatorSettingDefaults.palette});
108111
const [palette, setPalette] = React.useState({
109112
primary: {
@@ -225,6 +228,9 @@ export function SettingsOperatorUIConfigDialog(props) {
225228
const onChangeInteractType = (evt) => {
226229
setInteractType(evt.target.value);
227230
}
231+
const onChangeTaskTimestampDisplayField = (evt) => {
232+
setTaskTimestampDisplayField(evt.target.value);
233+
}
228234
const onChangeUseDisplayParamsForCLIHistory = (evt) => {
229235
setUseDisplayParamsForCLIHistory(!useDisplayParamsForCLIHistory);
230236
}
@@ -249,6 +255,7 @@ export function SettingsOperatorUIConfigDialog(props) {
249255
showMedia,
250256
interactType,
251257
useDisplayParamsForCLIHistory,
258+
taskTimestampDisplayField,
252259
palette: palette
253260
}});
254261
props.onClose();
@@ -270,6 +277,7 @@ export function SettingsOperatorUIConfigDialog(props) {
270277
setUseDisplayParamsForCLIHistory(operatorSettingDefaults.useDisplayParamsForCLIHistory);
271278
setResumeNotifications(false);
272279
setPalette(operatorSettingDefaults.palette);
280+
setTaskTimestampDisplayField(operatorSettingDefaults.taskTimestampDisplayField);
273281
}
274282
const clearAllUserSettings = () => {
275283
clearSettings();
@@ -477,6 +485,24 @@ export function SettingsOperatorUIConfigDialog(props) {
477485
</Select>
478486
</MythicStyledTableCell>
479487
</TableRow>
488+
<TableRow hover>
489+
<MythicStyledTableCell>
490+
Choose Which Timestamp to display for Tasks
491+
</MythicStyledTableCell>
492+
<MythicStyledTableCell>
493+
<Select
494+
labelId="demo-dialog-select-label"
495+
id="demo-dialog-select"
496+
value={taskTimestampDisplayField}
497+
onChange={onChangeTaskTimestampDisplayField}
498+
input={<Input style={{width: "100%"}}/>}
499+
>
500+
{taskTimestampDisplayFieldOptions.map( (opt) => (
501+
<MenuItem value={opt.name} key={opt.name}>{opt.display}</MenuItem>
502+
) )}
503+
</Select>
504+
</MythicStyledTableCell>
505+
</TableRow>
480506
<TableRow>
481507
<MythicStyledTableCell></MythicStyledTableCell>
482508
<MythicStyledTableCell>

MythicReactUI/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import jwt_decode from 'jwt-decode';
1515
import {meState} from './cache';
1616
import {getSkewedNow} from "./components/utilities/Time";
1717

18-
export const mythicUIVersion = "0.3.11";
18+
export const mythicUIVersion = "0.3.12";
1919

2020
let fetchingNewToken = false;
2121

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.3.1-rc45
1+
3.3.1-rc46

mythic-docker/src/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.3.1-rc45
1+
3.3.1-rc46

mythic-docker/src/authentication/jwt.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,12 @@ func ExtractAPIToken(c *gin.Context) (string, error) {
236236
}
237237
token = c.Request.Header.Get("apitoken")
238238
if len(token) == 0 {
239-
logging.LogError(nil, "[-] No 'apitoken` or 'Authorization: Bearer' token values supplied")
239+
if !strings.HasPrefix(c.Request.URL.Path, "/direct/") {
240+
logging.LogError(nil, "[-] No 'apitoken` or 'Authorization: Bearer' token values supplied")
241+
}
240242
return "", ErrMissingJWTToken
241243
}
242244
}
243-
244245
//logging.LogTrace("got apitoken header", "apitoken", token)
245246
return token, nil
246247
}

mythic-docker/src/rabbitmq/recv_c2_sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func c2Sync(in C2SyncMessage) error {
171171
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", c2Profile.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
172172
go database.ResolveAllOperationsMessage(getDownContainerMessage(c2Profile.Name), 0)
173173
go autoStartC2Profile(c2Profile)
174-
reSyncPayloadTypes()
174+
go reSyncPayloadTypes()
175175
checkContainerStatusAddC2Channel <- c2Profile
176176
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(c2Profile.Name)
177177
return nil

mythic-docker/src/rabbitmq/recv_tr_sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func processTrSyncMessages(msg amqp.Delivery) interface{} {
5353
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", trSyncMsg.Name, trSyncMsg.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
5454
go database.ResolveAllOperationsMessage(getDownContainerMessage(trSyncMsg.Name), 0)
5555
logging.LogDebug("Successfully synced", "service", trSyncMsg.Name)
56-
reSyncPayloadTypes()
56+
go reSyncPayloadTypes()
5757
}
5858
}
5959
return response

mythic-docker/src/rabbitmq/send_c2_rpc_config_check.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,26 @@ type C2ConfigCheckMessageResponse struct {
2121
func (r *rabbitMQConnection) SendC2RPCConfigCheck(configCheck C2ConfigCheckMessage) (*C2ConfigCheckMessageResponse, error) {
2222
configCheckResponse := C2ConfigCheckMessageResponse{}
2323
exclusiveQueue := true
24-
if configBytes, err := json.Marshal(configCheck); err != nil {
24+
configBytes, err := json.Marshal(configCheck)
25+
if err != nil {
2526
logging.LogError(err, "Failed to convert configCheck to JSON", "configCheck", configCheck)
2627
return nil, err
27-
} else if response, err := r.SendRPCMessage(
28+
}
29+
logging.LogDebug("Sending configCheck to RabbitMQ", "configCheck", configCheck)
30+
response, err := r.SendRPCMessage(
2831
MYTHIC_EXCHANGE,
2932
GetC2RPCConfigChecksRoutingKey(configCheck.Name),
3033
configBytes,
3134
exclusiveQueue,
32-
); err != nil {
35+
)
36+
if err != nil {
3337
logging.LogError(err, "Failed to send RPC message")
3438
return nil, err
35-
} else if err := json.Unmarshal(response, &configCheckResponse); err != nil {
39+
}
40+
err = json.Unmarshal(response, &configCheckResponse)
41+
if err != nil {
3642
logging.LogError(err, "Failed to parse config check response back to struct", "response", response)
3743
return nil, err
38-
} else {
39-
return &configCheckResponse, nil
4044
}
45+
return &configCheckResponse, nil
4146
}

mythic-docker/src/rabbitmq/send_c2_rpc_opsec_check.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,26 @@ type C2OPSECMessageResponse struct {
2424
func (r *rabbitMQConnection) SendC2RPCOpsecCheck(opsecCheck C2OPSECMessage) (*C2OPSECMessageResponse, error) {
2525
opsecCheckResponse := C2OPSECMessageResponse{}
2626
exclusiveQueue := true
27-
if opsecBytes, err := json.Marshal(opsecCheck); err != nil {
27+
opsecBytes, err := json.Marshal(opsecCheck)
28+
if err != nil {
2829
logging.LogError(err, "Failed to convert opsecCheck to JSON", "opseccheck", opsecCheck)
2930
return nil, err
30-
} else if response, err := r.SendRPCMessage(
31+
}
32+
logging.LogDebug("Sending opsecCheck to RabbitMQ", "opsecCheck", opsecCheck)
33+
response, err := r.SendRPCMessage(
3134
MYTHIC_EXCHANGE,
3235
GetC2RPCOpsecChecksRoutingKey(opsecCheck.Name),
3336
opsecBytes,
3437
exclusiveQueue,
35-
); err != nil {
38+
)
39+
if err != nil {
3640
logging.LogError(err, "Failed to send RPC message")
3741
return nil, err
38-
} else if err := json.Unmarshal(response, &opsecCheckResponse); err != nil {
42+
}
43+
err = json.Unmarshal(response, &opsecCheckResponse)
44+
if err != nil {
3945
logging.LogError(err, "Failed to parse opsec check response back to struct", "response", response)
4046
return nil, err
41-
} else {
42-
return &opsecCheckResponse, nil
4347
}
48+
return &opsecCheckResponse, nil
4449
}

mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,26 @@ func RestartC2ServerAfterUpdate(c2ProfileName string, sendNotifications bool) {
8080
func (r *rabbitMQConnection) SendC2RPCStartServer(startServer C2StartServerMessage) (*C2StartServerMessageResponse, error) {
8181
c2StartServerResponse := C2StartServerMessageResponse{}
8282
exclusiveQueue := true
83-
if opsecBytes, err := json.Marshal(startServer); err != nil {
83+
opsecBytes, err := json.Marshal(startServer)
84+
if err != nil {
8485
logging.LogError(err, "Failed to convert startServer to JSON", "startServer", startServer)
8586
return &c2StartServerResponse, err
86-
} else if response, err := r.SendRPCMessage(
87+
}
88+
logging.LogDebug("Sending start server request", "startServer", startServer)
89+
response, err := r.SendRPCMessage(
8790
MYTHIC_EXCHANGE,
8891
GetC2RPCStartServerRoutingKey(startServer.Name),
8992
opsecBytes,
9093
exclusiveQueue,
91-
); err != nil {
94+
)
95+
if err != nil {
9296
logging.LogError(err, "Failed to send RPC message")
9397
return &c2StartServerResponse, err
94-
} else if err := json.Unmarshal(response, &c2StartServerResponse); err != nil {
98+
}
99+
err = json.Unmarshal(response, &c2StartServerResponse)
100+
if err != nil {
95101
logging.LogError(err, "Failed to parse start server response back to struct", "response", response)
96102
return &c2StartServerResponse, err
97-
} else {
98-
return &c2StartServerResponse, nil
99103
}
104+
return &c2StartServerResponse, nil
100105
}

0 commit comments

Comments
 (0)