Skip to content

Commit

Permalink
v3.2.17
Browse files Browse the repository at this point in the history
bug fixes and new features for mythic-cli, mythic_server, and the UI
  • Loading branch information
its-a-feature committed Feb 6, 2024
1 parent 2aa0c39 commit af58bc9
Show file tree
Hide file tree
Showing 90 changed files with 2,141 additions and 554,123 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Docker_Templates/
documentation-docker/content/
documentation-docker/public/
display_output.txt
full_attack.json
nginx-docker/config/conf.d/services.conf
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ 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.2.17] - 2024-02-06

### Changed

- Added ability to export a callback (via callback dropdown) and import callback (via speeddial on top right of callbacks page)
- Added a new environment variable, `global_server_name`, that gets passed down to webhook and logging containers
- Added new `mythic-cli config help` subcommand to get helpful descriptions of all environment variables in .env file
- Updated logging to track user_id, username, and source of requests
- Updated internal MITRE ATT&CK to the latest as of 2024-02-06

## [3.2.16] - 2024-01-28

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

### Changed

- Added syntax highlighter and wrap option to plaintext output
- Added a little buffer above the task input field so it's a bit more obvious when the last task's output is done
- Fixed a bug where multiple browserscript elements would be aligned horizontally instead of vertically
- Fixed a bug where cancelling a dialog modal would prevent future dialog modals until page refresh
- Added new features for tagging data:
- Tag data values can now be formatted in partial markdown for additional configuration options
- The following format is available: `"key": "[display data](url){:target=\"_blank\"}"`
- This is displayed as a clickable link text "display data" that opens a new page to `url`
- The following format is available: `"key": "[display data](url){:target=\"api\",:color=\"success\"}"`
- This is displayed as a colored webhook icon that hits the url api with a GET request to `url` and has "display data" next to it

