c2 path and eventing wizard updates

This commit is contained in:
its-a-feature
2026-05-06 21:03:56 -05:00
parent 7697254abd
commit c913911476
11 changed files with 1632 additions and 304 deletions
@@ -9,7 +9,7 @@ import MenuList from '@mui/material/MenuList';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Paper from '@mui/material/Paper';
import Badge from '@mui/material/Badge';
import {useTheme} from '@mui/material/styles';
import {alpha, useTheme} from '@mui/material/styles';
import {
Draggable,
} from "@hello-pangea/dnd";
@@ -100,6 +100,8 @@ export function MythicTabLabel(props) {
{(() => {
const selected = selectedIndex === index || snapshot.isDragging;
const tabAccent = tabInfo?.color || theme.palette.primary.main;
const selectedTabSurface = theme.palette.background.paper;
const selectedTabAccent = alpha(tabAccent, theme.palette.mode === "dark" ? 0.34 : 0.2);
return (
<Tab
label={
@@ -120,10 +122,14 @@ export function MythicTabLabel(props) {
margin: "0 3px 0 0",
minHeight: "32px",
borderRadius: `${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0 0`,
border: `1px solid ${theme.borderColor}`,
borderTop: `2px solid ${selected ? tabAccent : "transparent"}`,
borderRight: `1px solid ${theme.borderColor}`,
borderBottom: selected ? `1px solid ${theme.palette.background.paper}` : `1px solid ${theme.borderColor}`,
backgroundColor: selected ? theme.palette.background.paper : theme.surfaces?.muted,
borderLeft: `1px solid ${theme.borderColor}`,
backgroundColor: selected ? selectedTabSurface : theme.surfaces?.muted,
backgroundImage: selected ?
`linear-gradient(180deg, ${selectedTabAccent} 0%, ${selectedTabSurface} 82%)` :
"none",
color: theme.palette.text.primary,
}}
/>
@@ -1,7 +1,7 @@
import React, {useEffect, useCallback, useMemo, useState} from 'react';
import Button from '@mui/material/Button';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import ELK from 'elkjs/lib/elk.bundled.js';
import {useTheme} from '@mui/material/styles';
import OutlinedInput from '@mui/material/OutlinedInput';
@@ -39,6 +39,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
import { MythicAgentSVGIconNoTooltip} from "../../MythicComponents/MythicAgentSVGIcon";
import {ImageWithAuth} from "../../utilities/ImageWithAuth";
import {MythicDialogButton, MythicDialogFooter} from "../../MythicComponents/MythicDialogLayout";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
@@ -249,23 +250,50 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
},
]}, [getLinkCommands, hideCallback, viewConfig, onOpenTab]);
const activeEdgeCount = callbackgraphedges?.filter(edge => edge.end_timestamp === null).length || 0;
const totalEdgeCount = callbackgraphedges?.length || 0;
const egressEdgeCount = callbackgraphedges?.filter(edge => !edge.c2profile?.is_p2p).length || 0;
const p2pEdgeCount = callbackgraphedges?.filter(edge => edge.c2profile?.is_p2p).length || 0;
return (
<>
<div style={{padding: "10px"}}>
<Typography variant='h4' style={{display:"inline-block", marginTop: "10px"}}>
Callback {callback.display_id}'s Egress Path
</Typography>
<div style={{float: "right"}}>
<FormControl sx={{ width: 200, marginTop: "10px" }}>
<InputLabel id="demo-chip-label">Group Callbacks By</InputLabel>
<DialogTitle className="mythic-c2-path-title">
<div className="mythic-c2-path-title-row">
<div className="mythic-c2-path-title-copy">
<Typography component="div" className="mythic-c2-path-title-text">
Callback {callback.display_id}'s Egress Path
</Typography>
<Typography component="div" className="mythic-c2-path-title-subtitle">
Review routes, grouping, and link-tasking options for this callback.
</Typography>
</div>
<div className="mythic-c2-path-summary">
<span className="mythic-c2-path-summary-chip">{activeEdgeCount} active</span>
<span className="mythic-c2-path-summary-chip">{totalEdgeCount} total</span>
<span className="mythic-c2-path-summary-chip">{egressEdgeCount} egress</span>
<span className="mythic-c2-path-summary-chip">{p2pEdgeCount} p2p</span>
</div>
</div>
</DialogTitle>
<DialogContent dividers={true} className="mythic-c2-path-content">
<div className="mythic-c2-path-toolbar">
<div className="mythic-c2-path-toolbar-copy">
<Typography component="div" className="mythic-c2-path-toolbar-title">
Graph View
</Typography>
<Typography component="div" className="mythic-c2-path-toolbar-description">
Adjust labels and grouping without changing callback state.
</Typography>
</div>
<div className="mythic-c2-path-controls">
<FormControl size="small" className="mythic-c2-path-control">
<InputLabel id="c2-path-group-label">Group By</InputLabel>
<Select
labelId="demo-chip-label"
id="demo-chip"
labelId="c2-path-group-label"
id="c2-path-group"
value={selectedGroupBy}
onChange={handleGroupByChange}
input={<OutlinedInput id="select-chip" label="Group Callbacks By" />}
input={<OutlinedInput id="select-c2-path-group" label="Group By" />}
>
{groupByOptions.map((name) => (
<MenuItem
@@ -277,16 +305,17 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
))}
</Select>
</FormControl>
<FormControl sx={{minWidth: 300, marginTop: "10px"}}>
<InputLabel id="demo-multiple-chip-label">Display Properties per Callback</InputLabel>
<FormControl size="small" className="mythic-c2-path-control mythic-c2-path-control-wide">
<InputLabel id="c2-path-label-components-label">Display Properties</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
labelId="c2-path-label-components-label"
id="c2-path-label-components"
multiple
value={selectedComponentOptions}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Display Properties per Callback" />}
input={<OutlinedInput id="select-c2-path-label-components" label="Display Properties" />}
MenuProps={MenuProps}
renderValue={(selected) => selected.join(", ")}
>
{labelComponentOptions.map((name) => (
<MenuItem
@@ -299,9 +328,8 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
))}
</Select>
</FormControl>
</div>
</div>
</div>
<DialogContent style={{height: "calc(70vh)" }}>
{manuallyRemoveEdgeDialogOpen &&
<MythicDialog fullWidth={true} maxWidth="sm" open={manuallyRemoveEdgeDialogOpen}
onClose={()=>{setManuallyRemoveEdgeDialogOpen(false);}}
@@ -330,19 +358,21 @@ export function C2PathDialog({callback, callbackgraphedges, onClose, onOpenTab})
action={"select"} display={"display"} identifier={"display"}/>}
/>
}
<DrawC2PathElementsFlowWithProvider
providedNodes={[callback]}
edges={callbackgraphedges}
view_config={viewConfig}
theme={theme}
contextMenu={contextMenu}
/>
<div className="mythic-c2-path-canvas">
<DrawC2PathElementsFlowWithProvider
providedNodes={[callback]}
edges={callbackgraphedges}
view_config={viewConfig}
theme={theme}
contextMenu={contextMenu}
/>
</div>
</DialogContent>
<DialogActions>
<Button onClick={onClose} variant="contained" color="primary">
<MythicDialogFooter>
<MythicDialogButton onClick={onClose}>
Close
</Button>
</DialogActions>
</MythicDialogButton>
</MythicDialogFooter>
</>
);
}
@@ -226,8 +226,10 @@ export function Callbacks({me}) {
alignItems: "center",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.borderColor}`,
borderTop: `1px solid ${theme.borderColor}`,
borderRight: 0,
borderBottom: `1px solid ${theme.borderColor}`,
borderLeft: `1px solid ${theme.borderColor}`,
borderRadius: `${theme.shape.borderRadius}px 0 0 ${theme.shape.borderRadius}px`,
paddingTop: "3px",
}}>
@@ -23,7 +23,6 @@ import {CallbacksTabsHideMultipleDialog, CallbacksTabsSelectMultipleDialog} from
import {CallbacksTabsTaskMultipleDialog} from "./CallbacksTabsTaskMultipleDialog";
import ip6 from 'ip6';
import {CallbacksContext, OnOpenTabContext, OnOpenTabsContext} from "./CallbacksTop";
import {useTheme} from '@mui/material/styles';
import {GetMythicSetting, useSetMythicSetting, GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
import {DetailedCallbackTable} from "./DetailedCallbackTable";
import {ModifyCallbackMythicTreeGroupsDialog} from "./ModifyCallbackMythicTreeGroupsDialog";
@@ -53,10 +52,10 @@ import NotificationsOffTwoToneIcon from '@mui/icons-material/NotificationsOffTwo
import {CallbacksTableEditTriggerOnCheckinDialog} from "./CallbacksTableEditTriggerOnCheckinDialog";
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwoTone';
import Typography from '@mui/material/Typography';
import {EventTriggerContextSelectDialog} from "../Eventing/EventTriggerContextSelect";
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
import {faFilter} from '@fortawesome/free-solid-svg-icons';
import {getReadableTextColor, isValidHexColor} from "../../MythicComponents/MythicColorInput";
export const getCustomBrowsersQuery = gql(`
query getCustomBrowsersQuery{
@@ -132,12 +131,20 @@ const callbackTableInitialColumns = [
{key: "process_short_name", type: 'string', name: "Process Name", fillWidth: true},
{key: "tags", type: 'tags', name: "Tags", fillWidth: false, disableDoubleClick: true, width: 150}
];
const getCallbackRowStyle = (row) => {
if(!isValidHexColor(row.color)){
return {};
}
return {
backgroundColor: row.color,
color: getReadableTextColor(row.color),
};
}
function CallbacksTablePreMemo(props){
const callbacks = useContext(CallbacksContext);
const onOpenTab = useContext(OnOpenTabContext);
const onOpenTabs = useContext(OnOpenTabsContext);
const interactType = GetMythicSetting({setting_name: "interactType", default_value: "interact", output: "string"});
const theme = useTheme();
const [loadingSettings, setLoadingSettings] = React.useState(true);
const [columnOrder, setColumnOrder] = React.useState(callbackTableInitialColumns);
const [openReorderDialog, setOpenReorderDialog] = React.useState(false);
@@ -155,6 +162,7 @@ function CallbacksTablePreMemo(props){
setOpenEditMythicTreeGroupsDialog(true);
}
const [openEditDescriptionDialog, setOpenEditDescriptionDialog] = React.useState(false);
const [callbackLocalUpdates, setCallbackLocalUpdates] = React.useState({});
const [updateDescriptionMutation] = useMutation(updateDescriptionCallbackMutation, {
update: (cache, {data}) => {
if(data.updateCallback.status === "success"){
@@ -170,6 +178,16 @@ function CallbacksTablePreMemo(props){
}
});
const updateDescriptionRef = React.useRef({payload_description: "", callback_display_id: 0, description: "", color: ""});
const applyCallbackLocalUpdate = React.useCallback(({callback_display_id, description, color}) => {
setCallbackLocalUpdates((currentUpdates) => ({
...currentUpdates,
[String(callback_display_id)]: {description, color},
}));
const callbackIndex = callbacks.findIndex((callback) => callback.display_id === callback_display_id);
if(callbackIndex !== -1){
callbacks[callbackIndex] = {...callbacks[callbackIndex], description, color};
}
}, [callbacks]);
const updateDescription = ({payload_description, callback_display_id, description, color}) => {
updateDescriptionRef.current = {
payload_description: payload_description,
@@ -196,8 +214,26 @@ function CallbacksTablePreMemo(props){
}
}
const updateDescriptionSubmit = React.useCallback( ({callback_display_id, description, color}) => {
updateDescriptionMutation({variables: {callback_display_id: callback_display_id, description, color}})
}, []);
const previousCallback = callbacks.find((callback) => callback.display_id === callback_display_id);
applyCallbackLocalUpdate({callback_display_id, description, color});
updateDescriptionMutation({variables: {callback_display_id: callback_display_id, description, color}}).then(({data}) => {
if(data?.updateCallback?.status !== "success" && previousCallback){
applyCallbackLocalUpdate({
callback_display_id,
description: previousCallback.description,
color: previousCallback.color,
});
}
}).catch(() => {
if(previousCallback){
applyCallbackLocalUpdate({
callback_display_id,
description: previousCallback.description,
color: previousCallback.color,
});
}
});
}, [applyCallbackLocalUpdate, callbacks, updateDescriptionMutation]);
const [openCallbackDropdown, setOpenCallbackDropdown] = React.useState(false);
const callbackDropdownRef = React.useRef({options: [], callback: {}});
const [clickedCallbackID, setClickedCallbackId] = React.useState(0);
@@ -349,19 +385,41 @@ function CallbacksTablePreMemo(props){
async function getCustomBrowsersForContextMenu(){
return await getCustomBrowsers({}).then(result => {return result.data?.custombrowser});
}
const callbackMenuIcon = (icon, intent = "neutral") => (
<span className={`mythic-callback-action-menu-icon mythic-callback-action-menu-icon-${intent}`}>
{icon}
</span>
);
const callbackMenuLabel = (primary, secondary) => (
<span className="mythic-callback-action-menu-label">
<span className="mythic-callback-action-menu-label-primary">{primary}</span>
{secondary &&
<span className="mythic-callback-action-menu-label-secondary">{secondary}</span>
}
</span>
);
const callbackMenuSection = (label) => ({
name: <span className="mythic-callback-action-menu-section-label">{label}</span>,
icon: null,
click: ({event}) => {},
type: "item",
disabled: true,
className: "mythic-callback-action-menu-section"
});
async function onRowContextClick({rowDataStatic}) {
// based on row, return updated options array?
let defaultInteractIcon = <KeyboardIcon style={{paddingRight: "5px"}}/>;
let defaultInteractIcon = <KeyboardIcon fontSize="small"/>;
if(interactType === "interactSplit"){
defaultInteractIcon = <VerticalSplitIcon style={{paddingRight: "5px"}}/>;
defaultInteractIcon = <VerticalSplitIcon fontSize="small"/>;
} else if(interactType === "interactConsole"){
defaultInteractIcon = <TerminalIcon style={{paddingRight: "5px"}}/>;
defaultInteractIcon = <TerminalIcon fontSize="small"/>;
}
let customBrowsers = await getCustomBrowsersForContextMenu();
customBrowsers = customBrowsers.map(b => {
return {
name: b.name,
icon: b.type === 'file' ? <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} /> : <AccountTreeIcon style={{paddingRight: "5px"}}/>,
icon: callbackMenuIcon(b.type === 'file' ? <FontAwesomeIcon icon={faFolderOpen} /> : <AccountTreeIcon fontSize="small" />),
className: "mythic-callback-action-menu-item",
click: ({event}) => {
event.preventDefault();
event.stopPropagation();
@@ -379,20 +437,15 @@ function CallbacksTablePreMemo(props){
})
return [
{
name: <div style={{display: "flex", flexDirection: "column"}}>
<Typography>
{"Callback: " + rowDataStatic.display_id}
</Typography>
<Typography style={{fontSize: theme?.typography.pxToRem(12),}}>
{rowDataStatic.user + "@" + rowDataStatic.host}
</Typography>
</div>,
name: callbackMenuLabel("Callback " + rowDataStatic.display_id, rowDataStatic.user + "@" + rowDataStatic.host),
icon: null, click: ({event}) => {},
type: "item",
disabled: true
disabled: true,
className: "mythic-callback-action-menu-header"
},
callbackMenuSection("Tasking"),
{
name: "Interact", icon: defaultInteractIcon, click: ({event}) => {
name: "Interact", icon: callbackMenuIcon(defaultInteractIcon, "primary"), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
const tabType = interactType;
onOpenTab({
@@ -404,7 +457,55 @@ function CallbacksTablePreMemo(props){
}, type: "item"
},
{
name: "Edit Description and Color", icon: <EditIcon style={{paddingRight: "5px"}} />, click:({event}) => {
name: "Tasking Views", icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: 'Default Tasking', icon: callbackMenuIcon(<KeyboardIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
const tabType = "interact";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: 'Split Tasking', icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
const tabType = "interactSplit";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: "Console View", icon: callbackMenuIcon(<TerminalIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
const tabType = "interactConsole";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: "Expand Callback", icon: callbackMenuIcon(<OpenInNewIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
window.open("/new/callbacks/" + rowDataStatic.display_id, "_blank").focus();
}
},
]
},
callbackMenuSection("Callback State"),
{
name: "Edit Description and Color", icon: callbackMenuIcon(<EditIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click:({event}) => {
event.stopPropagation();
updateDescription({payload_description: rowDataStatic.payload.description,
callback_display_id: rowDataStatic.display_id,
@@ -413,29 +514,10 @@ function CallbacksTablePreMemo(props){
});
}, type: "item"
},
{
name: 'Hide Callback', icon: <VisibilityOffIcon color={"warning"} style={{paddingRight: "5px"}}/>, click: ({event}) => {
event.stopPropagation();
hideCallback({variables: {callback_display_id: rowDataStatic.display_id}});
}, type: "item"
},
{
name: "Exit Callback", icon: <FontAwesomeIcon icon={faSkullCrossbones} style={{color: theme.palette.error.main, cursor: "pointer", marginRight: "8px"}} />,
click: ({event}) => {
taskingData.current = {
"parameters": "",
"ui_feature": "callback_table:exit",
"getConfirmation": true,
id: rowDataStatic.id,
display_id: rowDataStatic.display_id,
acceptText: "exit"
};
setOpenTaskingButton(true);
}, type: "item"
},
{
name: rowDataStatic.locked ? 'Unlock (Locked by ' + rowDataStatic.locked_operator.username + ')' : 'Lock Callback',
icon: rowDataStatic.locked ? (<LockIcon color={"error"} style={{paddingRight: "5px"}}/>) : (<LockOpenIcon color={"success"} style={{paddingRight: "5px"}} />),
icon: callbackMenuIcon(rowDataStatic.locked ? <LockIcon fontSize="small"/> : <LockOpenIcon fontSize="small" />, rowDataStatic.locked ? "warning" : "neutral"),
className: "mythic-callback-action-menu-item",
click: ({event}) => {
event.stopPropagation();
if(rowDataStatic.locked){
@@ -448,17 +530,39 @@ function CallbacksTablePreMemo(props){
{
name: rowDataStatic.trigger_on_checkin_after_time > 0 ? "Adjust Alert Trigger" : "Add New Alert Trigger",
type: "item",
icon: rowDataStatic.trigger_on_checkin_after_time > 0 ? (<NotificationsOffTwoToneIcon color={"warning"} style={{paddingRight: "5px"}} />) : (<NotificationsActiveTwoToneIcon color={"success"} style={{paddingRight: "5px"}}/>),
icon: callbackMenuIcon(rowDataStatic.trigger_on_checkin_after_time > 0 ? <NotificationsOffTwoToneIcon fontSize="small" /> : <NotificationsActiveTwoToneIcon fontSize="small"/>, rowDataStatic.trigger_on_checkin_after_time > 0 ? "warning" : "neutral"),
className: "mythic-callback-action-menu-item",
click: ({event}) => {
event.stopPropagation();
setOpenTriggerDialog({open: true, trigger_on_checkin_after_time: rowDataStatic.trigger_on_checkin_after_time, display_id: rowDataStatic.display_id})
}
},
{
name: "Eventing", icon: <PlayCircleFilledTwoToneIcon />, click: (event) => {}, type: "menu",
name: 'Hide Callback', icon: callbackMenuIcon(<VisibilityOffIcon fontSize="small"/>, "warning"), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
hideCallback({variables: {callback_display_id: rowDataStatic.display_id}});
}, type: "item"
},
{
name: "Exit Callback", icon: callbackMenuIcon(<FontAwesomeIcon icon={faSkullCrossbones} />, "error"), className: "mythic-callback-action-menu-item",
click: ({event}) => {
taskingData.current = {
"parameters": "",
"ui_feature": "callback_table:exit",
"getConfirmation": true,
id: rowDataStatic.id,
display_id: rowDataStatic.display_id,
acceptText: "exit"
};
setOpenTaskingButton(true);
}, type: "item"
},
callbackMenuSection("Workflows and Browsers"),
{
name: "Eventing", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small" />), click: (event) => {}, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: "Start Eventing Workflow", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}} />,
name: "Start Eventing Workflow", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small" />), className: "mythic-callback-action-menu-item",
click: ({event}) => {
eventingDataRef.current = {
name: "callback_id",
@@ -470,10 +574,10 @@ function CallbacksTablePreMemo(props){
]
},
{
name: "Browsers", icon: null, click: () => {}, type: "menu",
name: "Browsers", icon: callbackMenuIcon(<AccountTreeIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: 'File Browser', icon: <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} />,
name: 'File Browser', icon: callbackMenuIcon(<FontAwesomeIcon icon={faFolderOpen} />), className: "mythic-callback-action-menu-item",
click: ({event}) => {
event.preventDefault();
event.stopPropagation();
@@ -487,7 +591,7 @@ function CallbacksTablePreMemo(props){
}
},
{
name: 'Process Browser', icon: <AccountTreeIcon style={{paddingRight: "5px"}}/>,
name: 'Process Browser', icon: callbackMenuIcon(<AccountTreeIcon fontSize="small"/>), className: "mythic-callback-action-menu-item",
click: ({event}) => {
event.preventDefault();
event.stopPropagation();
@@ -501,105 +605,63 @@ function CallbacksTablePreMemo(props){
}
},
{
name: <div style={{display: "flex", flexDirection: "column", width: "100%"}}>
<Typography>
{"Custom Agent Browsers:"}
</Typography>
</div>,
name: <span className="mythic-callback-action-menu-section-label">Custom Agent Browsers</span>,
icon: null, click: ({event}) => {},
type: "item",
disabled: true
disabled: true,
className: "mythic-callback-action-menu-section"
},
...customBrowsers
]
},
{
name: "Bulk Actions", icon: null, click: (event) => { }, type: "menu",
name: "Other Callbacks", icon: callbackMenuIcon(<OpenInNewIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: "Hide Multiple", icon: <VisibilityOffIcon color={"warning"} style={{paddingRight: "5px"}}/>,
click: ({event}) => {
setOpenHideMultipleDialog(true);
name: "Interact", icon: callbackMenuIcon(<KeyboardIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interact"});
}
},
{
name: "Task Multiple", icon: <FontAwesomeIcon icon={faList} style={{cursor: "pointer", marginRight: "10px"}} />,
click: ({event}) => {
setOpenTaskMultipleDialog({open: true, data: rowDataStatic});
name: "Split Tasking", icon: callbackMenuIcon(<VerticalSplitIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interactSplit"});
}
},
{
name: "Start Multiple Eventing Workflows", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}}/>,
click: ({event}) => {
setOpenEventingMultipleDialog(true);
name: "Console View", icon: callbackMenuIcon(<TerminalIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interactConsole"});
}
},
{
name: "File Browser", icon: callbackMenuIcon(<FontAwesomeIcon icon={faFolderOpen} />), className: "mythic-callback-action-menu-item", click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "fileBrowser"});
}
},
{
name: "Process Browser", icon: callbackMenuIcon(<AccountTreeIcon fontSize="small"/>), className: "mythic-callback-action-menu-item", click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "processBrowser"});
}
}
]
},
callbackMenuSection("Metadata"),
{
name: "Tasking Views", icon: null, click: () => {}, type: "menu",
name: "Metadata", icon: callbackMenuIcon(<InfoIcon fontSize="small" />), click: () => {}, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: 'Default Tasking', icon: <KeyboardIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
event.stopPropagation();
const tabType = "interact";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: 'Split Tasking', icon: <VerticalSplitIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
event.stopPropagation();
const tabType = "interactSplit";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: "Console View", icon: <TerminalIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
event.stopPropagation();
const tabType = "interactConsole";
onOpenTab({
tabType: tabType,
tabID: rowDataStatic.id + tabType,
callbackID: rowDataStatic.id,
color: rowDataStatic.color,
displayID: rowDataStatic.display_id});
}
},
{
name: "Expand Callback", icon: <OpenInNewIcon style={{paddingRight: "5px"}} />, click: ({event}) => {
event.stopPropagation();
window.open("/new/callbacks/" + rowDataStatic.display_id, "_blank").focus();
}
},
]
},
{
name: "Metadata", icon: null, click: () => {}, type: "menu",
menuItems: [
{
name: "Export Callback", icon: <ImportExportIcon style={{paddingRight: "5px"}} />, click: ({event}) => {
name: "Export Callback", icon: callbackMenuIcon(<ImportExportIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
exportConfig({variables: {agent_callback_id: rowDataStatic.agent_callback_id}});
}
},
{
name: "View Metadata", icon: <InfoIcon color={"info"} style={{paddingRight: "5px"}} />, click: ({event}) => {
name: "View Metadata", icon: callbackMenuIcon(<InfoIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
metaDialog(rowDataStatic.id);
}
},
{
name: "Modify Groupings", icon: <WidgetsIcon color={"info"} style={{paddingRight: "5px"}} />, click: ({event}) => {
name: "Modify Groupings", icon: callbackMenuIcon(<WidgetsIcon fontSize="small" />), className: "mythic-callback-action-menu-item", click: ({event}) => {
event.stopPropagation();
editMythicTreeGroupsDialog(rowDataStatic.id);
}
@@ -607,31 +669,24 @@ function CallbacksTablePreMemo(props){
]
},
{
name: "Other Callbacks", icon: null, click: () => {}, type: "menu",
name: "Bulk Actions", icon: callbackMenuIcon(<FontAwesomeIcon icon={faList} />), click: (event) => { }, type: "menu", className: "mythic-callback-action-menu-item",
menuItems: [
{
name: "Interact", icon: <KeyboardIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interact"});
name: "Hide Multiple", icon: callbackMenuIcon(<VisibilityOffIcon fontSize="small"/>, "warning"), className: "mythic-callback-action-menu-item",
click: ({event}) => {
setOpenHideMultipleDialog(true);
}
},
{
name: "Split Tasking", icon: <VerticalSplitIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interactSplit"});
name: "Task Multiple", icon: callbackMenuIcon(<FontAwesomeIcon icon={faList} />), className: "mythic-callback-action-menu-item",
click: ({event}) => {
setOpenTaskMultipleDialog({open: true, data: rowDataStatic});
}
},
{
name: "Console View", icon: <TerminalIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "interactConsole"});
}
},
{
name: "File Browser", icon: <FontAwesomeIcon icon={faFolderOpen} style={{color: theme.folderColor, cursor: "pointer", marginRight: "10px"}} />, click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "fileBrowser"});
}
},
{
name: "Process Browser", icon: <AccountTreeIcon style={{paddingRight: "5px"}}/>, click: ({event}) => {
setOpenMultipleTabsDialog({open: true, tabType: "processBrowser"});
name: "Start Multiple Eventing Workflows", icon: callbackMenuIcon(<PlayCircleFilledTwoToneIcon fontSize="small"/>), className: "mythic-callback-action-menu-item",
click: ({event}) => {
setOpenEventingMultipleDialog(true);
}
}
]
@@ -779,7 +834,10 @@ function CallbacksTablePreMemo(props){
return false;
}
const sortedData = React.useMemo(() => {
const tempData = [...callbacks];
const tempData = callbacks.map((callback) => {
const localUpdate = callbackLocalUpdates[String(callback.display_id)];
return localUpdate ? {...callback, ...localUpdate} : callback;
});
if (sortData.sortType === 'number' || sortData.sortType === 'size' || sortData.sortType === 'date') {
tempData.sort((a, b) => (parseInt(a[sortData.sortKey]) > parseInt(b[sortData.sortKey]) ? 1 : -1));
@@ -823,7 +881,7 @@ function CallbacksTablePreMemo(props){
return <CallbacksTableIDCell
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
key={`callback${row.id}_${c.name}`}
callbackDropdown={callbackDropdown}
@@ -834,7 +892,7 @@ function CallbacksTablePreMemo(props){
cellData={row.mythictree_groups.join(", ")}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
/>;
case "IP":
@@ -843,105 +901,105 @@ function CallbacksTablePreMemo(props){
cellData={row.ip}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
callback_id={row.id} />;
case "External IP":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.external_ip}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "Host":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.host}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "User":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.user + (row.impersonation_context === "" ? "" : ` [${row.impersonation_context}]`)}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "Domain":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.domain}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "OS":
return <CallbacksTableOSCell key={`callback${row.id}_${c.name}`}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
cellData={row.os} />;
case "Arch":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
cellData={row.architecture} />;
case "PID":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.pid}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "Last Checkin":
return <CallbacksTableLastCheckinCell key={`callback${row.id}_${c.name}`} me={props.me}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
cellData={row.last_checkin} />;
case "Description":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.description}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "Sleep":
return <CallbacksTableSleepCell key={`callback${row.id}_${c.name}`}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
cellData={row.sleep_info} updateSleepInfo={updateSleepInfo} />;
case "Agent":
return <CallbacksTablePayloadTypeCell key={`callback${row.id}_${c.name}`}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}}
cellData={row.payload.payloadtype.name}/>;
case "C2":
return <CallbacksTableC2Cell key={`callback${row.id}_c2`}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />
case "Process Name":
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`}
cellData={row.process_short_name}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
case "Tags":
return <CallbacksTableTagsCell key={`callback${row.id}_${c.name}`}
cellData={row.tags}
rowData={{...row,
selected: row.id === clickedCallbackID,
rowStyle: {backgroundColor: `${row.color}`},
rowStyle: getCallbackRowStyle(row),
}} />;
}
})];
}
}, [])
}, [callbacks, sortData, filterOptions, columnVisibility, clickedCallbackID]);
}, [callbacks, callbackLocalUpdates, sortData, filterOptions, columnVisibility, clickedCallbackID]);
const onSubmitFilterOptions = (value) => {
setFilterOptions({...filterOptions, [selectedColumn.key]: value });
try{
@@ -1087,11 +1145,13 @@ function CallbacksTablePreMemo(props){
isOpen={callbackDropdownRef.current.dropdownAnchorRef}
onOpen={setOpenCallbackDropdown}
externallyOpen={openCallbackDropdown}
minWidth={285}
menu={
callbackDropdownRef.current.options.map((option, index) => (
option.type === 'item' ? (
<DropdownMenuItem
key={option.name}
key={"callback-action-" + index}
className={option.className}
disabled={option.disabled}
onClick={(event) => handleMenuItemClick(event, option.click)}
>
@@ -1099,12 +1159,15 @@ function CallbacksTablePreMemo(props){
</DropdownMenuItem>
) : option.type === 'menu' ? (
<DropdownNestedMenuItem
label={option.name}
key={"callback-action-" + index}
className={option.className}
label={<span className="mythic-callback-action-menu-nested-label">{option.icon}{option.name}</span>}
disabled={option.disabled}
menu={
option.menuItems.map((menuOption, indx) => (
<DropdownMenuItem
key={menuOption.name}
key={"callback-action-" + index + "-" + indx}
className={menuOption.className}
disabled={menuOption.disabled}
onClick={(event) => handleMenuItemClick(event, menuOption.click)}
>
@@ -11,7 +11,7 @@ import "ace-builds/src-noconflict/theme-monokai";
import "ace-builds/src-noconflict/ext-searchbox";
import {useTheme} from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import {isValidHexColor, MythicColorSwatchInput} from '../../MythicComponents/MythicColorInput';
import {getReadableTextColor, isValidHexColor, MythicColorSwatchInput} from '../../MythicComponents/MythicColorInput';
export function CallbacksTableEditDescriptionColorDialog(props) {
const [comment, setComment] = React.useState("");
@@ -42,7 +42,10 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
setColor(props.color);
}, [props.description, props.color]);
const previewColor = isValidHexColor(color) ? color : "";
const previewBorder = previewColor === "" ? "1px dashed" : `2px solid ${previewColor}`;
const darkPreviewBackground = previewColor || "#1f2937";
const lightPreviewBackground = previewColor || "#f8fafc";
const darkPreviewTextColor = previewColor ? getReadableTextColor(previewColor) : "#ffffff";
const lightPreviewTextColor = previewColor ? getReadableTextColor(previewColor) : "#111827";
return (
<React.Fragment>
<DialogTitle id="form-dialog-title">{props.title}</DialogTitle>
@@ -77,7 +80,7 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, mb: 1}}>
<Box sx={{minWidth: 0}}>
<Typography variant="body2" sx={{fontWeight: 700}}>Callback Color</Typography>
<Typography variant="caption" sx={{color: "text.secondary"}}>Callback row and tasking accent</Typography>
<Typography variant="caption" sx={{color: "text.secondary"}}>Callback row background and tasking accent</Typography>
</Box>
<Box sx={{display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap", justifyContent: "flex-end"}}>
<MythicColorSwatchInput
@@ -98,12 +101,12 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
display: "flex",
alignItems: "center",
borderRadius: "6px",
border: previewBorder,
borderColor: previewColor === "" ? "divider" : previewColor,
backgroundColor: "#1f2937",
border: "1px solid",
borderColor: "divider",
backgroundColor: darkPreviewBackground,
}}
>
<Typography sx={{color: "#ffffff", fontWeight: 700}}>Dark callback row</Typography>
<Typography sx={{color: darkPreviewTextColor, fontWeight: 700}}>Dark callback row</Typography>
</Box>
<Box
sx={{
@@ -112,12 +115,12 @@ export function CallbacksTableEditDescriptionColorDialog(props) {
display: "flex",
alignItems: "center",
borderRadius: "6px",
border: previewBorder,
borderColor: previewColor === "" ? "divider" : previewColor,
backgroundColor: "#f8fafc",
border: "1px solid",
borderColor: "divider",
backgroundColor: lightPreviewBackground,
}}
>
<Typography sx={{color: "#111827", fontWeight: 700}}>Light callback row</Typography>
<Typography sx={{color: lightPreviewTextColor, fontWeight: 700}}>Light callback row</Typography>
</Box>
</Box>
</Box>
@@ -519,9 +519,14 @@ export const CallbacksTableSleepCell = React.memo( ({rowData, cellData, updateSl
}, areEqual)
export const CallbacksTableTagsCell = React.memo(({rowData, cellData}) => {
return (
<div>
<div className="mythic-callback-tagsCell">
<ViewEditTags target_object={"callback_id"} target_object_id={rowData.id} />
<TagsDisplay tags={cellData} expand={false} />
<div className="mythic-callback-tagsList">
{cellData?.length > 0 ?
<TagsDisplay tags={cellData} expand={false} /> :
<span className="mythic-callback-tagsEmpty">No tags</span>
}
</div>
</div>
)
}, areEqual)
@@ -498,6 +498,26 @@ const TaskTagDisplay = ({task}) => {
<TagsDisplay tags={task.tags} />
)
}
const isTaskHeaderInteractiveClick = (e) => {
const clickedInteractiveElement = e.target?.closest?.(
`a, button, [role="button"], input, select, textarea`
);
return Boolean(clickedInteractiveElement && e.currentTarget.contains(clickedInteractiveElement));
}
const handleTaskHeaderClick = (e, onClick) => {
if(window.getSelection().toString() !== ""){
return;
}
if(isTaskHeaderInteractiveClick(e)){
e.stopPropagation();
return;
}
if(onClick){
e.stopPropagation();
e.preventDefault();
onClick(e);
}
}
const ColoredTaskDisplay = ({task, theme, children, expanded}) => {
const themeColor = getTaskAccentColor(task, theme);
return(
@@ -529,11 +549,6 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
e.stopPropagation();
//e.preventDefault();
}
const onLocalClick = (e) => {
if(onClick){
onClick(e);
}
}
const onClickKillIcon = (e, open) => {
if(e){
e.stopPropagation();
@@ -542,7 +557,8 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
}
return (
<ColoredTaskDisplay task={task} theme={theme} expanded={expanded} >
<div id={taskDivID} className={compact ? `${classes.taskHeaderBody} ${classes.taskHeaderBodyCompact}` : classes.taskHeaderBody}>
<div id={taskDivID} className={compact ? `${classes.taskHeaderBody} ${classes.taskHeaderBodyCompact}` : classes.taskHeaderBody}
onClick={(e) => handleTaskHeaderClick(e, onClick)}>
{displayComment && (
<div className={classes.taskCommentBlock} onClick={preventPropagation}>
<Typography component="div" sx={{fontSize: "0.72rem", fontWeight: 800, color: theme.palette.text.secondary, mb: 0.5}}>
@@ -553,7 +569,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
</Typography>
</div>
)}
<div className={classes.taskMetaRow} onClick={preventPropagation}>
<div className={classes.taskMetaRow}>
{taskingDisplayFields.map((fieldName) => (
<TaskingMetadataField
displayTimestamp={displayTimestamp}
@@ -603,7 +619,7 @@ export const ColoredTaskLabel = ({task, theme, me, taskDivID, onClick, displayCh
}
</span>
</div>
<div className={classes.taskCommandRow} onClick={onLocalClick}>
<div className={classes.taskCommandRow}>
{task.tasks.length > 0 &&
<IconButton className={classes.taskChildToggle} size="small" onClick={toggleDisplayChildren}>
{displayChildren ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
@@ -999,6 +1015,7 @@ const TaskLabel = ({task, dropdownOpen, toggleTaskDropdown, me, newlyIssuedTasks
<ColoredTaskLabel theme={theme} task={task} me={me} taskDivID={'scrolltotask' + task.id}
displayChildren={displayChildren} toggleDisplayChildren={toggleDisplayChildren}
expanded={dropdownOpen}
onClick={toggleTaskDropdown}
/>
</StyledAccordionSummary>
<TaskDisplayContainer key={task.id} me={me} task={task} />
@@ -1191,10 +1208,6 @@ export const ColoredTaskLabelConsole = ({task, theme, me, taskDivID, onClick, di
const [openKillTaskButton, setOpenKillTaskButton] = React.useState({open: false});
const command = task?.command?.cmd || task.command_name;
const commandLine = command + " " + task.display_params;
const preventPropagation = (e) => {
e.stopPropagation();
//e.preventDefault();
}
const onClickKillIcon = (e, open) => {
if(e){
e.stopPropagation();
@@ -1203,8 +1216,9 @@ export const ColoredTaskLabelConsole = ({task, theme, me, taskDivID, onClick, di
}
return (
<ColoredTaskDisplayConsole task={task} theme={theme} expanded={expanded}>
<div id={taskDivID} className={classes.taskHeaderBody}>
<div className={classes.taskMetaRow} onClick={preventPropagation}>
<div id={taskDivID} className={classes.taskHeaderBody}
onClick={(e) => handleTaskHeaderClick(e, onClick)}>
<div className={classes.taskMetaRow}>
{taskingDisplayFields.map((fieldName) => (
<TaskingMetadataField
compactTimestamp
@@ -288,6 +288,13 @@ export const commandInParsedParameters = (cmd, parsedParameters) =>{
}
return undefined
}
const getCaptureFileInputName = (parameterName, index) => {
const safeName = String(parameterName || "file").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
return `FILE_${safeName || "PARAMETER"}${index === undefined ? "" : "_" + (index + 1)}`;
}
const isSelectedFileObject = (value) => {
return value !== undefined && value !== null && typeof value !== "string" && value.name !== undefined && value.name !== "";
}
export function TaskParametersDialog(props) {
const [backdropOpen, setBackdropOpen] = React.useState(false);
const [commandInfo, setCommandInfo] = useState({});
@@ -839,6 +846,7 @@ export function TaskParametersDialog(props) {
}, [selectedParameterGroup, rawParameters, loadedCommandsLoading, allCommandsLoading, loadedAllEdgesLoading, requiredPieces, loadedAllPayloadsLoading, loadedCredentialsLoading, loadedAllPayloadsOnHostsLoading, props.callback_id, props.choices]);
const onSubmit = async () => {
let newFileUUIDs = [];
let capturedFiles = [];
let collapsedParameters = {};
for(const param of parameters){
switch(param.type){
@@ -863,6 +871,25 @@ export function TaskParametersDialog(props) {
collapsedParameters[param.name] = param.value;
break
case "File":
if(props.captureOnly){
if(isSelectedFileObject(param.value)){
const inputName = getCaptureFileInputName(param.name);
capturedFiles.push({
file: param.value,
filename: param.value.name,
index: 0,
inputName,
parameterName: param.name,
});
collapsedParameters[param.name] = inputName;
}else if(typeof param.value === "string" && param.value.length > 0){
collapsedParameters[param.name] = param.value;
}else if(param.required){
snackActions.warning("Missing file for " + param.display_name);
return;
}
break;
}
const newUUID = await UploadTaskFile(param.value, "Uploaded as part of tasking");
if(newUUID){
if(newUUID !== "Missing file in form"){
@@ -874,6 +901,30 @@ export function TaskParametersDialog(props) {
}
break;
case "FileMultiple":
if(props.captureOnly){
let fileInputNames = [];
for(let i = 0; i < param.value.length; i++){
if(typeof param.value[i] === "string"){
fileInputNames.push(param.value[i]);
}else if(isSelectedFileObject(param.value[i])){
const inputName = getCaptureFileInputName(param.name, i);
capturedFiles.push({
file: param.value[i],
filename: param.value[i].name,
index: i,
inputName,
parameterName: param.name,
});
fileInputNames.push(inputName);
}
}
if(fileInputNames.length === 0 && param.required){
snackActions.warning("Missing files for " + param.display_name);
return;
}
collapsedParameters[param.name] = fileInputNames;
break;
}
let fileIDs = [];
for(let i = 0; i < param.value.length; i++){
if(typeof param.value[i] === "string"){
@@ -908,7 +959,10 @@ export function TaskParametersDialog(props) {
}
}
setBackdropOpen(false);
props.onSubmit(commandInfo.cmd, JSON.stringify(collapsedParameters), newFileUUIDs, selectedParameterGroup, commandInfo?.payloadtype?.name);
props.onSubmit(commandInfo.cmd, JSON.stringify(collapsedParameters), props.captureOnly ? capturedFiles : newFileUUIDs, selectedParameterGroup, commandInfo?.payloadtype?.name, {
capturedFiles,
collapsedParameters,
});
}
const onAgentConnectAddNewPayloadOnHost = (host, payload) => {
@@ -1038,7 +1092,7 @@ export function TaskParametersDialog(props) {
Close
</Button>
<Button onClick={onSubmit} disabled={submenuOpenPreventTask} variant="contained" color="warning">
Task
{props.captureOnly ? "Use Parameters" : "Task"}
</Button>
</DialogActions>
</React.Fragment>
File diff suppressed because it is too large Load Diff
+375 -6
View File
@@ -523,7 +523,6 @@ tspan {
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.11 : 0.07)},
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.11 : 0.07)}) !important;
border-bottom-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.3 : 0.2)};
color: ${(props) => props.theme.palette.text.primary} !important;
}
.MythicResizableGrid-cell.MythicResizableGrid-contextRow {
background-image: linear-gradient(0deg,
@@ -531,13 +530,22 @@ tspan {
${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.18 : 0.1)}) !important;
border-bottom-color: ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.info.main, props.theme.palette.mode === "dark" ? 0.52 : 0.34)};
color: ${(props) => props.theme.palette.text.primary} !important;
}
.MythicResizableGrid-cell.selectedCallback {
background-color: ${(props) => props.theme.table?.selected || props.theme.selectedCallbackColor + "CC"} !important;
background-image: linear-gradient(0deg,
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)},
${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.2 : 0.12)}) !important;
border-bottom-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)},
inset 0 -1px 0 ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
}
.MythicResizableGrid-cell.selectedCallbackHierarchy {
background-color: ${(props) => props.theme.table?.selectedHierarchy || props.theme.selectedCallbackHierarchyColor + "CC"} !important;
background-image: linear-gradient(0deg,
${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.22 : 0.13)},
${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.22 : 0.13)}) !important;
border-bottom-color: ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
box-shadow: inset 0 1px 0 ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)},
inset 0 -1px 0 ${(props) => alpha(props.theme.palette.secondary.main, props.theme.palette.mode === "dark" ? 0.58 : 0.4)};
}
.MythicResizableGrid-cell.MythicResizableGrid-rowInteractive {
cursor: pointer !important;
@@ -563,6 +571,16 @@ tspan {
opacity: 1;
width: 4px;
}
.MythicResizableGrid-cell.MythicResizableGrid-rowFirstCell.selectedCallback::before {
background-color: ${(props) => props.theme.palette.primary.main};
opacity: 1;
width: 4px;
}
.MythicResizableGrid-cell.MythicResizableGrid-rowFirstCell.selectedCallbackHierarchy::before {
background-color: ${(props) => props.theme.palette.secondary.main};
opacity: 1;
width: 4px;
}
.MythicResizableGrid-cell.MythicResizableGrid-rowLastCell {
border-right-color: ${(props) => props.theme.table?.border || props.theme.borderColor};
}
@@ -617,6 +635,114 @@ tspan {
text-overflow: ellipsis;
white-space: nowrap;
}
.mythic-callback-action-menu-header.MuiMenuItem-root,
.mythic-callback-action-menu-header.MuiMenuItem-root.Mui-disabled {
cursor: default;
opacity: 1;
padding: 0.55rem 0.8rem 0.45rem;
}
.mythic-callback-action-menu-label {
display: flex;
flex-direction: column;
gap: 0.08rem;
min-width: 0;
}
.mythic-callback-action-menu-label-primary {
color: ${(props) => props.theme.palette.text.primary};
font-size: 0.82rem;
font-weight: 800;
line-height: 1.2;
}
.mythic-callback-action-menu-label-secondary {
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.72rem;
font-weight: 600;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mythic-callback-action-menu-section.MuiMenuItem-root,
.mythic-callback-action-menu-section.MuiMenuItem-root.Mui-disabled {
border-top: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
cursor: default;
margin-top: 0.25rem;
min-height: 26px;
opacity: 1;
padding: 0.45rem 0.8rem 0.18rem;
}
.mythic-callback-action-menu-section:first-of-type {
border-top: 0;
}
.mythic-callback-action-menu-section-label {
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.68rem;
font-weight: 850;
letter-spacing: 0.02em;
line-height: 1.2;
}
.mythic-callback-action-menu-item.MuiMenuItem-root {
align-items: center;
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.text.primary};
font-size: 0.78rem;
font-weight: 650;
gap: 0;
margin: 0.08rem 0.35rem;
min-height: 34px;
padding: 0.35rem 0.5rem;
}
.mythic-callback-action-menu-item.MuiMenuItem-root:hover,
.mythic-callback-action-menu-item.MuiMenuItem-root[data-open="true"] {
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.08)};
}
.mythic-callback-action-menu-nested-label {
align-items: center;
display: inline-flex;
gap: 0;
min-width: 0;
}
.mythic-callback-action-menu-icon {
align-items: center;
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.text.secondary};
display: inline-flex;
flex: 0 0 auto;
font-size: 0.82rem;
height: 22px;
justify-content: center;
line-height: 1;
margin-right: 0.7rem;
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
width: 22px;
}
.mythic-callback-action-menu-icon svg {
display: block;
font-size: 0.92rem;
}
.mythic-callback-action-menu-icon-primary {
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
color: ${(props) => props.theme.palette.primary.main};
}
.mythic-callback-action-menu-icon-warning {
background-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
border-color: ${(props) => alpha(props.theme.palette.warning.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
color: ${(props) => props.theme.palette.warning.main};
}
.mythic-callback-action-menu-icon-error {
background-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.12 : 0.07)};
border-color: ${(props) => alpha(props.theme.palette.error.main, props.theme.palette.mode === "dark" ? 0.34 : 0.22)};
color: ${(props) => props.theme.palette.error.main};
}
.mythic-callback-action-menu-item.MuiMenuItem-root:hover .mythic-callback-action-menu-icon-neutral,
.mythic-callback-action-menu-item.MuiMenuItem-root[data-open="true"] .mythic-callback-action-menu-icon-neutral {
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.09)};
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.3)};
color: ${(props) => props.theme.palette.primary.main};
}
.mythic-callback-iconButton.MuiIconButton-root {
border: 1px solid transparent;
border-radius: ${(props) => props.theme.shape.borderRadius}px;
@@ -702,7 +828,7 @@ tspan {
}
.mythic-callback-displayId {
align-items: center;
color: ${(props) => props.theme.palette.text.primary};
color: inherit;
display: inline-flex;
flex: 0 0 auto;
font-weight: 700;
@@ -768,6 +894,185 @@ tspan {
display: block;
font-size: inherit;
}
.mythic-callback-tagsCell {
align-items: center;
display: flex;
gap: 0.35rem;
height: 100%;
min-width: 0;
overflow: hidden;
width: 100%;
}
.mythic-callback-tagsCell .MuiIconButton-root {
align-items: center !important;
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.text.secondary} !important;
display: inline-flex !important;
flex: 0 0 auto;
float: none !important;
height: 22px;
justify-content: center !important;
padding: 0 !important;
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
width: 22px;
}
.mythic-callback-tagsCell .MuiIconButton-root:hover {
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.09)} !important;
border-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.44 : 0.3)};
color: ${(props) => props.theme.palette.primary.main} !important;
}
.mythic-callback-tagsCell .MuiIconButton-root svg {
font-size: 0.92rem;
}
.mythic-callback-tagsList {
align-items: center;
display: flex;
flex: 1 1 auto;
flex-wrap: nowrap;
gap: 0.2rem;
min-width: 0;
overflow: hidden;
}
.mythic-callback-tagsCell .MuiChip-root {
border: 1px solid ${(props) => alpha(props.theme.palette.common.black, props.theme.palette.mode === "dark" ? 0.2 : 0.08)};
flex: 0 0 auto;
float: none !important;
height: 18px !important;
max-width: 5.5rem;
}
.mythic-callback-tagsCell .MuiChip-label {
font-size: 0.68rem;
font-weight: 800;
padding-left: 0.35rem;
padding-right: 0.35rem;
text-overflow: ellipsis;
}
.mythic-callback-tagsEmpty {
color: ${(props) => props.theme.palette.text.disabled};
font-size: 0.72rem;
font-weight: 650;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mythic-c2-path-title.MuiDialogTitle-root {
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
padding: 0.85rem 1rem;
}
.mythic-c2-path-title-row {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: space-between;
min-width: 0;
}
.mythic-c2-path-title-copy {
min-width: min(100%, 18rem);
}
.mythic-c2-path-title-text.MuiTypography-root {
color: ${(props) => props.theme.palette.text.primary};
font-size: 1rem;
font-weight: 850;
line-height: 1.2;
}
.mythic-c2-path-title-subtitle.MuiTypography-root {
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.78rem;
line-height: 1.35;
margin-top: 0.18rem;
}
.mythic-c2-path-summary {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.mythic-c2-path-summary-chip {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.045)" : "rgba(0,0,0,0.025)"};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.7rem;
font-weight: 800;
line-height: 1;
padding: 0.32rem 0.45rem;
white-space: nowrap;
}
.mythic-c2-path-content.MuiDialogContent-root {
display: flex;
flex-direction: column;
gap: 0.75rem;
height: min(72vh, 820px);
min-height: 28rem;
padding: 0.85rem;
}
.mythic-c2-path-toolbar {
align-items: center;
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: space-between;
min-width: 0;
padding: 0.7rem;
}
.mythic-c2-path-toolbar-copy {
min-width: min(100%, 14rem);
}
.mythic-c2-path-toolbar-title.MuiTypography-root {
color: ${(props) => props.theme.palette.text.primary};
font-size: 0.84rem;
font-weight: 800;
line-height: 1.2;
}
.mythic-c2-path-toolbar-description.MuiTypography-root {
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.74rem;
line-height: 1.35;
margin-top: 0.12rem;
}
.mythic-c2-path-controls {
align-items: center;
display: flex;
flex: 1 1 24rem;
flex-wrap: wrap;
gap: 0.65rem;
justify-content: flex-end;
min-width: min(100%, 18rem);
}
.mythic-c2-path-control.MuiFormControl-root {
min-width: 11rem;
}
.mythic-c2-path-control-wide.MuiFormControl-root {
min-width: min(100%, 20rem);
}
.mythic-c2-path-canvas {
background-color: ${(props) => props.theme.palette.background.paper};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
flex: 1 1 auto;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.mythic-c2-path-canvas .react-flow__controls {
box-shadow: none;
}
.mythic-c2-path-canvas .react-flow__controls-button {
background-color: ${(props) => props.theme.palette.background.paper};
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
color: ${(props) => props.theme.palette.text.secondary};
}
.mythic-c2-path-canvas .react-flow__controls-button:hover {
background-color: ${(props) => alpha(props.theme.palette.primary.main, props.theme.palette.mode === "dark" ? 0.16 : 0.08)};
color: ${(props) => props.theme.palette.primary.main};
}
.mythic-callback-trigger-summary {
align-items: flex-start;
background-color: ${(props) => props.theme.surfaces?.muted || props.theme.palette.background.paper};
@@ -4083,9 +4388,29 @@ tspan {
display: inline-flex;
font-size: 0.72rem;
font-weight: 750;
gap: 0.2rem;
line-height: 1.2;
min-height: 1.45rem;
padding: 0 0.45rem;
padding: 0 0.18rem 0 0.45rem;
}
.mythic-eventing-file-chip-name {
max-width: 14rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mythic-eventing-file-chip-remove.MuiIconButton-root {
color: ${(props) => props.theme.palette.text.secondary} !important;
height: 1.15rem;
padding: 0;
width: 1.15rem;
}
.mythic-eventing-file-chip-remove.MuiIconButton-root:hover {
background-color: ${(props) => props.theme.palette.error.main + "18"} !important;
color: ${(props) => props.theme.palette.error.main} !important;
}
.mythic-eventing-file-chip-remove svg {
font-size: 0.82rem;
}
.mythic-eventing-metadata-editor {
min-height: 16rem;
@@ -4607,6 +4932,7 @@ tspan {
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.warning.main};
display: inline-flex;
align-items: center;
font-size: 0.64rem;
font-weight: 850;
line-height: 1;
@@ -4627,6 +4953,49 @@ tspan {
.mythic-eventing-action-data-control .MuiTextField-root {
width: 100%;
}
.mythic-eventing-task-create-command-row {
align-items: flex-start;
display: flex;
flex-direction: column;
gap: 0.45rem;
min-width: 0;
}
.mythic-eventing-task-create-command-actions {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
justify-content: flex-start;
}
.mythic-eventing-task-helper-summary {
background-color: ${(props) => props.theme.palette.info.main + "10"};
border: 1px solid ${(props) => props.theme.palette.info.main + "2f"};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
color: ${(props) => props.theme.palette.text.secondary};
font-size: 0.76rem;
font-weight: 650;
line-height: 1.4;
margin-bottom: 0.65rem;
padding: 0.55rem 0.65rem;
}
.mythic-eventing-task-helper-filter {
margin-bottom: 0.65rem;
}
.mythic-eventing-task-helper-table {
height: min(50vh, 34rem);
min-height: 18rem;
overflow-y: auto;
}
.mythic-eventing-task-helper-preview {
color: ${(props) => props.theme.palette.text.secondary};
display: -webkit-box;
font-size: 0.74rem;
line-height: 1.35;
max-height: 2.1rem;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.mythic-eventing-action-array-list {
display: flex;
flex-direction: column;
@@ -2,6 +2,8 @@ package webcontroller
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
@@ -9,7 +11,6 @@ import (
"github.com/its-a-feature/Mythic/logging"
"github.com/pelletier/go-toml/v2"
"gopkg.in/yaml.v3"
"net/http"
)
type EventingExportInput struct {
@@ -90,9 +91,11 @@ func getFormattedEventingFile(eventGroup *databaseStructs.EventGroup, includeSte
RunAs: eventGroup.RunAs,
}
if includeSteps {
err := database.DB.Select(&eventGroup.Steps, `SELECT * FROM eventstep WHERE eventgroup_id=$1`, eventGroup.ID)
if err != nil {
logging.LogError(err, "failed to get eventgroup steps")
if len(eventGroup.Steps) == 0 {
err := database.DB.Select(&eventGroup.Steps, `SELECT * FROM eventstep WHERE eventgroup_id=$1`, eventGroup.ID)
if err != nil {
logging.LogError(err, "failed to get eventgroup steps")
}
}
for _, step := range eventGroup.Steps {
exportedEventGroup.Steps = append(exportedEventGroup.Steps, eventing.EventStep{