Skip to content

Commit

Permalink
added UI config setting for timestamps and rabbitmq server updates
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Feb 21, 2025
1 parent 9d2e452 commit 0831e8c
Show file tree
Hide file tree
Showing 23 changed files with 313 additions and 248 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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.3.1-rc46] - 2025-02-21

### Changed

- Updated some rabbitmq processing to reduce deadlocks and return quicker from timeout delays to the UI

## [3.3.1-rc45] - 2025-02-20

### Changed
Expand Down
6 changes: 6 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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.12] - 2025-02-21

### Changed

- Updated UI to allow changing what's displayed for the task's timestamp field via user settings

## [0.3.11] - 2025-02-20

### Changed
Expand Down
15 changes: 15 additions & 0 deletions MythicReactUI/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ import {snackActions} from "./components/utilities/Snackbar";
export const meState = makeVar({loggedIn:false, user: null, access_token: null, refresh_token: null});
export const menuOpen = makeVar(false);
export const alertCount = makeVar(0);
export const taskTimestampDisplayFieldOptions = [
{
name: "timestamp",
display: "Latest Timestamp for anything task related"
},
{
name: "status_timestamp_preprocessing",
display: "When Operator Submitted Task"
},
{
name: "status_timestamp_processing",
display: "When Agent Picked up Task",
}
]
export const operatorSettingDefaults = {
fontSize: 12,
fontFamily: "Verdana, Arial, sans-serif",
Expand All @@ -15,6 +29,7 @@ export const operatorSettingDefaults = {
showCallbackGroups: false,
useDisplayParamsForCLIHistory: true,
interactType: "interact",
taskTimestampDisplayField: "timestamp",
callbacks_table_columns: ["Interact", "Host", "Domain", "User", "Description", "Last Checkin", "Agent", "IP", "PID"],
callbacks_table_filters: {},
autoTaskLsOnEmptyDirectories: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export const taskingDataFragment = gql`
status
timestamp
status_timestamp_submitted
status_timestamp_processing
status_timestamp_preprocessing
command {
cmd
supported_ui_features
Expand Down
13 changes: 8 additions & 5 deletions MythicReactUI/src/components/pages/Callbacks/TaskDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {GetMythicSetting} from "../../MythicComponents/MythicSavedUserSetting";
import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwoTone';
import CropRotateTwoToneIcon from '@mui/icons-material/CropRotateTwoTone';
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
import {operatorSettingDefaults} from "../../../cache";


const PREFIX = 'TaskDisplay';
Expand Down Expand Up @@ -233,11 +234,13 @@ const GetOperatorDisplay = ({initialHideUsernameValue, task}) => {
export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayChildren, toggleDisplayChildren, expanded }) => {
const [displayComment, setDisplayComment] = React.useState(false);
const [alertBadges, setAlertBadges] = React.useState(0);
const initialHideUsernameValue = GetMythicSetting({setting_name: "hideUsernames", default_value: false});
const initialShowIPValue = GetMythicSetting({setting_name: "showIP", default_value: false});
const initialHideUsernameValue = GetMythicSetting({setting_name: "hideUsernames", default_value: operatorSettingDefaults.hideUsernames});
const initialShowIPValue = GetMythicSetting({setting_name: "showIP", default_value: operatorSettingDefaults.showIP});
const ipValue = JSON.parse(task.callback.ip)[0];
const initialShowHostnameValue = GetMythicSetting({setting_name: "showHostname", default_value: false});
const initialShowCallbackGroupsValue = GetMythicSetting({setting_name: "showCallbackGroups", default_value: false});
const initialShowHostnameValue = GetMythicSetting({setting_name: "showHostname", default_value: operatorSettingDefaults.showHostname});
const initialShowCallbackGroupsValue = GetMythicSetting({setting_name: "showCallbackGroups", default_value: operatorSettingDefaults.showCallbackGroups});
const initialTaskTimestampDisplayField = GetMythicSetting({setting_name: "taskTimestampDisplayField", default_value: operatorSettingDefaults.taskTimestampDisplayField});
const displayTimestamp = task[initialTaskTimestampDisplayField] ? task[initialTaskTimestampDisplayField] : task.timestamp;
const toggleDisplayComment = (evt) => {
evt.stopPropagation();
setDisplayComment(!displayComment);
Expand All @@ -264,7 +267,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
) : null}
<div style={{lineHeight: 0}}>
<Typography className={classes.taskAndTimeDisplay} onClick={preventPropagation}>
[{toLocalTime(task.timestamp, me?.user?.view_utc_time || false)}]
[{toLocalTime(displayTimestamp, me?.user?.view_utc_time || false)}]
{" / "}
<span style={{}}>
{task.has_intercepted_response &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Select from '@mui/material/Select';
import Input from '@mui/material/Input';
import IconButton from '@mui/material/IconButton';
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
import { operatorSettingDefaults} from "../../../cache";
import {operatorSettingDefaults, taskTimestampDisplayFieldOptions} from "../../../cache";
import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
Expand Down Expand Up @@ -104,6 +104,9 @@ export function SettingsOperatorUIConfigDialog(props) {
const initialUseDisplayParamsForCLIHistory = GetMythicSetting({setting_name: "useDisplayParamsForCLIHistory", default_value: operatorSettingDefaults.useDisplayParamsForCLIHistory});
const [useDisplayParamsForCLIHistory, setUseDisplayParamsForCLIHistory] = React.useState(initialUseDisplayParamsForCLIHistory);

const initialTaskTimestampDisplayField = GetMythicSetting({setting_name: "taskTimestampDisplayField", default_value: operatorSettingDefaults.taskTimestampDisplayField});
const [taskTimestampDisplayField, setTaskTimestampDisplayField] = React.useState(initialTaskTimestampDisplayField);

const initialPalette = GetMythicSetting({setting_name: 'palette', default_value: operatorSettingDefaults.palette});
const [palette, setPalette] = React.useState({
primary: {
Expand Down Expand Up @@ -225,6 +228,9 @@ export function SettingsOperatorUIConfigDialog(props) {
const onChangeInteractType = (evt) => {
setInteractType(evt.target.value);
}
const onChangeTaskTimestampDisplayField = (evt) => {
setTaskTimestampDisplayField(evt.target.value);
}
const onChangeUseDisplayParamsForCLIHistory = (evt) => {
setUseDisplayParamsForCLIHistory(!useDisplayParamsForCLIHistory);
}
Expand All @@ -249,6 +255,7 @@ export function SettingsOperatorUIConfigDialog(props) {
showMedia,
interactType,
useDisplayParamsForCLIHistory,
taskTimestampDisplayField,
palette: palette
}});
props.onClose();
Expand All @@ -270,6 +277,7 @@ export function SettingsOperatorUIConfigDialog(props) {
setUseDisplayParamsForCLIHistory(operatorSettingDefaults.useDisplayParamsForCLIHistory);
setResumeNotifications(false);
setPalette(operatorSettingDefaults.palette);
setTaskTimestampDisplayField(operatorSettingDefaults.taskTimestampDisplayField);
}
const clearAllUserSettings = () => {
clearSettings();
Expand Down Expand Up @@ -477,6 +485,24 @@ export function SettingsOperatorUIConfigDialog(props) {
</Select>
</MythicStyledTableCell>
</TableRow>
<TableRow hover>
<MythicStyledTableCell>
Choose Which Timestamp to display for Tasks
</MythicStyledTableCell>
<MythicStyledTableCell>
<Select
labelId="demo-dialog-select-label"
id="demo-dialog-select"
value={taskTimestampDisplayField}
onChange={onChangeTaskTimestampDisplayField}
input={<Input style={{width: "100%"}}/>}
>
{taskTimestampDisplayFieldOptions.map( (opt) => (
<MenuItem value={opt.name} key={opt.name}>{opt.display}</MenuItem>
) )}
</Select>
</MythicStyledTableCell>
</TableRow>
<TableRow>
<MythicStyledTableCell></MythicStyledTableCell>
<MythicStyledTableCell>
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import jwt_decode from 'jwt-decode';
import {meState} from './cache';
import {getSkewedNow} from "./components/utilities/Time";

export const mythicUIVersion = "0.3.11";
export const mythicUIVersion = "0.3.12";

let fetchingNewToken = false;

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.1-rc45
3.3.1-rc46
2 changes: 1 addition & 1 deletion mythic-docker/src/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.1-rc45
3.3.1-rc46
5 changes: 3 additions & 2 deletions mythic-docker/src/authentication/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,12 @@ func ExtractAPIToken(c *gin.Context) (string, error) {
}
token = c.Request.Header.Get("apitoken")
if len(token) == 0 {
logging.LogError(nil, "[-] No 'apitoken` or 'Authorization: Bearer' token values supplied")
if !strings.HasPrefix(c.Request.URL.Path, "/direct/") {
logging.LogError(nil, "[-] No 'apitoken` or 'Authorization: Bearer' token values supplied")
}
return "", ErrMissingJWTToken
}
}