## [0.1.61] - 2024-01-29

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export const MythicFileContext = ({agent_file_id, display_link, filename}) => {
onCompleted: (data) => {
setFileData( {...fileData, filename: b64DecodeUnicode(data.filemeta[0].filename_text)});
if(display_link === "" || display_link === undefined){
setFileData( {...fileData, display_link: b64DecodeUnicode(data.filemeta[0].filename_text)});
setFileData( {...fileData,
filename: b64DecodeUnicode(data.filemeta[0].filename_text),
display_link: b64DecodeUnicode(data.filemeta[0].filename_text)});
}
},
onError: (data) => {
Expand Down
85 changes: 69 additions & 16 deletions MythicReactUI/src/components/MythicComponents/MythicTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import { snackActions } from '../utilities/Snackbar';
import { MythicDialog } from './MythicDialog';
import {MythicConfirmDialog} from './MythicConfirmDialog';
import DeleteIcon from '@mui/icons-material/Delete';
import WebhookIcon from '@mui/icons-material/Webhook';
import Chip from '@mui/material/Chip';
import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined';
import {MythicStyledTooltip} from "./MythicStyledTooltip";

const createNewTagMutationTemplate = ({target_object}) => {
// target_object should be something like "task_id"
Expand Down Expand Up @@ -120,13 +122,68 @@ const TagChipDisplay = ({tag}) => {
<React.Fragment>
<Chip label={tag.tagtype.name} size="small" onClick={(e) => onSelectTag(e)} style={{float: "right", backgroundColor:tag.tagtype.color}} />
{openTagDisplay &&
<MythicDialog fullWidth={true} maxWidth="md" open={openTagDisplay}
<MythicDialog fullWidth={true} maxWidth="xl" open={openTagDisplay}
onClose={onClose}
innerDialog={<ViewTagDialog onClose={onClose} target_object_id={tag.id}/>}
/>}
</React.Fragment>
)
}
const StringTagDataEntry = ({name, value}) => {
// want to match markdown [display](url)
const regex = "^\\[.*\\]\\(.*\\)";
const captureRegex = "^\\[(?<display>.*)\\]\\((?<url>.*)\\)(?<other>.*)";
const targetRegex = ":target=[\"\'](?<target>.*?)[\"\']";
const colorRegex = ":color=[\"\'](?<color>.*?)[\"\']";
const onClick = (e, url) => {
e.preventDefault();
fetch(url).then((response) => {
if (response.status !== 200) {
snackActions.warning("HTTP " + response.status + " response");
} else {
snackActions.success("Successfully contacted url");
}
}).catch(error => {
if(error.toString() === "TypeError: Failed to fetch"){
snackActions.warning("Failed to make connection - this could be networking issues or ssl certs that need to be accepted first");
} else {
snackActions.warning("Error talking to server: " + error.toString());
}
console.log("There was an error!", error);
})
}
if(RegExp(regex).test(value)){
const capturePieces = RegExp(captureRegex).exec(value);
const targetPieces = RegExp(targetRegex).exec(capturePieces[3]);
const colorPieces = RegExp(colorRegex).exec(capturePieces[3]);
if(targetPieces && targetPieces["groups"]["target"] === "api"){
let color = "textPrimary";
if(colorPieces && colorPieces["groups"]["color"]){
color = colorPieces["groups"]["color"];
}
return (
<MythicStyledTooltip title={"Make API Request"}>
<WebhookIcon style={{cursor: "pointer", marginRight: "10px"}}
onClick={(e) => onClick(e, capturePieces[2])}
color={color}
/>
{capturePieces[1]}
</MythicStyledTooltip>
)
}
return (
<Link href={capturePieces[2]} color="textPrimary" target={"_blank"} referrerPolicy='no'>{capturePieces[1]}</Link>
)
} else if(value.startsWith("http")){
return (
<>
{"Click for: "}
<Link href={value} color="textPrimary" target="_blank" referrerPolicy='no'>{name}</Link>
</>
)
}
return value;
}
function ViewTagDialog(props) {
const theme = useTheme();
const [selectedTag, setSelectedTag] = React.useState({});
Expand Down Expand Up @@ -165,7 +222,7 @@ return (
<TableBody>
<TableRow hover>
<TableCell style={{width: "20%"}}>Tag Type</TableCell>
<TableCell style={{display: "inline-flex", flexDirection: "row-reverse"}}>
<TableCell style={{display: "inline-flex", flexDirection: "row", width: "100%"}}>
<Chip label={selectedTag?.tagtype?.name||""} size="small" style={{float: "right", backgroundColor:selectedTag?.tagtype?.color||""}} />
</TableCell>
</TableRow>
Expand All @@ -182,26 +239,23 @@ return (
<TableRow hover>
<TableCell>Reference URL</TableCell>
<TableCell>
<Link href={selectedTag?.url || "#"} color="textPrimary" target="_blank" referrerPolicy='no'>{selectedTag?.url ? "click here" : "No link provided"}</Link>
<Link href={selectedTag?.url || "#"} color="textPrimary" target="_blank" referrerPolicy='no'>{selectedTag?.url ? "click here" : "No reference link provided"}</Link>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Data</TableCell>
<TableCell>
{selectedTag?.is_json || false ? (
<TableContainer component={Paper} className="mythicElement">
<TableContainer className="mythicElement">
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
<TableBody>
{Object.keys(selectedTag.data).map( key => (
<TableRow key={key} hover>
<TableCell>{key}</TableCell>
{typeof selectedTag.data[key] === "string" ? (
<TableCell>{selectedTag.data[key].startsWith("http") ? (
<>
{"Click for: "}
<Link href={selectedTag.data[key]} color="textPrimary" target="_blank" referrerPolicy='no'>{key}</Link>
</>
) : (selectedTag.data[key])}</TableCell>
<TableCell>
<StringTagDataEntry name={key} value={selectedTag.data[key]} />
</TableCell>
) : typeof selectedTag.data[key] === "object" ? (
<TableCell>{selectedTag.data[key].toString()}</TableCell>
) : typeof selectedTag.data[key] === "boolean" ? (
Expand All @@ -224,7 +278,7 @@ return (
fontSize={14}
showGutter={true}
maxLines={20}
highlightActiveLine={true}
highlightActiveLine={false}
value={selectedTag?.data || ""}
width={"100%"}
setOptions={{
Expand Down Expand Up @@ -296,7 +350,8 @@ export function ViewEditTagsDialog(props) {
});
const [updateTag] = useMutation(updateTagMutationTemplate, {
onCompleted: data => {

snackActions.success("Successfully updated tag");
props.onClose();
},
onError: error => {
snackActions.error("Failed to update: " + error.message);
Expand All @@ -308,8 +363,6 @@ const onSubmit = () => {
if(props.onSubmit !== undefined){
props.onSubmit({source:newSource, url:newURL, data:newData, tag_id:selectedTag.id});
}

props.onClose();
}
const onChangeSource = (name, value, error) => {
setNewSource(value);
Expand Down Expand Up @@ -341,7 +394,7 @@ return (
</DialogTitle>
<DialogContent dividers={true}>
{openNewDialog ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openNewDialog}
(<MythicDialog fullWidth={true} maxWidth="xl" open={openNewDialog}
onClose={()=>{setOpenNewDialog(false);}}
innerDialog={<NewTagDialog me={props.me}
target_object={props.target_object}
Expand Down Expand Up @@ -567,7 +620,7 @@ export const ViewEditTags = ({target_object, target_object_id, me}) => {
<React.Fragment>
<IconButton onClick={(e) => toggleTagDialog(e, true)} size="small" style={{display: "inline-block", float: "right"}}><LocalOfferOutlinedIcon /></IconButton>
{openTagDialog ?
(<MythicDialog fullWidth={true} maxWidth="md" open={openTagDialog}
(<MythicDialog fullWidth={true} maxWidth="xl" open={openTagDialog}
onClose={(e)=>{toggleTagDialog(e, false)}}
innerDialog={<ViewEditTagsDialog me={me} target_object={target_object} target_object_id={target_object_id} onClose={(e)=>{toggleTagDialog(e, false)}} />}
/>) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,12 @@ mutation createTasking($callback_id: Int, $callback_ids: [Int], $command: String
}
}
`;
export const exportCallbackConfigQuery = gql`
query exportCallbackConfigQuery($agent_callback_id: String!) {
exportCallbackConfig(agent_callback_id: $agent_callback_id) {
status
error
config
}
}
`;
19 changes: 18 additions & 1 deletion MythicReactUI/src/components/pages/Callbacks/Callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import TocIcon from '@mui/icons-material/Toc';
import AssessmentIcon from '@mui/icons-material/Assessment';
import { CallbacksTop } from './CallbacksTop';
import Split from 'react-split';
import PhoneForwardedIcon from '@mui/icons-material/PhoneForwarded';
import {ImportPayloadConfigDialog} from "../Payloads/ImportPayloadConfigDialog";
import {MythicDialog} from "../../MythicComponents/MythicDialog";
import {ImportCallbackConfigDialog} from "./ImportCallbackConfigDialog";

const PREFIX = 'Callbacks';

Expand Down Expand Up @@ -216,7 +220,7 @@ export function Callbacks({me}) {
*/
function SpeedDialWrapperPreMemo({ setTopDisplay }) {
const [open, setOpen] = React.useState(false);

const [openCallbackImport, setOpenCallbackImport] = React.useState(false);
const actions = React.useMemo(
() => [
{
Expand All @@ -233,11 +237,24 @@ function SpeedDialWrapperPreMemo({ setTopDisplay }) {
setTopDisplay('graph');
},
},
{
icon: <PhoneForwardedIcon />,
name: "Import Callback",
onClick: () => {
setOpenCallbackImport(true);
}
}
],
[] // eslint-disable-line react-hooks/exhaustive-deps
);
return (
<React.Fragment>
{openCallbackImport &&
<MythicDialog fullWidth={true} maxWidth="sm" open={openCallbackImport}
onClose={()=>{setOpenCallbackImport(false);}}
innerDialog={<ImportCallbackConfigDialog onClose={()=>{setOpenCallbackImport(false);}} />}
/>
}
<StyledSpeedDial
ariaLabel='SpeedDial example'
className={classes.speedDial}
Expand Down
38 changes: 36 additions & 2 deletions MythicReactUI/src/components/pages/Callbacks/CallbacksTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import {
hideCallbackMutation,
lockCallbackMutation,
unlockCallbackMutation,
updateIPsCallbackMutation
updateIPsCallbackMutation,
exportCallbackConfigQuery
} from './CallbackMutations';
import {useMutation } from '@apollo/client';
import {useMutation, useLazyQuery } from '@apollo/client';
import SnoozeIcon from '@mui/icons-material/Snooze';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import {useTheme} from '@mui/material/styles';
Expand All @@ -47,6 +48,8 @@ import moment from 'moment';
import WidgetsIcon from '@mui/icons-material/Widgets';
import {ModifyCallbackMythicTreeGroupsDialog} from "./ModifyCallbackMythicTreeGroupsDialog";
import TerminalIcon from '@mui/icons-material/Terminal';
import ImportExportIcon from '@mui/icons-material/ImportExport';
import {b64DecodeUnicode} from "./ResponseDisplay";

export const CallbacksTableIDCell = React.memo(({rowData, toggleLock, updateDescription, setOpenHideMultipleDialog, setOpenTaskMultipleDialog}) =>{
const dropdownAnchorRef = React.useRef(null);
Expand Down Expand Up @@ -161,6 +164,33 @@ export const CallbacksTableIDCell = React.memo(({rowData, toggleLock, updateDesc
lockCallback({variables: {callback_display_id: rowDataStatic.display_id}})
}
}
const [exportConfig] = useLazyQuery(exportCallbackConfigQuery, {
fetchPolicy: "no-cache",
onCompleted: (data) => {
if(data.exportCallbackConfig.status === "success"){
const dataBlob = new Blob([data.exportCallbackConfig.config], {type: 'text/plain'});
const ele = document.getElementById("download_config");
if(ele !== null){
ele.href = URL.createObjectURL(dataBlob);
ele.download = rowDataStatic.agent_callback_id + ".json";
ele.click();
}else{
const element = document.createElement("a");
element.id = "download_config";
element.href = URL.createObjectURL(dataBlob);
element.download = rowDataStatic.agent_callback_id + ".json";
document.body.appendChild(element);
element.click();
}
}else{
snackActions.error("Failed to export configuration: " + data.exportCallbackConfig.error);
}
},
onError: (data) => {
console.log(data);
snackActions.error("Failed to export configuration: " + data.message)
}
})
const options = [
{name: 'Hide Callback', icon: <VisibilityOffIcon style={{color: theme.palette.warning.main, paddingRight: "5px"}}/>, click: (evt) => {
evt.stopPropagation();
Expand Down Expand Up @@ -211,6 +241,10 @@ export const CallbacksTableIDCell = React.memo(({rowData, toggleLock, updateDesc
evt.stopPropagation();
window.open("/new/callbacks/" + rowDataStatic.display_id, "_blank").focus();
}},
{name: "Export Callback", icon: <ImportExportIcon style={{paddingRight: "5px"}} />, click: (evt) => {
evt.stopPropagation();
exportConfig({variables: {agent_callback_id: rowDataStatic.agent_callback_id}});
}},
{name: "View Metadata", icon: <InfoIcon style={{color: theme.infoOnMain, paddingRight: "5px"}} />, click: (evt) => {
evt.stopPropagation();
setOpenMetaDialog(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export function CallbacksTabsTaskMultipleDialog({onClose, callback, me}) {
}
}
const onTasked = ({tasked, variables}) => {
onClose();
//onClose();
}
const onSubmitCommandLine = (message, cmd, parsed, force_parsed_popup, cmdGroupNames, previousTaskingLocation) => {
//console.log(message, cmd, parsed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export const CallbacksTabsTaskingPanel = ({tabInfo, index, value, onCloseTab, pa
setCommandInfo({...cmd, "parsedParameters": parsed});
}
setOpenParametersDialog(true);
return;

}else{
delete parsed["_"];
onCreateTask({callback_id: tabInfo.displayID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ export function CallbacksTabsTaskingInputPreMemo(props){
value={message}
autoFocus={true}
fullWidth={true}
style={{marginBottom: "10px"}}
style={{marginBottom: "10px", marginTop: "10px"}}
InputProps={{ type: 'search',
endAdornment:
<React.Fragment>
Expand All @@ -1189,7 +1189,7 @@ export function CallbacksTabsTaskingInputPreMemo(props){
startAdornment: <React.Fragment>
{tokenOptions.length > 0 ? (
<CallbacksTabsTaskingInputTokenSelect options={tokenOptions} changeSelectedToken={props.changeSelectedToken}/>
) : (null)}
) : null}

</React.Fragment>

Expand Down
3 changes: 0 additions & 3 deletions MythicReactUI/src/components/pages/Callbacks/CallbacksTop.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ export function CallbacksTop(props){
onError: ({data}) => {
console.log(data)
},
onComplete: ({data}) => {
console.log(data)
}
});
useSubscription(SUB_Edges, {
fetchPolicy: "network-only",
Expand Down
Loading

0 comments on commit af58bc9

Please sign in to comment.