//logging.LogTrace("got apitoken header", "apitoken", token)
return token, nil
}
Expand Down
2 changes: 1 addition & 1 deletion mythic-docker/src/rabbitmq/recv_c2_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func c2Sync(in C2SyncMessage) error {
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", c2Profile.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
go database.ResolveAllOperationsMessage(getDownContainerMessage(c2Profile.Name), 0)
go autoStartC2Profile(c2Profile)
reSyncPayloadTypes()
go reSyncPayloadTypes()
checkContainerStatusAddC2Channel <- c2Profile
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(c2Profile.Name)
return nil
Expand Down
2 changes: 1 addition & 1 deletion mythic-docker/src/rabbitmq/recv_tr_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func processTrSyncMessages(msg amqp.Delivery) interface{} {
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", trSyncMsg.Name, trSyncMsg.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
go database.ResolveAllOperationsMessage(getDownContainerMessage(trSyncMsg.Name), 0)
logging.LogDebug("Successfully synced", "service", trSyncMsg.Name)
reSyncPayloadTypes()
go reSyncPayloadTypes()
}
}
return response
Expand Down
17 changes: 11 additions & 6 deletions mythic-docker/src/rabbitmq/send_c2_rpc_config_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ type C2ConfigCheckMessageResponse struct {
func (r *rabbitMQConnection) SendC2RPCConfigCheck(configCheck C2ConfigCheckMessage) (*C2ConfigCheckMessageResponse, error) {
configCheckResponse := C2ConfigCheckMessageResponse{}
exclusiveQueue := true
if configBytes, err := json.Marshal(configCheck); err != nil {
configBytes, err := json.Marshal(configCheck)
if err != nil {
logging.LogError(err, "Failed to convert configCheck to JSON", "configCheck", configCheck)
return nil, err
} else if response, err := r.SendRPCMessage(
}
logging.LogDebug("Sending configCheck to RabbitMQ", "configCheck", configCheck)
response, err := r.SendRPCMessage(
MYTHIC_EXCHANGE,
GetC2RPCConfigChecksRoutingKey(configCheck.Name),
configBytes,
exclusiveQueue,
); err != nil {
)
if err != nil {
logging.LogError(err, "Failed to send RPC message")
return nil, err
} else if err := json.Unmarshal(response, &configCheckResponse); err != nil {
}
err = json.Unmarshal(response, &configCheckResponse)
if err != nil {
logging.LogError(err, "Failed to parse config check response back to struct", "response", response)
return nil, err
} else {
return &configCheckResponse, nil
}
return &configCheckResponse, nil
}
17 changes: 11 additions & 6 deletions mythic-docker/src/rabbitmq/send_c2_rpc_opsec_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ type C2OPSECMessageResponse struct {
func (r *rabbitMQConnection) SendC2RPCOpsecCheck(opsecCheck C2OPSECMessage) (*C2OPSECMessageResponse, error) {
opsecCheckResponse := C2OPSECMessageResponse{}
exclusiveQueue := true
if opsecBytes, err := json.Marshal(opsecCheck); err != nil {
opsecBytes, err := json.Marshal(opsecCheck)
if err != nil {
logging.LogError(err, "Failed to convert opsecCheck to JSON", "opseccheck", opsecCheck)
return nil, err
} else if response, err := r.SendRPCMessage(
}
logging.LogDebug("Sending opsecCheck to RabbitMQ", "opsecCheck", opsecCheck)
response, err := r.SendRPCMessage(
MYTHIC_EXCHANGE,
GetC2RPCOpsecChecksRoutingKey(opsecCheck.Name),
opsecBytes,
exclusiveQueue,
); err != nil {
)
if err != nil {
logging.LogError(err, "Failed to send RPC message")
return nil, err
} else if err := json.Unmarshal(response, &opsecCheckResponse); err != nil {
}
err = json.Unmarshal(response, &opsecCheckResponse)
if err != nil {
logging.LogError(err, "Failed to parse opsec check response back to struct", "response", response)
return nil, err
} else {
return &opsecCheckResponse, nil
}
return &opsecCheckResponse, nil
}
17 changes: 11 additions & 6 deletions mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,26 @@ func RestartC2ServerAfterUpdate(c2ProfileName string, sendNotifications bool) {
func (r *rabbitMQConnection) SendC2RPCStartServer(startServer C2StartServerMessage) (*C2StartServerMessageResponse, error) {
c2StartServerResponse := C2StartServerMessageResponse{}
exclusiveQueue := true
if opsecBytes, err := json.Marshal(startServer); err != nil {
opsecBytes, err := json.Marshal(startServer)
if err != nil {
logging.LogError(err, "Failed to convert startServer to JSON", "startServer", startServer)
return &c2StartServerResponse, err
} else if response, err := r.SendRPCMessage(
}
logging.LogDebug("Sending start server request", "startServer", startServer)
response, err := r.SendRPCMessage(
MYTHIC_EXCHANGE,
GetC2RPCStartServerRoutingKey(startServer.Name),
opsecBytes,
exclusiveQueue,
); err != nil {
)
if err != nil {
logging.LogError(err, "Failed to send RPC message")
return &c2StartServerResponse, err
} else if err := json.Unmarshal(response, &c2StartServerResponse); err != nil {
}
err = json.Unmarshal(response, &c2StartServerResponse)
if err != nil {
logging.LogError(err, "Failed to parse start server response back to struct", "response", response)
return &c2StartServerResponse, err
} else {
return &c2StartServerResponse, nil
}
return &c2StartServerResponse, nil
}
Loading

0 comments on commit 0831e8c

Please sign in to comment.